묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨스프링 시큐리티 OAuth2
Ajax 인증시 인가코드가 발급 되지 않는 원인 문의
Spring Authorization 1.0,1 기반으로 개발을 하고 있습니다. 인가코드를 발급 할떄 FormLogin 기본 설정을 사용하면 인가코드가 발급이 되는데 Ajax 로 로그인을 하면 인가코드가 발급되지 않고 있습니다. 디버깅을 해보면 로그인인 후 OAuth2AuthorizationEndpointFilter 는 실행되는데 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (!this.authorizationEndpointMatcher.matches(request)) { filterChain.doFilter(request, response); return; } try { Authentication authentication = this.authenticationConverter.convert(request); if (authentication instanceof AbstractAuthenticationToken) { ((AbstractAuthenticationToken) authentication) .setDetails(this.authenticationDetailsSource.buildDetails(request)); } Authentication authenticationResult = this.authenticationManager.authenticate(authentication); if (!authenticationResult.isAuthenticated()) { // If the Principal (Resource Owner) is not authenticated then // pass through the chain with the expectation that the authentication process // will commence via AuthenticationEntryPoint filterChain.doFilter(request, response); return; } if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) { if (this.logger.isTraceEnabled()) { this.logger.trace("Authorization consent is required"); } sendAuthorizationConsent(request, response, (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication, (OAuth2AuthorizationConsentAuthenticationToken) authenticationResult); return; } this.authenticationSuccessHandler.onAuthenticationSuccess( request, response, authenticationResult); } catch (OAuth2AuthenticationException ex) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorization request failed: %s", ex.getError()), ex); } this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); } } FormLogin 적용시에는 authenticationResult의 principal 에 UsernamePasswordAuthenticationToken이 설정되어 인가 코드가 정상적으로 발급되는데 AjaxLogin 적용시에는 authenticationResult의 principal 에 AnonymousAuthenticationToken이 설정되어 인가 코드가 정상적으로 발급되지 않고 403 예외가 발생합니다.AuthenticationProvider 구현체에서는 정상적으로 토큰을 저장하고 있습니다. AuthenticationProvider 구현체 소스@Component @RequiredArgsConstructor public class CustomAuthenticationProvider implements AuthenticationProvider { private final CustomUserDetailsService customUserDetailsService; private final PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if(authentication == null){ throw new InternalAuthenticationServiceException("Authentication is null"); } LoginRequestDto loginRequestDto = (LoginRequestDto)authentication.getPrincipal(); String password = loginRequestDto.getLoginPassword(); UserAdapter userAdapter = (UserAdapter) customUserDetailsService.loadUserByLoinRequestDto(loginRequestDto); if (!passwordEncoder.matches(password, userAdapter.getCurrentUser().getLoginPwd())) { throw new BadCredentialsException("BadCredentialsException"); } CustomAuthenticationToken result = CustomAuthenticationToken.authenticated(userAdapter.getCurrentUser(), authentication.getCredentials(), userAdapter.getAuthorities()); result.setDetails(authentication.getDetails()); return result; } @Override public boolean supports(Class<?> authentication) { return CustomAuthenticationToken.class.isAssignableFrom(authentication); } } 이외 Custom 소스Spring Security 설정@EnableWebSecurity @RequiredArgsConstructor @Configuration public class DefaultSecurityConfig { private final CustomAuthenticationProvider customAuthenticationProvider; // private final CustomUserDetailsService customUserDetailsService; @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public CustomAuthenticationProcessingFilter customAuthenticationProcessingFilter() throws Exception { CustomAuthenticationProcessingFilter filter = new CustomAuthenticationProcessingFilter(); // filter.setAuthenticationManager(authenticationManager(null)); filter.setAuthenticationManager(new ProviderManager(customAuthenticationProvider)); // filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler()); // filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler()); return filter; } // @formatter:off @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests ->authorizeRequests .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .requestMatchers(new AntPathRequestMatcher("/")).permitAll() .requestMatchers(new AntPathRequestMatcher("/login/**")).permitAll() .requestMatchers("/api/login/**").permitAll() .requestMatchers("/api/registered-client/**").permitAll() .anyRequest().authenticated() ); http.addFilterBefore(customAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); http.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer .authenticationEntryPoint(new CustomLoginAuthenticationEntryPoint()) .accessDeniedHandler(customAccessDeniedHandler()) ); // http.userDetailsService(customUserDetailsService); // http.formLogin(); http.csrf().disable(); return http.build(); } // @formatter:on @Bean public AccessDeniedHandler customAccessDeniedHandler() { return new CustomAccessDeniedHandler(); } @Bean public AuthenticationSuccessHandler customAuthenticationSuccessHandler() { return new CustomAuthenticationSuccessHandler(); } @Bean public AuthenticationFailureHandler customAuthenticationFailureHandler() { return new CustomAuthenticationFailureHandler(); } } Ajax 로그인 처리 필터 소스public class CustomAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { private final ObjectMapper objectMapper = new ObjectMapper(); private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/api/login", HttpMethod.POST.name()); public CustomAuthenticationProcessingFilter() { super(DEFAULT_ANT_PATH_REQUEST_MATCHER); } public CustomAuthenticationProcessingFilter(AuthenticationManager authenticationManager) { super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } LoginRequestDto loginRequestDto = objectMapper.readValue(request.getReader(), LoginRequestDto.class); if(StringUtils.isEmpty(loginRequestDto.getLoginId())||StringUtils.isEmpty(loginRequestDto.getLoginPassword())) { throw new IllegalStateException("Username or Password is empty"); } CustomAuthenticationToken authRequest = CustomAuthenticationToken.unauthenticated(loginRequestDto, loginRequestDto.getLoginPassword()); authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); return getAuthenticationManager().authenticate(authRequest); } } CustomAuthenticationToken 소스public class CustomAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private final Object principal; private Object credentials; public CustomAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); } public CustomAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); // must use super, as we override } public static CustomAuthenticationToken unauthenticated(Object principal, Object credentials) { return new CustomAuthenticationToken(principal, credentials); } public static CustomAuthenticationToken authenticated(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { return new CustomAuthenticationToken(principal, credentials, authorities); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } }
-
해결됨스프링 프레임워크는 내 손에 [스프1탄]
23.Spring MVC02 버전 만들기(Ajax, jQuery)
SPringMVC01 폴더를 복사 후, SpringMVC02 폴더로 변경해서 강의 순서대로 실행을 했는데, Mapping 경로를 찾지 못하는 오류가 발생했습니다. 그래서 해결 방법으로 [appServlet] - [servlet-context.xml] 파일 안에 19번째 줄 기존의 파일 내용 일부분을 수정했습니다. 이렇게 해도 괜찮을까요 ? 다른 방법이 있으면 알고 싶어요 ㅠ[servlet-context.xml] - 수정 전 <resources mapping="/resources/**" location="/resources/" /> [servlet-context.xml] - 수정 <resources mapping="/assets/**" location="/resources/assets/" /> <resources mapping="/ad_assets/**" location="/resources/ad_assets/" /> a
-
미해결스프링 프레임워크는 내 손에 [스프1탄]
Loading descript for 문제
안녕하세요! 나프에 이어 스프 강의까지 열심히 따라 가고 있는 학생입니다.강의 따라 열심히 실습하고 있는데요, API 설치 이후 위와 같은 팝업이 계속 뜨고 있습니다. 혼자 해결해보려고 구글 검색해봤지만, 뭐가 문제인지 모르겠습니다. ㅠ서버도 잘 작동되서 화면도 띄어지고 콘솔창에도 아무 문제가 나오지 않는데, 어떻게 해결하면 좋을까요?
-
미해결토비의 스프링 부트 - 이해와 원리
자동 구성 정보 분리가 안됩니다.
안녕하세요 자동 구성 정보 분리쪽 강의를 따라 구현하던 도중 문제가 발생하여 질문을 남깁니다. return new String[]{ "tobyspring.config.autoconfig.TomcatWebServerConfig", "tobyspring.config.autoconfig.DispatcherServletConfig" };직접 경로를 작성해주는 경우에는 문제 없이 동작합니다.Iterable<String> candidates = ImportCandidates.load(MyAutoConfiguration.class, classLoader); return StreamSupport.stream(candidates.spliterator(), false).toArray(String[]::new);클래스 로더를 이용해서 외부에서 파일 경로를 불러오는 방법을 사용하면 아래와 같은 문제가 발생합니다.오후 11:26:47: Executing ':HellobootApplication.main()'... > Task :compileJava > Task :processResources UP-TO-DATE > Task :classes > Task :HellobootApplication.main() FAILED . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.8) 2023-02-27 23:26:49.370 INFO 3532 --- [ main] t.helloboot.HellobootApplication : Starting HellobootApplication using Java 17.0.6 on yj_notebook with PID 3532 (C:\git\TID\helloboot\build\classes\java\main started by deter in C:\git\TID\helloboot) 2023-02-27 23:26:49.374 INFO 3532 --- [ main] t.helloboot.HellobootApplication : No active profile set, falling back to 1 default profile: "default" 2023-02-27 23:26:49.641 WARN 3532 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.context.MissingWebServerFactoryBeanException: No qualifying bean of type 'org.springframework.boot.web.servlet.server.ServletWebServerFactory' available: Unable to start AnnotationConfigServletWebServerApplicationContext due to missing ServletWebServerFactory bean 2023-02-27 23:26:49.667 ERROR 3532 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context. Action: Check your application's dependencies for a supported servlet web server. Check the configured web application type. Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. See https://docs.gradle.org/7.6/userguide/command_line_interface.html#sec:command_line_warnings 3 actionable tasks: 2 executed, 1 up-to-date FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':HellobootApplication.main()'. > Process 'command 'C:/Users/deter/.jdks/azul-17.0.6/bin/java.exe'' finished with non-zero exit value 1 * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 2s 오후 11:26:49: Execution finished ':HellobootApplication.main()'. 제가 생각하기로는 클래스 로더를 통해서 구성 정보를 불러와야 하는데 못 불러오고 있는 것 같습니다.package tobyspring.config.autoconfig; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.Bean; import tobyspring.config.MyAutoConfiguration; @MyAutoConfiguration public class TomcatWebServerConfig { @Bean public ServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); } } 이 문제를 해결하기 위해서java 버전 11, 17gradle 버전 7.6, 8.0Build and run 을 Geadle, IntelliJ파일 경로 수정 등로 변경하는 방법들을 시도해봤지만 해결하지 못했습니다. 현재 제 개발 환경은 아래와 같습니다.IntelliJ버전은 2022.2.3 Community버전spring boot 2.7.8gradle 7.6어디서 문제가 발생하는지 감을 잡을 수 없어서 문의드립니다.
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
JdbcTemplate 테스트에서의 중복 회원 예외 오류가 뜨는 이유를 모르겠습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (비슷한 내용은 있었지만 원하는 해답을 찾지 못했습니다)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]여기에 질문 내용을 남겨주세요.차근차근 강의 예제를 따라가면서 잘 오고 있었는데 테스트 하는 부분에서 갑작스런 오류로 인해 해답을 찾던 도중 어려움을 겪어서 프로젝트 링크와 오류 사진을 남기겠습니다https://drive.google.com/file/d/1DIAzsFD6RnTCv73h-kNpeDWUE5CsF8oC/view?usp=share_link
-
해결됨스프링 DB 1편 - 데이터 접근 핵심 원리
테스트 코드에서의 생성자 의존관계 주입 관련하여 질문 드립니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.안녕하세요. 강의 복습 중 해결되지 않는 의문점이 있어서 질문 드립니다. 테스트 코드에서 @TestCOnfiguration을 통해 해당 테스트 클래스에서 사용할 빈들을 등록을 하고, 의존성 주입을 할 때필드 주입은 정상 동작하지만, 생성자 주입은 정상 작동하지 않는지 의문이 생겨 질문 드립니다. 영한님께서는 필드 주입을 하셨고, 저는 복습 중에 생성자 주입으로 한 번 해볼까 하여 시도를 해봤는데 정상 동작하지 않았습니다. 아래는 생성자 주입을 시도해본 코드입니다./** * JDBC - 트랜잭션매니저를 통한 트랜잭션 + 트랜잭션 AOP(@Transactional) */ @SpringBootTest @RequiredArgsConstructor class MemberServiceV3_3Test { private static final String MEMBER_A = "memberA"; private static final String MEMBER_B = "memberB"; private static final String MEMBER_EX = "ex"; private final MemberServiceV3_3 memberService; private final MemberRepositoryV3 memberRepository; @TestConfiguration static class TestConfig { @Bean DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); return dataSource; } @Bean PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean MemberRepositoryV3 memberRepositoryV3() { return new MemberRepositoryV3(dataSource()); } @Bean MemberServiceV3_3 memberServiceV3_3() { return new MemberServiceV3_3(memberRepositoryV3()); } } @AfterEach void after() throws SQLException { memberRepository.delete(MEMBER_A); memberRepository.delete(MEMBER_B); memberRepository.delete(MEMBER_EX); } @Test @DisplayName("정상 이체") void accountTransfer() throws SQLException { memberRepository.save(new Member(MEMBER_A, 10000)); memberRepository.save(new Member(MEMBER_B, 10000)); // 커밋 memberService.accountTransfer(MEMBER_A, MEMBER_B, 2000); Member fromMember = memberRepository.findById(MEMBER_A); Member toMember = memberRepository.findById(MEMBER_B); assertThat(fromMember.getMoney()).isEqualTo(8000); assertThat(toMember.getMoney()).isEqualTo(12000); } @Test @DisplayName("이제 중 예외 발생") void accountTransferEx() throws SQLException { memberRepository.save(new Member(MEMBER_A, 10000)); memberRepository.save(new Member(MEMBER_EX, 10000)); // 예외 발생 // 롤백 assertThatThrownBy(() -> memberService.accountTransfer(MEMBER_A, MEMBER_EX, 2000)) .isInstanceOf(IllegalStateException.class); Member fromMember = memberRepository.findById(MEMBER_A); Member toMember = memberRepository.findById(MEMBER_EX); // 정상 이체 X // 롤백을 통해 돈은 트랜잭션 시작 전으로 복구된다. assertThat(fromMember.getMoney()).isEqualTo(10000); assertThat(toMember.getMoney()).isEqualTo(10000); } } (추가 질문)혹시나 해서 final 키워드를 제거하고 시도해보니 생성자 주입도 정상 동작하는 것을 발견했습니다. 테스트 코드를 실행할 때 어떤 과정 때문에 이러한 현상이 발생하는 지 궁금합니다.. 아래는 final 키워드를 제거한 코드입니다./** * JDBC - 트랜잭션매니저를 통한 트랜잭션 + 트랜잭션 AOP(@Transactional) */ @SpringBootTest class MemberServiceV3_3Test { private static final String MEMBER_A = "memberA"; private static final String MEMBER_B = "memberB"; private static final String MEMBER_EX = "ex"; private MemberServiceV3_3 memberService; private MemberRepositoryV3 memberRepository; @Autowired public MemberServiceV3_3Test(MemberServiceV3_3 memberService, MemberRepositoryV3 memberRepository) { this.memberService = memberService; this.memberRepository = memberRepository; } @TestConfiguration static class TestConfig { @Bean DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); return dataSource; } @Bean PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean MemberRepositoryV3 memberRepositoryV3() { return new MemberRepositoryV3(dataSource()); } @Bean MemberServiceV3_3 memberServiceV3_3() { return new MemberServiceV3_3(memberRepositoryV3()); } } ..... }
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
31강 대출기능에서 테이블 생성에 대한 질문입니다.
대출기능을 개발하려고 하니,저희가 가진 두 테이블, User와 Book은 서로를 가리킬 필드가 없어서 '대출했다.'라는 정보를 표시할 수 없었습니다.그래서 user_loan_history 테이블을 새로 만드는 내용이 강의의 주된 내용이 되는데요,제가 궁금한건 여기서 User와 Book 테이블을 수정하여, 예를들면 User테이블에는 OneToMany로 Book의 id를 가리킬 수 있는 필드를, Book테이블에는 ManyToOne으로 User의 id를 가리킬 수 있는 필드를 추가하여 개발할 수도 있지 않나 싶어서요!객체지향적으로 생각했을 때 User와 Book은 객체지만, '대출기록'은 객체가 아니라 객체간의 관계 같아서 테이블로 만드는 것에 거부감이 생기는 것 같습니다.어떤 이유에서 기존의 테이블을 수정하지 않고, 새로운 테이블을 만들었는지 궁금합니다!
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
SpringConfig에 JdbcMemberRepsoitory반환 오류
[질문 내용]SpringConfig클래스에 MemoryMemberRepsoitory 메소드에 retrun new JdbcMemberRepository();를 하고dataSource 객체 생성 후 생성자까지 만들어주었습니다.여기까지는 ()에 오류가 생기는데 여기에 dataSource를 넣어주면 전체오류가 생기여 옵션엔터를 눌러 해결하면 메소드 이름을 MemoryMemberRepository가 아닌 JdbcMemeberRepository로 바꿔라 합니다.이를 변경하면 위에 MemberService도 바꿔야하며 전체코드에 이상이 생깁니다.package hello.hellospring; import hello.hellospring.repository.JdbcMemberRepository; import hello.hellospring.repository.MemoryMemberRepository; import hello.hellospring.service.MemberService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class SpringConfig { DataSource dataSource; public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; } @Bean public MemberService memberService(){ return new MemberService(memberRepository()); } @Bean public MemoryMemberRepository memberRepository(){ //return new MemoryMemberRepository(); return new JdbcMemberRepository(dataSource); //오류발생 } }java: incompatible types: hello.hellospring.repository.JdbcMemberRepository cannot be converted to hello.hellospring.repository.MemoryMemberRepository이러한 오류내용이 뜹니다.수업 소스코드와 동일하게 해봤는데 이럽니다!
-
해결됨재고시스템으로 알아보는 동시성이슈 해결방법
Spring Boot 3.0.2~ nativeQuery 작성시 에러
안녕하세요 강의듣다가 막혔다 해결한 부분이 있어서 혹여나 동일한 문제를 겪고 있으신 분이 계실까봐 공유드립니다.named lock파트의 native query를 작성하는 부분에서 강의 코드와 동일하게 작성하였음에도 불구하고 스프링 빈을 초기화 하는 과정에서 다음과 같은 에러를 만나게 되었습니다.작성한 코드 @Query("select get_lock(:key, 3000)", nativeQuery = true) fun getLock(key: String) @Query("select release_lock(:key)", nativeQuery = true) fun releaseLock(key: String)발생한 에러Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract void com.waterfogsw.cucurrentsolutions.domain.LockRepository.getLock(java.lang.String); Reason: Cannot invoke "String.contains(java.lang.CharSequence)" because "variable" is null at app//org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:115) at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:99) at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:88) at java.base@17.0.6/java.util.Optional.map(Optional.java:260) at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.<init>(QueryExecutorMethodInterceptor.java:88) at app//org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:357) at app//org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279) at app//org.springframework.data.util.Lazy.getNullable(Lazy.java:245) at app//org.springframework.data.util.Lazy.get(Lazy.java:114) at app//org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:285) at app//org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1798) at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1748) ... 122 more Caused by: java.lang.NullPointerException: Cannot invoke "String.contains(java.lang.CharSequence)" because "variable" is null at org.springframework.data.jpa.repository.query.QueryUtils.createCountQueryFor(QueryUtils.java:620) at org.springframework.data.jpa.repository.query.DefaultQueryEnhancer.createCountQueryFor(DefaultQueryEnhancer.java:49) at org.springframework.data.jpa.repository.query.StringQuery.deriveCountQuery(StringQuery.java:111) at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.<init>(AbstractStringBasedJpaQuery.java:82) at org.springframework.data.jpa.repository.query.NativeJpaQuery.<init>(NativeJpaQuery.java:58) at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:53) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:170) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:252) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:95) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:111) ... 134 more 동일한 강의를 수강중이던 지인분과 함께 비교해본 결과 스프링 부트 버전 문제임을 확인하였습니다.지인분은 data jpa 3.0.1 버전을 사용중이셨고, 저는 data jpa 3.0.3버전을 사용하였는데 3.0.2 이상 버전에서 nativeQuery=true 사용시 NullPointerException이 발생하는 이슈가 있음을 알려드립니다.저는 부트버전을 3.0.1로 다운그레이드하여 정상적으로 실습 진행할 수 있었습니다 :)https://github.com/spring-projects/spring-data-jpa/issues/2812
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
CalculatorAddRequest 질문
안녕하세요 선생님.CalculatorAddRequest 클래스에서 number1과 number2를 private final로 설정하는 이유가 무엇인가요?감사합니다.
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
@ArgumentResolver = ReturnValueHandler 같은 말인가요?
위 관계도에서@ResponseBody의 경우에 ReturnValueHandler를 사용한다고 합니다.@ResponseBody가 있으면 ArgumentResolver를 사용한다고 합니다. 이 관계도를 보고 ArgumentResolver와 ReturnValueHandler는서로 관련없다고 생각했었는데요.사실은 ArgumentResolver = ReturnValueHandler이런식이여서@ResponseBody가 있으면 ArgumentResolver를 사용한다는게ReturnValueHandler를 사용한다는 말과 같다는뜻인가요?
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
loanBook 메소드 만들 때 유저정보 가져오는 코드에서 오류가 납니다
예외 처리 부분이니까 주석 처리하면 실행될까 했는데 아래와 같은 에러메시지가 나옵니다. 혹시 몰라 전체 코드를 깃헙에 업로드해놓겠습니다!https://github.com/you-eun-hye/library-app-Inflearn
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
@Aspect 와 @Transactional
강의를 듣던중 궁금한 부분이 생겨 질문남깁니다@Transactional 어노테이션을 표기함으로써 해당 클래스의 프록시가 생성되고, AOP 또한 pointcut에 해당하는 클래스의 프록시를 생성해주는 것으로 알고 있습니다.여기서 궁금한 점이 있습니다.만약 @Transactional 과 aop 설정을 같은 메서드에 걸어준다면 어떤 형태로 프록시가 생성되는 걸까요?먼저 @Transactional 관련 프록시가 생성된다음, aop에서 앞서 만들어진 프록시를 대상으로 새로운 프록시를 또 만들어 컨테이너에 넣어주는 것 일까요?질문드린 것과 같이 생각하며 공부하던 중,@Transactional(readonly = true)를 적용하면 aop 코드에서 트랜잭션 관련 설정을 하지 않았음에도 해당 트랜잭션이 그대로 이어져 aop에서도 readonly 가 true 설정되어 있음을 확인하였고, 이러한 상황이 이해가 가지 않아 질문드리게 되었습니다.
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
ArgumentResolver,returnValueResolver 이해한게 맞을까요?
코드를 복습하다가 공통점과강사님께서 설명하는 부분이 제가 이해한게 맞는지 확인하고 싶어서 문의를 남겨요public class ControllerV4HandlerAdapter implements MyHandlerAdapter { @Override public ModelView handler(HttpServletRequest request,HttpServletResponse response,Object Hnadler){ ControllerV4 controller = (ControllerV4) handler; //createParamMap,model ==> ArgumentResolver 역할 Map<String,String> paramMap = createParamMap(request); Map<String,Object> model = new HashMap<>(); String viewName = controller.process(paramMap); //아래 로직이 ==> returnValueResolver ModelView mv = new ModelView(viewName); mv.setModel(model) return mv; } }이렇게 이해해도 될까요 ??
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
errorPage500Api 함수 내에서 ex 가 null 로 return 되네요.
errorPage500Api 함수 내에서 ex 가 null 로 return 되어 postman 결과가 json 으로 오지 않고 body 가 비었습니다. 오류는 아래와 같네요. java.lang.NullPointerException: Cannot invoke "java.lang.Exception.getMessage()" because "ex" is null at hello.exception.servlet.ErrorPageController.errorPage500Api(ErrorPageController.java:39) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[spring-web-6.0.5.jar:6.0.5] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[spring-web-6.0.5.jar:6.0.5] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.5.jar:6.0.5] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.0.5.jar:6.0.5] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[tomcat-embed-core-10.1.5.jar:6.0] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.5.jar:6.0.5] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[tomcat-embed-core-10.1.5.jar:6.0] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.5.jar:6.0.5] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.5.jar:6.0.5] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.5.jar:6.0.5] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:691) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:443) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:367) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:295) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:372) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:296) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:153) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.5.jar:10.1.5] at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]2023-02-26T09:40:16.190+09:00 ERROR 54414 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost] : Exception Processing ErrorPage[exceptionType=java.lang.RuntimeException, location=/error-page/500] 어떻게 수정가능할까요?
-
해결됨스프링 핵심 원리 - 기본편
@Configuration과 싱글톤강의를 따라하는데 값이 다르게 나옵니다.
@Configuration 어노테이션 안의 AppConfig 클래스에서 memberService와 orderService에 들어가는 memberRepository 설정이 각각 new로 MemoryMemberRepository를 생성한다 하더라도 그 MemberRepository들은 같은 싱글톤으로 있다는 강의 내용은 전부 이해했습니다.그런데 강의 내용 그대로 따라 코딩을 하였는데 결과값은 각각 다른 MemberRepository를 갖게 되더라구요... 그래서 제가 잘못한 것이 있는지 알 수 있을까 해서 질문드립니다.AppConfig@Configuration public class AppConfig { @Bean public static MemberRepository memberRepository() { return new MemoryMemberRepository(); } @Bean public static DiscountPolicy discountPolicy() { // return new FixDiscountPolicy(); return new RateDiscountPolicy(); } @Bean public MemberService memberService() { return new MemberServiceImpl(memberRepository()); } @Bean public OrderService orderService() { return new OrderServiceImpl(memberRepository(), discountPolicy()); } }MemberServiceImplpublic class MemberServiceImpl implements MemberService{ private final MemberRepository memberRepository; public MemberServiceImpl(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Override public void join(Member member) { memberRepository.save(member); } @Override public Member findMember(Long memberId) { return memberRepository.findById(memberId); } // 스프링의 @Configuration 싱글톤 테스트를 위한 Getter public MemberRepository getMemberRepository() { return memberRepository; } }OrderServiceImplpublic class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } @Override public Order createOrder(Long memberId, String itemName, int itemPrice) { Member member = memberRepository.findById(memberId); int discountPrice = discountPolicy.discount(member, itemPrice); return new Order(memberId, itemName, itemPrice, discountPrice); } // 스프링의 @Configuration 싱글톤 테스트를 위한 Getter public MemberRepository getMemberRepository() { return memberRepository; } }ConfigurationSingletonTestpublic class ConfigurationSingletonTest { @Test void configurationTest() { ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class); OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class); MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class); System.out.println("memberService -> memberRepository = " + memberService.getMemberRepository()); System.out.println("orderService -> memberRepository = " + orderService.getMemberRepository()); System.out.println("memberRepository = " + memberRepository); assertThat(memberService.getMemberRepository()).isSameAs(memberRepository); assertThat(orderService.getMemberRepository()).isSameAs(memberRepository); } }결과제가 강의 내용을 이해한대로라면 저렇게 3개가 같은 MemoryMemberRepository를 가져야 할것같은데 제가 뭔가 잘못 작성한것이 있을까요...?
-
해결됨스프링 핵심 원리 - 기본편
테스트에서 @BeforeEach 안에 AppConfig를 생성하고 사용하는 이유
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요 단위 테스트 도중에 @BeforeEach 사용시 어떤 이점이 있는지 궁금해서 질문 드립니다.public class orderServiceTest { AppConfig appConfig = new AppConfig(); MemberService memberService = appConfig.memberService(); OrderService orderService = appConfig.orderService(); }이렇게 사용하는 것보다 @BeforEach 어노테이션을 사용해서public class orderServiceTest { MemberService memberService; OrderService orderService; @BeforeEach public void beforeEach(){ AppConfig appConfig = new AppConfig(); memberService = appConfig.memberService(); orderService = appConfig.orderService(); }이렇게 작성하는게 좋다고 말씀하셨는데제 생각에는 그 이유가 1번 처럼 작성하면 다른 테스트 진행시 기존의 선언되어 있는 meberService 객체에 남아있는 데이터?가 있어서 오류가 날 수 있어 초기화와 같은 목적으로 @BeforeEach를 사용하여 매번 테스트를 실행시킬때마다 새롭게 의존관계 주입을 하는 것 같은데정확한 의미를 알고 싶어서 질문 드립니다.
-
해결됨스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
httpSession.setAttribute를 연속 두번 사용 했을 때 구조 질문
안녕하세요. 강의 정주행중입니다!보다가 세션의 원리에 대해 자세히 알게되었습니다만 한 가지 궁금한 점이 있어서 질문을 남깁니다.loginV3에서 session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);위 코드를 통해 세션 저장소에 세션 정보를 생성하고 response에 쿠키 정보를 담아서 클라이언트에 전달하는 것까지 이해가 되었습니다. 그래서 쿠키 저장소를 보면 잘 조회가 됩니다.만약에 위 상태에서 session.setAttribute("mem", "123"); 를 한번 더 사용하면 위 사진처럼 JSESSIONID가 아닌 다른 Name으로 row 데이터가 추가될 줄 알았는데 그게 아니더군요. (JESSIONID 하나만 있었습니다.)그래서 만약 한 컨트롤러에 session.setAttribute 를 두번 사용하게 되면 아래와 같은 구조가 되는게 맞는건가요??위 그림처럼 되면 JESSIONID 하나만 있어도 value 자체가 Map이기 때문에 HttpSession.getAttribute("SessionConst.LOGIN_MEMBER");를 조회하면 loginMember 객체가HttpSession.getAttribute("mem");를 조회하면 "123" 문자열이 반환되는 것이 맞을까요??
-
미해결토비의 스프링 부트 - 이해와 원리
spring: command not fount 왜 뜨는걸까요 ?
spring boot 가 설치되었다고 나오는데 spring 명령어를 치면 command not found 라고 뜨네요.. 혹시 해결방법이 없을까요 ?
-
미해결스프링과 JPA 기반 웹 애플리케이션 개발
모임 만들기 페이지에서 시간을 설정할 때, 연월일 제외하고 시간만 입력하려면 어떻게 하면 되는지요?
안녕하세요. 모임 만들기 페이지에서 시간을 설정할 때,연월일 제외하고 시간만 입력하려면 어떻게 하면 되는지요?fragments.html 의 <div th:fragment="event-form (mode, action)"> 에서<input id="endEnrollmentDateTime" type="datetime-local"의 type 부분을 type="time" 으로 하고Event 클래스에서 private LocalDateTime endEnrollmentDateTime;을private DateTime endEnrollmentDateTime;로 변경하고 실행하면 html 상에서는 시간이 입력되나,DB 에는 insert 되어 있지 않습니다.어떻게 하면 가능한지요?자세한 설명 부탁드립니다. 그리고, 제공해 주신 소스를 다운로드해서 프로젝트를 실행한 후, 오른쪽 드롭다운 메뉴 중스터디를 클릭해도 아무 동작이 일어나지 않습니다.소스를 보면<a class="dropdown-item" >스터디</a>이렇게만 나와있고 th:href="@{}" 로 연결된 페이지가 없습니다.이 부분 기능 구현은 안 해 놓으신 건지요? 프로필 페이지에서도왼쪽 프로필 사진 밑에 있는스터디 버튼을 누르면 Study 라고만 나오고별다른 페이지가 나오지 않습니다.이 부분도 기능 구현은 안 해 놓으신 건지요?