안녕하세요, 김영한 강사님 강의 잘 듣고 있는 전진호 학생입니다. 첫번째 캡쳐사진은 제가 강의를 들으면서 적었던 코드에서 상품 주문을 클릭하고, 아무 것도 적지 않았을 때 나오는 화면입니다. 두번째 캡쳐사진은 자료 소스 코드에서 동일한 작동을 했을 때 나오는 소스코드입니다. 똑같은 status=400, Bad Request인데 첫번째만 왜 에러 로그가 길게 나오는 걸까요? 답변해주시면 감사하겠습니다. *) zip 파일을 export 했는데 어떻게 올려야 할지 잘 모르겠어서 추가적으로 필요하시다면 보내드리겠습니다.
강의를 다 보고 따로 찾아보니 @Transactional은 controller가 아닌 service에서 사용해야되고 controller에서 사용하게 되면 트랜잭션이 적용이 안된다고 하길래 직접 해봤습니다. controller의 메소드에 @Transactional을 붙여서 로직을 실행시켜보니 변경감지가 일어나서 update 쿼리가 날라가는 것을 확인하였는데 이러면 트랜잭션을 통해서 변경감지가 일어난 것 아닌가요?
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 안녕하세요! 선생님 강의를 수강하고 현재 플젝을 진행중에 값타임 컬렉션 처리와 관련해서 고민하면서 강의를 다시 보았는데도 해결방안이 잘 떠오르지 않아서 질문드렸습니다. 현재 Post 폼에서 장기 Post와 단기 Post를 나누어서 각각 작성된 폼을 DB에 저장하는데 장기 Post에서는 LocalDateTime 필드들이 리스트형식으로 있는 필드들과 다른 필드들이 함께 객체로 프론트에서 넘어오고, 단기 Post에서는 String 타입의 리스트들과 다른 필드들이 객체 형식으로 넘어옵니다. 또한 Post는 수정을 통해 날짜도 변경할 수 있습니다. 따라서 @ElementCollcetion을 사용할 경우, 만약 LocatDateTime과 관련된 날짜들이 여러 개 넘어 올 경우, 날짜 각각이 insert되고, 만약 수정한다면 선생님의 말씀처럼 값타입으로 불필요한 값들까지 모두 삭제되고 다시 갱신되므로, 일대다관계를 생각해보았습니다. 일대다관계일 경우 각각의 LocalDateTime 갯수만큼 insert문으로 DB에 저장될 것이고, Entity이므로 수정의 경우 필요한 부분만 수정하면 된다고 생각하였습니다. 하지만, 예를 들어 하나의 Post당 날짜 필드들이 20개라고 친다면 insert문이 20개 들어갈 것이고, Post의 갯수가 많아진다면 그에 따른 성능이나 비용측면에서 단점이 되지 않을까 생각하였습니다. 그래서 다음으로는 @Converter를 사용하여 들어오는 리스트들의 값을 String 타입으로 변환하고 하나의 Column안에 넣어서 관리하는 방법을 생각하였습니다. 해당 방법은 insert문은 하나만 들어가기 때문에 성능에서 큰 문제를 일으키지 않을 것 같았는데, 만약 수정한다면 해당 컬럼을 삭제하고 다시 추가해야하고, 로직이 복잡해질 것 같습니다. 선생님께서는 현업에서 selectedbox같은 것을 처리할 때 일대다 관계를 이용해서 처리한다고 하셨는데 저와 같은 경우에도 일대다 관계 방법이 좋은 방법일지 아님 더 좋은 방법이 있을지 질문드리게 되었습니다.
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended 2023-09-09T20:53:34.199+09:00 INFO 2419 --- [ main] p6spy : #1694260414199 | took 0ms | rollback | connection 2| url jdbc:h2:tcp:// localhost/~/datajpa ; 2023-09-09T20:53:34.201+09:00 WARN 2419 --- [ main] o.s.test.context.TestContextManager : Caught exception while invoking 'afterTestMethod' callback on TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] for test method [public void study.datajpa.entity.MemberTest.jpaEventBaseEntity() throws java.lang.Exception] and test instance [study.datajpa.entity.MemberTest@7d9587b3] org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only at org.springframework.transaction.support .AbstractPlatformTransactionManager.processCommit( AbstractPlatformTransactionManager.java:752 ) ~[spring-tx-6.0.11.jar:6.0.11] at org.springframework.transaction.support .AbstractPlatformTransactionManager.commit( AbstractPlatformTransactionManager.java:711 ) ~[spring-tx-6.0.11.jar:6.0.11] at org.springframework.test.context.transaction.TransactionContext.endTransaction( TransactionContext.java:135 ) ~[spring-test-6.0.11.jar:6.0.11] at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod( TransactionalTestExecutionListener.java:259 ) ~[spring-test-6.0.11.jar:6.0.11] at org.springframework.test.context.TestContextManager.afterTestMethod( TestContextManager.java:440 ) ~[spring-test-6.0.11.jar:6.0.11] at org.springframework.test.context.junit.jupiter.SpringExtension.afterEach( SpringExtension.java:206 ) ~[spring-test-6.0.11.jar:6.0.11] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAfterEachCallbacks$12( TestMethodTestDescriptor.java:260 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$13( TestMethodTestDescriptor.java:276 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$14( TestMethodTestDescriptor.java:276 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at java.base/java.util.ArrayList.forEach( ArrayList.java:1511 ) ~[na:na] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAllAfterMethodsOrCallbacks( TestMethodTestDescriptor.java:275 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAfterEachCallbacks( TestMethodTestDescriptor.java:259 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute( TestMethodTestDescriptor.java:144 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute( TestMethodTestDescriptor.java:68 ) ~[junit-jupiter-engine-5.9.3.jar:5.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$6( NodeTestTask.java:151 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8( NodeTestTask.java:141 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.Node.around( Node.java:137 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$9( NodeTestTask.java:139 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively( NodeTestTask.java:138 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.execute( NodeTestTask.java:95 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at java.base/java.util.ArrayList.forEach( ArrayList.java:1511 ) ~[na:na] at org.junit.platform.engine.support .hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll( SameThreadHierarchicalTestExecutorService.java:41 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$6( NodeTestTask.java:155 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8( NodeTestTask.java:141 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.Node.around( Node.java:137 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$9( NodeTestTask.java:139 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively( NodeTestTask.java:138 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.execute( NodeTestTask.java:95 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at java.base/java.util.ArrayList.forEach( ArrayList.java:1511 ) ~[na:na] at org.junit.platform.engine.support .hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll( SameThreadHierarchicalTestExecutorService.java:41 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$6( NodeTestTask.java:155 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8( NodeTestTask.java:141 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.Node.around( Node.java:137 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$9( NodeTestTask.java:139 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively( NodeTestTask.java:138 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.NodeTestTask.execute( NodeTestTask.java:95 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.SameThreadHierarchicalTestExecutorService.submit( SameThreadHierarchicalTestExecutorService.java:35 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.HierarchicalTestExecutor.execute( HierarchicalTestExecutor.java:57 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.engine.support .hierarchical.HierarchicalTestEngine.execute( HierarchicalTestEngine.java:54 ) ~[junit-platform-engine-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:147 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:127 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:90 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0( EngineExecutionOrchestrator.java:55 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams( EngineExecutionOrchestrator.java:102 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:54 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.DefaultLauncher.execute( DefaultLauncher.java:114 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.DefaultLauncher.execute( DefaultLauncher.java:86 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute( DefaultLauncherSession.java:86 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute( SessionPerRequestLauncher.java:53 ) ~[junit-platform-launcher-1.9.3.jar:1.9.3] at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs( JUnit5IdeaTestRunner.java:57 ) ~[junit5-rt.jar:na] at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute( IdeaTestRunner.java:38 ) ~[junit-rt.jar:na] at com.intellij.rt.execution.junit.TestsRepeater.repeat( TestsRepeater.java:11 ) ~[idea_rt.jar:na] at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs( IdeaTestRunner.java:35 ) ~[junit-rt.jar:na] at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart( JUnitStarter.java:232 ) ~[junit-rt.jar:na] at com.intellij.rt.junit.JUnitStarter.main( JUnitStarter.java:55 ) ~[junit-rt.jar:na] java.lang.ClassCastException: Cannot cast java.lang.String to java.time.LocalDateTime at java.base/java.lang.Class.cast( Class.java:3889 ) at study.datajpa.entity.Member_Accessor_i3e07h.setProperty(Unknown Source) at org.springframework.data .mapping.model.InstantiationAwarePropertyAccessor.setProperty( InstantiationAwarePropertyAccessor.java:81 ) at org.springframework.data .mapping.model.SimplePersistentPropertyPathAccessor.setProperty( SimplePersistentPropertyPathAccessor.java:108 ) at org.springframework.data .mapping.model.SimplePersistentPropertyPathAccessor.setProperty( SimplePersistentPropertyPathAccessor.java:127 ) at org.springframework.data .auditing.MappingAuditableBeanWrapperFactory$MappingMetadataAuditableBeanWrapper.lambda$setProperty$0( MappingAuditableBeanWrapperFactory.java:231 ) at java.base/java.lang.Iterable.forEach( Iterable.java:75 ) at org.springframework.data .auditing.MappingAuditableBeanWrapperFactory$MappingMetadataAuditableBeanWrapper.setProperty( MappingAuditableBeanWrapperFactory.java:231 ) at org.springframework.data .auditing.MappingAuditableBeanWrapperFactory$MappingMetadataAuditableBeanWrapper.setLastModifiedBy( MappingAuditableBeanWrapperFactory.java:206 ) at org.springframework.data .auditing.AuditingHandlerSupport.touchAuditor( AuditingHandlerSupport.java:173 ) at org.springframework.data .auditing.AuditingHandlerSupport.lambda$touch$0( AuditingHandlerSupport.java:136 ) at java.base/java.util.Optional.map( Optional.java:260 ) at org.springframework.data .auditing.AuditingHandlerSupport.touch( AuditingHandlerSupport.java:134 ) at org.springframework.data .auditing.AuditingHandlerSupport.markCreated( AuditingHandlerSupport.java:114 ) at org.springframework.data .auditing.AuditingHandler.markCreated( AuditingHandler.java:86 ) at org.springframework.data.jpa.domain.support .AuditingEntityListener.touchForCreate( AuditingEntityListener.java:92 ) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke( NativeMethodAccessorImpl.java:77 ) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke( DelegatingMethodAccessorImpl.java:43 ) at java.base/java.lang.reflect.Method.invoke( Method.java:568 ) at org.hibernate.jpa.event.internal.ListenerCallback.performCallback( ListenerCallback.java:55 ) at org.hibernate.jpa.event.internal.CallbackRegistryImpl.callback( CallbackRegistryImpl.java:113 ) at org.hibernate.jpa.event.internal.CallbackRegistryImpl.preCreate( CallbackRegistryImpl.java:62 ) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId( AbstractSaveEventListener.java:113 ) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient( DefaultPersistEventListener.java:175 ) at org.hibernate.event.internal.DefaultPersistEventListener.persist( DefaultPersistEventListener.java:93 ) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist( DefaultPersistEventListener.java:77 ) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist( DefaultPersistEventListener.java:54 ) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener( EventListenerGroupImpl.java:127 ) at org.hibernate.internal.SessionImpl.firePersist( SessionImpl.java:755 ) at org.hibernate.internal.SessionImpl.persist( SessionImpl.java:739 ) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke( NativeMethodAccessorImpl.java:77 ) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke( DelegatingMethodAccessorImpl.java:43 ) at java.base/java.lang.reflect.Method.invoke( Method.java:568 ) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke( SharedEntityManagerCreator.java:311 ) at jdk.proxy2/jdk.proxy2.$Proxy132.persist(Unknown Source) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save ( SimpleJpaRepository.java:617 ) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke( NativeMethodAccessorImpl.java:77 ) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke( DelegatingMethodAccessorImpl.java:43 ) at java.base/java.lang.reflect.Method.invoke( Method.java:568 ) at org.springframework.data.repository.core.support .RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0( RepositoryMethodInvoker.java:288 ) at org.springframework.data.repository.core.support .RepositoryMethodInvoker.doInvoke( RepositoryMethodInvoker.java:136 ) at org.springframework.data.repository.core.support .RepositoryMethodInvoker.invoke( RepositoryMethodInvoker.java:120 ) at org.springframework.data.repository.core.support .RepositoryComposition$RepositoryFragments.invoke( RepositoryComposition.java:516 ) at org.springframework.data.repository.core.support .RepositoryComposition.invoke( RepositoryComposition.java:285 ) at org.springframework.data.repository.core.support .RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke( RepositoryFactorySupport.java:628 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.data.repository.core.support .QueryExecutorMethodInterceptor.doInvoke( QueryExecutorMethodInterceptor.java:168 ) at org.springframework.data.repository.core.support .QueryExecutorMethodInterceptor.invoke( QueryExecutorMethodInterceptor.java:143 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.data .projection.DefaultMethodInvokingMethodInterceptor.invoke( DefaultMethodInvokingMethodInterceptor.java:72 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation( TransactionInterceptor.java:123 ) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction( TransactionAspectSupport.java:391 ) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke( TransactionInterceptor.java:119 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.dao.support .PersistenceExceptionTranslationInterceptor.invoke( PersistenceExceptionTranslationInterceptor.java:137 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.data.jpa.repository.support .CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke( CrudMethodMetadataPostProcessor.java:164 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke( ExposeInvocationInterceptor.java:97 ) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed( ReflectiveMethodInvocation.java:184 ) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke( JdkDynamicAopProxy.java:244 ) at jdk.proxy2/jdk.proxy2.$Proxy145.save(Unknown Source) at study.datajpa.entity.MemberTest.jpaEventBaseEntity( MemberTest.java:30 ) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke( NativeMethodAccessorImpl.java:77 ) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke( DelegatingMethodAccessorImpl.java:43 ) at java.base/java.lang.reflect.Method.invoke( Method.java:568 ) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod( ReflectionUtils.java:727 ) at org.junit.jupiter.engine.execution.MethodInvocation.proceed( MethodInvocation.java:60 ) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed( InvocationInterceptorChain.java:131 ) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept( TimeoutExtension.java:156 ) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod( TimeoutExtension.java:147 ) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod( TimeoutExtension.java:86 ) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0( InterceptingExecutableInvoker.java:103 ) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0( InterceptingExecutableInvoker.java:93 ) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed( InvocationInterceptorChain.java:106 ) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed( InvocationInterceptorChain.java:64 ) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke( InvocationInterceptorChain.java:45 ) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke( InvocationInterceptorChain.java:37 ) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke( InterceptingExecutableInvoker.java:92 ) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke( InterceptingExecutableInvoker.java:86 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7( TestMethodTestDescriptor.java:217 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod( TestMethodTestDescriptor.java:213 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute( TestMethodTestDescriptor.java:138 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute( TestMethodTestDescriptor.java:68 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$6( NodeTestTask.java:151 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8( NodeTestTask.java:141 ) at org.junit.platform.engine.support .hierarchical.Node.around( Node.java:137 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$9( NodeTestTask.java:139 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively( NodeTestTask.java:138 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.execute( NodeTestTask.java:95 ) at java.base/java.util.ArrayList.forEach( ArrayList.java:1511 ) at org.junit.platform.engine.support .hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll( SameThreadHierarchicalTestExecutorService.java:41 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$6( NodeTestTask.java:155 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8( NodeTestTask.java:141 ) at org.junit.platform.engine.support .hierarchical.Node.around( Node.java:137 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$9( NodeTestTask.java:139 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively( NodeTestTask.java:138 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.execute( NodeTestTask.java:95 ) at java.base/java.util.ArrayList.forEach( ArrayList.java:1511 ) at org.junit.platform.engine.support .hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll( SameThreadHierarchicalTestExecutorService.java:41 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$6( NodeTestTask.java:155 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8( NodeTestTask.java:141 ) at org.junit.platform.engine.support .hierarchical.Node.around( Node.java:137 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$9( NodeTestTask.java:139 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively( NodeTestTask.java:138 ) at org.junit.platform.engine.support .hierarchical.NodeTestTask.execute( NodeTestTask.java:95 ) at org.junit.platform.engine.support .hierarchical.SameThreadHierarchicalTestExecutorService.submit( SameThreadHierarchicalTestExecutorService.java:35 ) at org.junit.platform.engine.support .hierarchical.HierarchicalTestExecutor.execute( HierarchicalTestExecutor.java:57 ) at org.junit.platform.engine.support .hierarchical.HierarchicalTestEngine.execute( HierarchicalTestEngine.java:54 ) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:147 ) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:127 ) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:90 ) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0( EngineExecutionOrchestrator.java:55 ) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams( EngineExecutionOrchestrator.java:102 ) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute( EngineExecutionOrchestrator.java:54 ) at org.junit.platform.launcher.core.DefaultLauncher.execute( DefaultLauncher.java:114 ) at org.junit.platform.launcher.core.DefaultLauncher.execute( DefaultLauncher.java:86 ) at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute( DefaultLauncherSession.java:86 ) at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute( SessionPerRequestLauncher.java:53 ) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs( JUnit5IdeaTestRunner.java:57 ) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute( IdeaTestRunner.java:38 ) at com.intellij.rt.execution.junit.TestsRepeater.repeat( TestsRepeater.java:11 ) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs( IdeaTestRunner.java:35 ) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart( JUnitStarter.java:232 ) at com.intellij.rt.junit.JUnitStarter.main( JUnitStarter.java:55 ) Suppressed: org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only at org.springframework.transaction.support .AbstractPlatformTransactionManager.processCommit( AbstractPlatformTransactionManager.java:752 ) at org.springframework.transaction.support .AbstractPlatformTransactionManager.commit( AbstractPlatformTransactionManager.java:711 ) at org.springframework.test.context.transaction.TransactionContext.endTransaction( TransactionContext.java:135 ) at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod( TransactionalTestExecutionListener.java:259 ) at org.springframework.test.context.TestContextManager.afterTestMethod( TestContextManager.java:440 ) at org.springframework.test.context.junit.jupiter.SpringExtension.afterEach( SpringExtension.java:206 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAfterEachCallbacks$12( TestMethodTestDescriptor.java:260 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$13( TestMethodTestDescriptor.java:276 ) at org.junit.platform.engine.support .hierarchical.ThrowableCollector.execute( ThrowableCollector.java:73 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$14( TestMethodTestDescriptor.java:276 ) at java.base/java.util.ArrayList.forEach( ArrayList.java:1511 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAllAfterMethodsOrCallbacks( TestMethodTestDescriptor.java:275 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAfterEachCallbacks( TestMethodTestDescriptor.java:259 ) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute( TestMethodTestDescriptor.java:144 ) ... 48 more 해결이 안되네요 ㅠㅠㅠ
MemberRepository를 개발할 때, @PersistenceContext를 사용할 때와 @RequiredArgsConstructor를 사용할 때 테스트 결과가 달라져서 문의남깁니다. @PersistenceContext private EntityManager em; 은 가능하지만, @RequiredArgsConstructor를 사용할 때는 final이 들어가야만 하던데 왜 그런지 알 수 있을까요?
안녕하세요, 김영한님 야생형 스타일로 강의 잘 듣다가 질문드립니다. 질문을 적다가 order가 형성되는 순간에 해당 order와 입력된 orderItem과 연결이 되는 거 같다는 생각이 들었습니다. 제가 생각한 게 맞는지 질문드립니다. 정리하자면, OrderService에서 order 메소드를 실행할 때 orderItem과 생성된 order_id는 order가 생성되는 순간 해당 order_id와 orderItem와 연결되는 건가요? 답변해주시면 감사하겠습니다. 감사합니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] 본 강의에서 선생님 화면처럼 sql 이 로그에 나오지 않습니다. yml 파일은 똑같이 작성했는데도 나오지 않는 이유를 모르겠습니다...
안녕하세요. 강의에 매우 깊은 감명을 받고 토이 프로젝트에 말씀해주신 부분들 적용해보고 있습니다. 강의에서 가이드 주신대로 유효성 검증에 대한 테스트도 mock 라이브러리 없이 순수 자바 코드로 해보고 있습니다. 기존 코드에서는 입력값 유효성 검증을 위해 @Valid 어노테이션을 사용했는데요. 스프링부트를 실행시키지 않으니 @Valid 어노테이션이 정상작동을 안해서 테스트를 작성하는데 어려움을 겪고 있습니다....ㅎㅎ 그래서 유효성 검증을 하는 코드를 직접 작성해야 하나 고민하다가 질문을 남기게 되었습니다. 강사님께서는 유효성 검증에 대한 테스트 코드를 작성하는데 어떤 방법을 사용하시는지 궁금합니다. (@Valid는 자바 표준 스펙으로 알고 있어서 스프링을 실행시키지 않아도 방법을 잘 찾아보면 가능할 것 같다 라고도 생각하고 있습니다.)
안녕하세요. 우빈님! 먼저 우빈님의 테스트 강의가 너무나 재미있어, 테스트에 대한 막연한 지식을 구체화하고 테스트 작성 열망을 크게 키울 수 있어 서 감사하다는 말씀을 드리고 싶습니다! 다름이 아니라, Controller 테스트를 위해 @WebMvcTest 를 통해 Service와 Repository를 Mocking하여 단위 테스트의 형식으로 작성 한다는 것을 배웠습니다. 이렇게 배운 것을 사이드 프로젝트에 적용해보며, 의문점이 생겼는데 능력 부족으로 인해 의문이 해결되지 않아 질문을 드리려 합니다. 상황 강의에서 작성한 컨트롤러 테스트 중 일부인 OrderControllerTest의 테스트 메서드는 다음과 같습니다. @DisplayName("신규 주문을 등록한다.") @Test void createOrder() throws Exception { // given OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); // when & then mockMvc.perform( post("/api/v1/orders/new") .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.status").value("OK")) .andExpect(jsonPath("$.message").value("OK")); } 또한, 해당 메서드의 실행 로그를 보면 MockHttpServletResponse 이 다음과 같다는 것을 볼 수 있었습니다. MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"status":"OK","message":"OK","data":null} Forwarded URL = null Redirected URL = null Cookies = [] 이후, 저의 사이드 프로젝트의 컨트롤러 테스트를 위와 동일한 방식으로 작성하였지만, 아래와 같이 MockHttpServletResponse의 Body가 빈 채로 응답 이 되어 테스트가 실패하게 되는 문제가 발생했습니다. MockHttpServletResponse: Status = 200 Error message = null Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = [] 이때, given()을 통해 Service에 대한 행위를 Stubbing해주니 정상적으로 테스트가 성공 하는 것을 확인했습니다. 문제 파악을 위해 조사한 결과 추측되는 차이점은 다음과 같습니다. 강사님의 프로젝트에서 Controller의 응답 타입은 커스텀 응답 객체인 ApiResponse 를 사용합니다. 저의 사이드 프로젝트에서 Controller의 응답 타입은 HttpEntity를 상속하는 ResponseEntity 를 사용합니다. 질문 Q1. 강사님의 코드를 보면, 아래와 같이 createOrder 메서드에 대한 Stubbing 없이도 정상적으로MockHttpServletResponse의 Body가 응답되어 테스트가 성공합니다. // given ... given(orderService.createOrder(any())) .willReturn(OrderResponse.builder() ... // 생략 .build() ); // 없어도 테스트는 성공한다. 저는 given() 절에 @MockBean을 통해 Mock 객체로 설정한 OrderService가 어떤 행위를 해야할지 Stubbing 해주어야 하는 것으로 이해하고 있었는데, 어떻게 Stubbing 없이 Body가 정상적으로 채워져 테스트가 성공한 것인지 궁금 합니다. Q2. Q1과 연관하여 강사님의 코드에서는 ApiResponse 라는 커스텀한 응답 객체를 컨트롤러 메서드의 응답으로 사용하는데, 제 사이드 프로젝트에서의 응답 타입은 ResponseEntity 를 사용하고 있습니다. 이 차이 때문에 발생하는 문제인지 궁금합니다. Q3. 이번 의문점을 통해 컨트롤러 테스트에서 메서드의 행위에 대한 기댓값을 Stubbing하여 검증하는 것이 일종의 답정너(?)와 같은 테스트를 작성하는 것은 아닐까? 라는 생각과 함께, 컨트롤러 테스트 방식에 많은 고민을 해야 하겠다는 다짐을 하게 되었습니다. 이에 대한 우빈님의 생각은 어떠하신지 궁금합니다. 질문이 수준이 다소 떨어지지만, 이 의문점을 해결하고 싶은 마음에 장황하게 나열할 수 밖에 없었음을 양해 부탁드립니다. 답변 기다리겠습니다. 감사합니다!
강의를 보다가 궁금한 점이 생겨 이렇게 질문을 남깁니다 ! 프론트에서 page, size, sort 정보를 파라미터로 넘길텐데 /** * 페이징과 정렬 */ @GetMapping("/members") public Page<Member> list(Pageable pageable) { Page<Member> page = memberRepository.findAll(pageable); return page; } 여기서 그러면 알아서 pageable 객체로 바인딩 되는 것인가요 ? 따로 설정할 필요가 없이 page, size 값을 알아서 바인딩 시켜주는 것인지 궁금합니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx ) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG ) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예 [질문 내용] 여기에 질문 내용을 남겨주세요. Team teamA = new Team(); teamA.setName("팀A"); em.persist(teamA); Team teamB = new Team(); teamB.setName("팀B"); em.persist(teamB); Member member1 = new Member(); member1.setUsername("회원1"); member1.setTeam(teamA); em.persist(member1); Member member2 = new Member(); member2.setUsername("회원2"); member2.setTeam(teamA); em.persist(member2); Member member3 = new Member(); member3.setUsername("회원3"); member3.setTeam(teamB); em.persist(member3); int resultCount = em.createQuery("update Member m set m.age=20") .executeUpdate(); em.clear(); Member findMember = em.find(Member.class, member1.getId()); System.out.println("findMember = " + findMember.getAge()); System.out.println("resultCount = " + resultCount); tx.commit(); System.out.println("resultCount = " + resultCount); } catch (Exception e) { 해당 코드를 보면 em.clear(); 이걸하면 영속성 컨텍스트를 초기화해서 db 에서 값을 가져온다는데요 tx.commit(); 하기 전에는 breakpoint 를 찍어보면 db 에 반영이 안되어있는데 어디 db 에서 가져온다는 말인지 잘 이해가 안됩니다.. 즉 한마디로 1.em.clear(); 을 하면 영속성컨텍스트 전체 초기화 2.findMember.getAge() 이 동작을 할때 영속성컨텍스트에 값이 없으므로 Db 에서 가져옴 3. 하지만 commit 하기전 그 시점엔 db에는 아무값이 없다 (flush 의 경우에도 마찬가지 insert 가 적혀있어도 커밋 직전엔 실제 db에는 아무 값이 없다) 도대체 어떤 db에만 반영되었고 (이건 임시 db인건가) 가져온다는 말인지.. 왜냐하면 그때 실제 db에는 그 당시에 아무값도 없기때문 (commit 이 일어나지 않았기 때문에) (하지만 System.out.println("findMember = " + findMember.getAge());); 이 결과로보면 findMember.getAge()의 20이란 숫자를 select 해서 가져온건 맞음 도대체 어디서 select 를 했단 말인가..) -콘솔 결과값 이 순간 ! db 를 조회하면 (tx.commit 을 안했을 가정.) ps. 제가말한 Db 는 h2 콘솔에서 조회했을때입니다. 콘솔에서 조회했을때는 안보이는데 분명 어딘가의 db 는 바뀐게 맞는거같아요 db에있는 20이라는 숫자를 어디서 가져오는지가 궁금합니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니요) 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예 EntityA 1:N EntityB EntityB 1:N EntityC 이런식으로 있을 때 , EntityA 가 EntityB를 @oneToMany로 가지고있고, EntityB 가 EntityC를 @oneToMany로 가지고 있습니다. 배치사이즈 옵션을 넣고 select a from EntityA a 했을때 , EntityA의 갯수만큼 EntityB에 in절로 EntityA의 키값을 넣어서 @oneToMany를 한번에 끌고왔다고 했을 때 , EntityB안에있는 EntityC를 또 EntityB의 갯수만큼 EntityC에 in절로 EntityB의 키값을 넣어서 한번에 끌고 올 수 있는 방법이 있는지 궁금했습니다. 다중 페치조인 안되는걸 알지만 예를 들어서 이런걸 원한다고 생각하시면 될 것 같습니다 select a from EntityA a join fetch EnitityB(List) b join fetch b.EntityC(List) 이런식으로 컬럼 갯수가 일대다 일대다로 기하급수적으로 증가하는 경우인데 배치사이즈를 사용하여 select a from EntityA a 를 했을 땐 a.EntityB 에 접근할때 당연히 in 절로 가져와주지만 EntityB의 일대다인 EntityC 를 조회 할 땐 N+1이 다시 터집니다 엮여있는 모든 연관관계들을 IN절로 태우고 싶은데 방법이 없을까요?
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] 사용자 정의함수를 db에 등록시켜놓은 다음에 jpql로 사용을 해보니 따로 방언을 등록하지 않아도 사용이 가능합니다. List<String> str = em.createQuery("select create_prefix(m.username) from Member m", String.class).getResultList(); create_prefix는 직접 등록한 함수입니다. 수업에서는 방언으로 사용자 정의 함수를 등록해야지 적용이 된다고 하는데 이 경우 왜 작동되는지 알고싶습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 실무에서 트랜잭션 매니저를 두개 이상 사용하는 경우가 많나요? 기본적으로 스프링부트가 빈에 트랜잭션 매니저를 등록해주는데 그거 말고 다른 트랜잭션 매니저를 사용해야하는 경우들은 어떤 경우들이 있을까요 ? 검색 해보니 데이터베이스를 나눌 때 두개이상을 등록하는데 맞을까요? 감사합니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] 안녕하세요! [강의] 주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화 관련해서 궁금한 점이 있습니다. findAllByDto_flat()에서 distinct를 통해 중복을 제거하는게 아니라 강의와 같이 findAllByDto_flat() 호출 후 groupingBy를 통해 중복을 제거했을 때 서로 어떤 차이가 있나요?