묻고 답해요
130만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
OneToMany 페치 조인 문제
현재 그룹 조회 API를 구현하고 있습니다. 메신저 앱 처럼 그룹 조회시 그룹 아이디, 그룹 이름, 그룹 이미지, 그룹에 속해있는 멤버들 (컬렉션)을 조회해 와야합니다. 여기서 강의의 V3 내용처럼 1 + N 문제를 해결하기 위해서 다음과 같이 페치 조인을 사용하여 쿼리문을 작성하였습니다. TeamRepository.java @Transactional(readOnly = true) public interface TeamRepository extends JpaRepository<Team, Long> { @Query("select distinct t from Team t" + " join fetch t.id id" + " join fetch t.groupName gn" + " join fetch t.imageUrl iu" + " join fetch t.teamMembers tm" + " join fetch tm.member m" + " where tm.id = :id") List<SearchGroupResponse> findGroupInfoWithMembers(@Param("id") Long id); } `Team.java` @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Team extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; @Column(length = 30) private String groupName; @Lob private String imageUrl; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "place_id") private Place place; @OneToMany(mappedBy = "team", cascade = CascadeType.ALL) private final List<TeamMember> teamMembers = new ArrayList<>(); private LocalDateTime date; } TeamMember.java @Entity @Getter @Table(name = "GroupMember") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class TeamMember extends BaseEntity { @ManyToOne(fetch = LAZY) @JoinColumn(name = "member_id") private Member member; @ManyToOne(fetch = LAZY) @JoinColumn(name = "team_id") private Team team; @Column(length = 100) private String curLocate; private char arrived; public TeamMember(Member member, Team team) { this.member = member; this.team = team; } } 강의 V3-1 과 같이 컬랙션 데이터는 지연 로딩과 BatchSize를 적용하여 성능 최적화를 해줘야 된다고 배웠지만 일단은 V3를 적용해 보고 개선해 보고자 이렇게 코드를 작성해 보았습니다. 하지만 다음과 같은 에러가 발생하였고 이를 해결하고자 여러 구글링을 해보았지만 해답을 찾지 못하여 질문 남겨봅니다. org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'promiseController' defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/domain/promise/api/PromiseController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'promiseService' defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/domain/promise/service/PromiseService.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'teamRepository' defined in promisor.promisor.domain.team.dao.TeamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! Reason: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)!; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.15.jar:5.3.15] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.15.jar:5.3.15] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.3.jar:2.6.3] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[spring-boot-2.6.3.jar:2.6.3] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:414) ~[spring-boot-2.6.3.jar:2.6.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) ~[spring-boot-2.6.3.jar:2.6.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.6.3.jar:2.6.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.6.3.jar:2.6.3] at promisor.promisor.PromisorApplication.main(PromisorApplication.java:12) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[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:564) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.6.3.jar:2.6.3] Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'promiseService' defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/domain/promise/service/PromiseService.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'teamRepository' defined in promisor.promisor.domain.team.dao.TeamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! Reason: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)!; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.15.jar:5.3.15] ... 24 common frames omitted Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'teamRepository' defined in promisor.promisor.domain.team.dao.TeamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! Reason: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)!; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.15.jar:5.3.15] ... 38 common frames omitted Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! Reason: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)!; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:106) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$mapMethodsToQuery$1(QueryExecutorMethodInterceptor.java:94) ~[spring-data-commons-2.6.1.jar:2.6.1] at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na] at java.base/java.util.Collections$UnmodifiableCollection$1.forEachRemaining(Collections.java:1056) ~[na:na] at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:96) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:86) ~[spring-data-commons-2.6.1.jar:2.6.1] at java.base/java.util.Optional.map(Optional.java:258) ~[na:na] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.<init>(QueryExecutorMethodInterceptor.java:86) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:364) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:322) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.util.Lazy.getNullable(Lazy.java:230) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.util.Lazy.get(Lazy.java:114) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:328) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:144) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.15.jar:5.3.15] ... 49 common frames omitted Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List promisor.promisor.domain.team.dao.TeamRepository.findGroupInfoWithMembers(java.lang.Long)! at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:96) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.data.jpa.repository.query.SimpleJpaQuery.<init>(SimpleJpaQuery.java:66) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:51) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:163) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:252) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:87) ~[spring-data-jpa-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:102) ~[spring-data-commons-2.6.1.jar:2.6.1] ... 71 common frames omitted Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.hql.internal.ast.tree.FromElement.setAllPropertyFetch(boolean)" because "fromElement" is null at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromJoinElement(HqlSqlWalker.java:431) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.joinElement(HqlSqlBaseWalker.java:3990) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3776) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3654) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:737) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:593) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:330) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:278) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:636) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:748) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:114) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[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:564) ~[na:na] at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362) ~[spring-orm-5.3.15.jar:5.3.15] at com.sun.proxy.$Proxy139.createQuery(Unknown Source) ~[na:na] at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:90) ~[spring-data-jpa-2.6.1.jar:2.6.1] ... 77 common frames omitted
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
batch size 와 Limit 질문입니다
안녕하세요 영한님! 강의 재밌고 유익하게 잘 보고 있습니다. 토이 프로젝트를 진행하면서 Batch SIze 와 Limit 에 대한 궁금증이 생겨서 질문 드리게 되었습니다. # 명세 도메인을 대략적으로 설명 드리면 한 명의 사용자는 여러 게시글을 작성할 수 있고 각 게시글에는 여러 개의 댓글이 달릴 수 있습니다. 사용자 (Member) 1 -> 게시글 (Post) N -> 댓글 (Comment) M 제가 뽑고자 하는 데이터는 여러 사용자가 작성한 게시글과 댓글인데, 여기서 페이징을 적용하기 위해 limit 을 사용하려고 합니다. 여러 Member 가 작성한 Post 를 5 개만 뽑고 각 Post 에 달린 Comment 는 3 개만 뽑는 로직을 짜려고 합니다. default_batch_fetch_size 는 1000 으로 설정했습니다. # 질문 1. 먼저 Post 를 5 개 뽑아야 하는데 이런 경우 쿼리 자체에 Limit 을 거는 게 좋을까요? // (1) 직접 순회 List<Member> members = memberRepository.findAll(); // 편의를 위해 findAll 로 했습니다. 실제로는 조건이 있음!! List<Post> posts = members.stream() .map(Member::getPosts) .flatMap(Collection::stream) .limit(5L) .collect(Collectors.toList()); // (2) Repository 메서드의 IN + Limit 쿼리 사용 List<Member> members = memberRepository.findAll(); // 편의를 위해 findAll 로 했습니다. 실제로는 조건이 있음!! List<Post> posts = postRepository.findTop5ByMemberIn(members); (1) 번으로 할 때 배치 사이즈 설정이 적용되어 1 + N 문제는 발생하진 않지만 모든 Post 데이터를 다 끌어오게 됩니다. Fetch Join 도 Limit 쿼리가 제대로 적용되지 않아서 OutOfMemory 발생 위험이 있기 때문에 안쓰는 걸로 알고 있는데 (2) 번으로 하는 게 맞을까요? (2) 번으로 했을 시 우려되는 점은 만약 members 의 사이즈가 1000 이 넘어가면 IN 쿼리의 성능이 굉장히 떨어지지 않을까 걱정됩니다. Limit 를 걸어두었으니 괜찮을지 아니면 개발자가 직접 사이즈를 쪼개서 나누어서 IN 쿼리를 호출해야 할지.. 어느 방법이 맞을까요 2. 1번과 비슷한 질문인데 5 개의 Post 를 순회하면서 3 개의 Comment 씩 뽑으려고 할 때도 @OneToMany 컬렉션을 호출하는 것보다 쿼리를 직접 호출하는게 좋을까요? 1 번 질문의 답에 따라서 조금 다를 것 같은데 만약 쿼리를 직접 호출해야 한다면 Post -> Dto 구하는 과정에 넣지 말고 Service 에서 쿼리를 호출한 후 직접 넣어줘야 할까요? public class PostDto { // ... 자잘한 field 생략 public static PostDto of(Post post) { return PostDto.builder() .comment(post.getComments().stream().limit(3L)...) .build(); } } 현재는 위와 같이 Post Entity 만을 넘겨줘서 Dto 로 변환시켜 주고 있는데 만약 쿼리를 직접 호출하는게 좋다면 Service 로 옮겨서 작성하는지 아니면 BatchSize 와 Limit 을 동시에 사용하는 꿀팁이 있는지 궁금합니다 ! 3. 마지막으로 위의 Post, Comment 와 같이 데이터가 무한히 많아질 가능성이 있는 테이블을 다룰 때는 Batch Size 가 아닌 다른 방법을 사용하기도 할까요?