묻고 답해요
138만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
테이블 생성 순서중 id 값 ( Pk)
테이블 ddl create 로 자동 생성할때강사님은 id 값 생성은 무조건 순서 1번째 컬럼으로 가는데저는 그거 상관없이 무조건 알파벳 순이네요 pk포함버전에 따른 문제인건가요?방법은 없을까요아래는 제 버전입니다. <!-- JPA 하이버네이트 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>6.2.7.Final</version> </dependency> <!-- H2 데이터베이스 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.200</version> </dependency> @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @Column(name = "name", length = 30, nullable = false) private String username; @Column private int age; 이렇게 했을때 테이블 create 결과는 이렇구요,근데 정말 신기한건 만약 age 를 int 에서 String 타입으로 바꾸면 뒤로 밀리네요이게 타입별로 먼저 정렬하고 그 뒤에 알파벳인거같기도하고왜 그런걸까요?뭐 솔직히 실무에서는 중요한 내용은 아닌거 같은데강사님 버전 타입이랑 달라서 궁금합니다.그리고 테이블 순서에대한 내용은 hibernate 문서를 보면 되는걸까요?문서를보고 직접 확인해보고싶네용
-
미해결Practical Testing: 실용적인 테스트 가이드
강의 내용 블로그 포스팅 문의 드립니다.
안녕하세요 강사님, 다름이 아니라 강의 내용을 블로그(벨로그) 글로 정리해서 기록해도 될까 여쭤봅니다. 코드, 내용을 사용하고 싶은데 허락해주실까요? ㅠ 출처글을 항상 남기겠습니다.
-
미해결생산성을 향상시키는 스프링부트 기반의 API 템플릿 프로젝트 구현
OAuthAttributes 클래스의 toMemberEntity의 파라미터로 memberType이 들어가야하는 이유가 궁금합니다.
@Getter @Builder public class OAuthAttributes { private String name; private String email; private String profile; private MemberType memberType; public Member toMemberEntity(MemberType memberType, Role role){ return Member.builder() .memberName(name) .email(email) .profile(profile) .memberType(memberType) .role(role) .build(); } }강사님 안녕하세요 강의 잘 듣고 있습니다.다름이 아니라 소셜로그인(4) 소셜 로그인 구조 설계 강의를 듣던 중 toMemberEntity 메소드에 대해 궁금한 점이 있어서 질문 드리게 되었습니다. OauthAttributes 클래스 같은 경우 필드로 memberType을가지고 있는데 메소드 파라미터로 따로 memberType을 받아야 하는 특별한 상황이 있는지 궁금합니다. 감사합니다 :)
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
MappedSuperClass 사용 기준
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]MappedSuperclass도 결국 상속 받는거고이전 예시의 Item도 결국 값, 이름만 상속해서 결국 공통정보를 사용하는거 같은데어느상황에서 MappedSuperclass를 사용하고 어떨 때 상속을 받아 사용하는지 궁금합니다.MappedSuperclass를 사용하면 결국 디비는 다르다는게 결국은 단일테이블 전략으로 상속하는거랑 비슷한거 아닌가요??
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
stock_quantity 동시성 해결하는 방법에 대해
@Test public void 상품_주문() { //given Member member = new Member(); member.setName("회원1"); member.setAddress(new Address("서울", "강가", "123-123")); em.persist(member); Book book = new Book(); book.setName("시골 JPA"); book.setPrice(10000); book.setStockQuantity(10); book.setAuthor("kim"); em.persist(book); //when int orderCount = 2; // 두권 주문 Long orderId = this.orderService.order(member.getId(), book.getId(), orderCount); //then Order getOrder = this.orderRepository.findOne(orderId); Assertions.assertEquals(OrderStatus.ORDER, getOrder.getStatus()); }강의 내용 중 테스트 코드//==생성 메서드==// public static OrderItem createOrderItem(Item item, int orderPrice, int count) { OrderItem orderItem = new OrderItem(); orderItem.setItem(item); orderItem.setOrderPrice(orderPrice); orderItem.setCount(count); item.removeStock(count); // 해당 상품의 재고 수량 차감 return orderItem; }강의 내용 중 OrderItem 엔티티 내 생성 메서드 createOrderItem()강의에서는 위와 같이 OrderItem 엔티티의 생성 메서드라는 것을 통해 주문상품을 만들고 상품의 재고를 감소시키는데요. jpa가 상품 수량(stock_quantity) 감소시킬 때 사용한 UPDATE 쿼리를 보니까 UPDATE item SET stock_quantity = 8; 과 같이 되어있더라고요. 이러면 여러 클라이언트가 동시에 해당 상품 주문할 때 덮어쓰는 문제가 발생하니까 따로 해결 방법을 찾아봤습니다.JPA에 낙관적 락이라는 게 있길래 적용해봤더니 다른 트랜잭션이 중간에 상품 수량을 변경하고 커밋하면 해당 트랜잭션에서 변환된 스프링 예외(ObjectOptimisticLockingFailureException)가 올라오며 덮어쓰는 문제는 막을 수 있었습니다(테이블 데이터 생성 후 ddl-auto: none 모드로 실행, h2 콘솔과 함께 테스트). 예외를 잡고 새 스냅샷으로 다시 호출할 수 있겠지만, db에 stock_quantity 데이터만 정확히 맞추면 되는 게 목적이어서 createOrderItem 로직을 별도의 리포지토리로 대체해보았습니다.@Slf4j @Repository @RequiredArgsConstructor public class OrderItemRepository { private final EntityManager em; /** * 주문 상품 생성 * @param item * @param orderPrice * @param count * @return */ public OrderItem createOrderItem(Item item, int orderPrice, int count) { OrderItem orderItem = new OrderItem(); orderItem.setItem(item); orderItem.setOrderPrice(orderPrice); orderItem.setCount(count); int restStock = item.getStockQuantity() - count; if (restStock < 0) { log.info("need more stock for {}.{}", item.getId(), item.getName()); throw new NotEnoughStockException("need more stock"); } // 해당 상품의 재고 수량 차감 em.createQuery("UPDATE Item i " + "SET stock_quantity = stock_quantity - :count " + "WHERE item_id = :item_id") .setParameter("count", count) .setParameter("item_id", item.getId()) .executeUpdate(); // em.refresh(item); return orderItem; }em.createQuery()로 item.removeStock(count); 부분을 바꿨습니다. SET stock_quantity = stock_quantity - :count /** OrderService 내 order 메서드 */ @Transactional(readOnly = false) public Long order(Long memberId, Long itemId, int count) { //엔티티 조회 Member member = this.memberRepository.findOne(memberId); Item item = this.itemRepository.findOne(itemId); //배송 정보 생성 Delivery delivery = new Delivery(); delivery.setAddress(member.getAddress()); //주문 상품 생성 OrderItem orderItem = this.orderItemRepository.createOrderItem(item, item.getPrice(), count); //주문 생성 Order order = Order.createOrder(member, delivery, orderItem); //주문 저장 this.orderRepository.save(order); return order.getId(); }기존에 호출하던 OrderItem.createOrderItem(item, item.getPrice(), count); 대신에 orderItemRepository.createOrderItem()를 호출하도록 OrderService 코드를 변경강의에서 작성한 상품_주문() 테스트를 단일 실행했을 때도 em.createQuery.executeUpdate()할 때 보니까, flush인가 그것도 호출안했는데 jpa가 그전에 persist한 book이랑 member까지는 실제로 인서트하고 업데이트 쿼리를 실행하는 것을 확인했습니다.[질문]1. 강의에선 OrderItem 엔티티 자체에서 createOrderItem()을 처리해주기 때문에 OrderItem의 기본 생성자도 protected로 지정해서 막았는데, 글에서 적용한 방식으로 해결하려면 public으로 바꿔야 했습니다. 지금처럼 OrderItemRepository를 따로 만들어서 처리하는 게 구조상 문제가 없는 건지 리포지토리의 역할이 맞는지 모르겠고, 문제가 생길 수 있는지 궁금합니다.(예측되는 문제점: 주문/주문 취소/상품 수정 등 stock_quantity와 얽혀있는 로직마다 쿼리를 작성해야 됨) 2. JPA의 기본적인 변경 감지 방식을 유지하면서 낙관적 락 없이도 간단하게 해결할 수 있는 방법이 있는지 알고 싶습니다.
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
일대다 매핑시 질문있습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]@Entity public class MemberProduct { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name = "MEMBER_ID") private Member member;강좌 9분에 작성하시는 위 코드에서 @ManyToOne 코드에서 JoinColum(name = "") 이 부분의 name속성은 MemberProduct의 디비에 칼럼 이름을 지정하는 것이고 디비 자체에서 외래 키(MEMBER_ID)가 어느 엔티티와 연결되었느냐는 바로 밑의코드 private Member member; 이 부분을 보고 Member 클래스도 Entity 이기 때문에 member와 연결시키는 쿼리를 작성해주는건가요??
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
testMember 시 오류 발생 (MySQL 사용)
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.구글 드라이브에 전체 코드 업로드하였습니다. (properties.yml 에서 MySQL password만 지우고 올렸습니다.)https://drive.google.com/drive/folders/1az491IbBQQf5nqE9DsPHk3NHG1oM5Xg2?usp=drive_link 안녕하세요. 저는 h2 대신 MySQL을 사용하며 실습을 진행하고 있습니다.다름이 아니라, 강의와 동일하게 진행하고 있는데, 계속 오류가 떠서 진행이 힘든 상황입니다.org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statementCaused by: org.hibernate.exception.SQLGrammarException: could not execute statementCaused by: java.sql.SQLSyntaxErrorException: Table 'jpa-shop.members' doesn't exist위와 같은 오류가 발생합니다. (중간에 자잘자잘한 코드들은 제외했습니다.)MySQL에서 member 가 예약어라고 해서 @Table(name="Members)를 붙여주었지만, 소용이 없었습니다.코드 첨부하겠습니다.MemberMemberRepository여기서 em 필드에 색깔이 안 들어오는 것도 뭔가 신경쓰입니다.application.yml캡처할 때만 password를 지우고 캡처했습니다. MemberRepositoryTest위에서 save에 빨간 밑줄이 뜨고 있습니다. (Junit5 사용중입니다)도와주시면 감사하겠습니다..!! 혹시 몰라서 전체 오류 로그를 복붙하겠습니다.org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statementat org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:259)at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)at jpabook.jpashop.MemberRepository$$EnhancerBySpringCGLIB$$3abfe072.save(<generated>)at jpabook.jpashop.MemberRepositoryTest.testMember(MemberRepositoryTest.java:31)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.base/java.lang.reflect.Method.invoke(Method.java:566)at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)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:149)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)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.ExecutableInvoker.invoke(ExecutableInvoker.java:104)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)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:1541)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:1541)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:107)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)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)Caused by: org.hibernate.exception.SQLGrammarException: could not execute statementat org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:63)at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:37)at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:58)at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:43)at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3279)at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3914)at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:84)at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:329)at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:286)at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:192)at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:122)at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185)at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55)at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:756)at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:742)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.base/java.lang.reflect.Method.invoke(Method.java:566)at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:315)at com.sun.proxy.$Proxy97.persist(Unknown Source)at jpabook.jpashop.MemberRepository.save(MemberRepository.java:15)at jpabook.jpashop.MemberRepository$$FastClassBySpringCGLIB$$a3e1a60b.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)... 74 moreCaused by: java.sql.SQLSyntaxErrorException: Table 'jpa-shop.members' doesn't existat com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:121)at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1061)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1009)at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1320)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:994)at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)... 106 more
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
testMember() 테스트 실행 시 entityManagerFactory 생성하는데 에러 발생했다고 나옵니다.
h2 설치하고 설정 후 테스트 코드 작성 부분에서 다음과 같은 문제가 발생합니다.애플리케이션을 실행해도 똑같은 에러가 발생합니다.org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not instantiate id generator [entity-name=jpabook.jpashop.Member]build.gradleplugins { id 'java' id 'org.springframework.boot' version '3.1.3' id 'io.spring.dependency-management' version '1.1.3' } group = 'jpabook' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-devtools' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation("org.junit.vintage:junit-vintage-engine") { exclude group: "org.hamcrest", module: "hamcrest-core" } } tasks.named('test') { useJUnitPlatform() } h2 접속 화면application.yml 파일spring: datasource: url: jdbc:h2:tcp://localhost/~/jpashop username: sa password: driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: create properties: hibernate: format_sql: true logging.level: org.hibernate.orm.jdbc.bind: traceMember 클래스package jpabook.jpashop; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import lombok.Getter; import lombok.Setter; @Getter @Setter @Entity public class Member { @Id @GeneratedValue private Long id; private String username; }
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
JPA 최적화 강의 수강 후, 개인 프로젝트 수행하면서 생긴 질문입니다.
안녕하세요. JPA 최적화 강의를 수강하고, 개인적으로 DDD를 책으로 공부해본 후에 개인 토이 프로젝트를 진행하던 중, 뜻밖에 의문이 생겼는데 여쭐 곳이 없어서 이렇게 질문 올리게 되었습니다. 맨 땅에 헤딩으로 이런저런 강의, 책, 다른 분들의 소스를 참고 하려다보니 여러 개념이 뒤섞여서 혼동이 옵니다.. ㅠㅠ 현재 프로젝트에서는 크게 에그리거트를 CUSTOMER, EXTERNAL, SECURITYMEDIA 3개로 나누어 설계했는데요. 강의에서도, 책에서도 DOMAIN 계층에 있는 서비스는 해당 도메인에 대한 순수한 CRUD를 수행하는 것으로 보았습니다. DDD 책에서는 여러 에그리거트가 필요로 하는 기능을 구현할 때는 도메인 서비스로 구현하라고 이야기 했구요.처음에는 책에서 조언하는 대로 도메인 서비스로 구현해보고자 하다가, 좀처럼 구현이 안되어서 다른 분들이 구현한 소스를 참조하다 보니 application(응용)영역을 FACADE라는 상위 계층을 두는 것을 방식을 알게 되었습니다. 소스를 따라가보니 각 애그리거트의 DOMAIN 영역에 있는 서비스를 주입하여, 각 도메인 영역에 있는 서비스를 적절히 호출하기에 책에서 본 도메인 서비스와 같은 역할을 하겠구나 하여,, 해당 프로젝트 구성 방식을 따라 개발해보기로 했습니다.그런데 개발을 하다보니,, 참조하는 소스에서 메소드 단위의 트랜잭션의 적용을 facade 영역이 아닌, 도메인 영역의 서비스 구현체에서 하는 것을 알게 되었습니다. 제가 개발하고자 기능은 여러 애그리거트를 생성, 변경하는 하나의 행위가 하나의 트랜잭션으로 묶여야 하는데 말이죠.이러한 이유 때문에 현재 소스는 FACADE에서는 하나의 도메인 영역의 서비스를 주입하여 하나의 메소드를 호출하도록 되어있고, 도메인 영역에 있는 해당 서비스의 구현체에서 여러 애그리거트의 서비스, 레포지토리를 주입받아 하나의 메소드에서 트랜잭션 단위로 수행하도록 구현되어있습니다..@Service @RequiredArgsConstructor public class SecurityMediaFacade { private final SecurityMediaService securityMediaService; public SecurityMediaInfo.Main registerOtp(SecurityMediaCommand.RegisterSecurityMediaRequest req) { //디지털otp 발급 // 디지털 otp 발행 return securityMediaService.issueSecurityMedia(req, SecurityMediaType.DIGITAL_OTP); } ... }public interface SecurityMediaService { public SecurityMediaInfo.Main issueSecurityMedia(SecurityMediaCommand.RegisterSecurityMediaRequest req, SecurityMediaType type); ... }@Slf4j @Service @RequiredArgsConstructor public class SecurityMediaServiceImpl implements SecurityMediaService { private final CustomerReader customerReader; private final SecurityMediaStore securityMediaStore; private final TokenStore tokenStore; private final ExternalClientService externalClientService; @Override @Transactional public SecurityMediaInfo.Main issueSecurityMedia(SecurityMediaCommand.RegisterSecurityMediaRequest req, SecurityMediaType type) { // 요청고객 찾기 Customer customer = customerReader.findCustomerByRnn(req.getRnn()); SecurityMedia newOtp = null; if(!customer.existActiveSecurityMedia()) { // otp 신규 SecurityMedia initOtp = req.toEntity(SecurityMediaType.DIGITAL_OTP, customer); newOtp = securityMediaStore.store(initOtp); // 토큰 발급 요청 Token newToken = externalClientService.getToken(customer, newOtp); newOtp.addToken(newToken); tokenStore.store(newToken); } return new SecurityMediaInfo.Main(newOtp); }위에는 프로젝트의 구성인데.. 첫 단추부터 잘못 끼운 것도 같아서 시작 단계인 지금에서라도 좀 개선을 해보려고 하는데요.사실 도메인 서비스가 제가 의도로 하는 여러 애그리거트의 서비스 기능을 묶어서 하는 건지 아무리 읽어봐도 혼선이 옵니다. 혹시 DDD 책에서 이벤트라는 개념이 나오는데 도메인 서비스가 아니라, 이 이벤트를 통해 다른 애그리거트의 응용 서비스를 호출하도록 핸들링 하는게 올바른 방법일까요?지금과 같은 구조를 유지해도 된다면.. facade 영역의 메소드를 트랜잭션으로 묶고, 각 도메인 계층의 서비스들에서 선언된 해당 도메인에 대한 crud 메소드를 적절히 호출해가면서 facade 영역에서 비즈니스 로직을 처리해도 될까요? 너무 글이 장황하고 기네요.. ㅠㅠ 혹시 도움을 주신다면 너무나도 감사드리겠습니다.
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
강의 16분
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]강의 16분에 정리하실 때 주 테이블이냐 대상 테이블이냐는 비즈니스 로직에 따라 즉 개발자가 선택하기 나름인거죠?? 기준이 있나요??
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
안녕하세요 5강 진행중에 RestController를 어노테이션할때 오류가 발생합니다
5강 진행중 RestController를 @RestController 할때 오류가 발생합니다.위에 import 를 추가하였으나, 이와 같은 에러가 발생합니다 어떻게 하면 될까요?
-
미해결Practical Testing: 실용적인 테스트 가이드
공통관심사 테스트 가이드
안녕하세요. 우빈님질문이 있습니다! AOP, Interceptor... 와 같은 공통 관심사를 테스트할 때는 보통 어떤 전략을 갖고 테스트 코드를 작성하나요?예를 들어 아래와 같은 요구사항이 있다고 가정합니다.22시 이후에는 모든 요청에 대해서 예외를 발생시킨다. 저라면 모든 Controller에 check22hours()라는 로직을 AOP를 사용하여 적용할 것 같은데요.이때 순수하게 check22hours()라는 단위 테스트만 작성하면 되는 것 일까요? 아니면 AOP 자체가 의도한대로 동작하는지 테스트 코드를 작성하는 것이 바람직한 것 인가요? 감사합니다.
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
@JoinColumn 질문
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]1. 강의 4분 36초 쯤의 @JoinColumn에 name 속성인 LOCKER_ID는 디비의 MEMBER테이블의 LOCKER_ID 칼럼이라는 뜻인가요 아니면 Locker Id를 Column(name = "LOCKER_ID")라고 했을 때 여기있는 LOCKER_ID를 나타내는 것인가요?
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
객체가 스스로의 리스트를 가지고, 양방향 매핑을 해도 될까요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]악! 스프링 웹 개발은 즐거워! 김영한 선생님 질문있습니다.선생님의 두 로드맵을 거의 다 듣고 그를 바탕으로 포폴용 게시판을 만들어보고 있습니다. 와중 댓글과 대댓글 기능을 구현하는 과정에서 Comment라는 객체를 만들고 객체의 타입을 Comment와 reply로 나누었습니다. 특정 게시판에 관한 데이터를 불러올 때 댓글과 대댓글을 편리하게 불러오기 위해 타입이 Comment인 객체가 reply에 해당하는 객체를 리스트로 갖도록 설계했습니다. 이후 테스트를 진행해보았는데 fetch join을 통한 데이터 로드는 문제 없이 진행되었습니다. 다만 이러한 설계 방식이 올바른지에 대한 질문을 스스로 해결할 수 없어서 글 남깁니다. 아래는 코드와 테스트 코드 및 실행 결과이고, 마지막에 질문이 있습니다.package toy.board.domain.post; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.util.StringUtils; import toy.board.domain.BaseDeleteEntity; import toy.board.domain.user.Member; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PROTECTED) @EqualsAndHashCode(callSuper = true) public class Comment extends BaseDeleteEntity { public static final int CONTENT_LENGTH = 1000; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "comment_id", nullable = false) private Long id; @Column(name = "content", nullable = false, length = CONTENT_LENGTH) private String content; @Column(name = "type", nullable = false, updatable = false) private CommentType type; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id", nullable = false, updatable = false) private Post post; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false, updatable = false) private Member member; @OneToMany(mappedBy = "parent") private List<Comment> replies = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_comment_id", updatable = false) private Comment parent; /** * 양방향 관계인 Member와 Post에 대해 자동으로 양방향 매핑을 수행한다. */ public Comment( @NotNull final Post post, @NotNull final Member member, @NotNull final String content, @NotNull final CommentType type ) { this.post = post; this.member = member; this.content = content; this.type = type; } public boolean update(final String content) { if (!StringUtils.hasText(content)) { return false; } this.content = content; return true; } public void leaveReply(Comment reply) { if (areTypesCorrectThisAnd(reply)) { throw new IllegalArgumentException("주어진 댓글과 대댓글의 타입이 올바르지 않습니다."); } if (hasComment(reply)) { throw new IllegalArgumentException("대댓글이 이미 다른 댓글에 포섭되어 있습니다."); } if (isNew(reply)) { throw new IllegalArgumentException("댓글이 이미 해당 대댓글을 포함하고 있습니다."); } this.replies.add(reply); reply.parent = this; } private boolean isNew(Comment reply) { return this.replies.contains(reply); } private static boolean hasComment(Comment reply) { return reply.parent != null; } private boolean areTypesCorrectThisAnd(Comment reply) { return this.type != CommentType.COMMENT || reply.type != CommentType.REPLY; } } @Transactional @DisplayName("comment가 List<comment>를 갖고, fetch join으로 가져올 수 있는가?") @Test public void comment_has_comments_fetch_join() throws Exception { //given Member member = Member.builder( "member", new Login("password"), Profile.builder("nickname").build(), LoginType.LOCAL_LOGIN, UserRole.USER ).build(); em.persist(member); Post post = new Post(member, "title", "content"); em.persist(post); Comment comment = new Comment(post, member, "comment", CommentType.COMMENT); em.persist(comment); for (int i = 0; i < 5; i++) { Comment reply = new Comment(post, member, "reply" + String.valueOf(i), CommentType.REPLY); comment.leaveReply(reply); em.persist(reply); } em.flush(); em.clear(); //when QComment reply = new QComment("reply"); List<Comment> findComments = queryFactory .selectFrom(QComment.comment) .leftJoin(QComment.comment.replies, reply).fetchJoin() .where( QComment.comment.post.id.eq(post.getId()), QComment.comment.type.eq(CommentType.COMMENT) ) .fetch(); System.out.println("============================================="); for (Comment findComment : findComments) { System.out.println("findComment.getId() = " + findComment.getId()); System.out.println("findComment.getContent() = " + findComment.getContent()); } //then Comment findComment = findComments.get(0); for (Comment findReply : findComment.getReplies()) { System.out.println("findReply content = " + findReply.getContent()); } }[테스트 실행 시 create query]create table comment ( is_deleted boolean default false not null, type tinyint not null check (type between 0 and 1), comment_id bigint generated by default as identity, created_date timestamp(6), deleted_date timestamp(6), last_modified_date timestamp(6), member_id bigint not null, parent_comment_id bigint, post_id bigint not null, content varchar(1000) not null, created_by varchar(255), last_modified_by varchar(255), primary key (comment_id) )[테스트 실행 결과] select c1_0.comment_id, c1_0.content, c1_0.created_by, c1_0.created_date, c1_0.deleted_date, c1_0.is_deleted, c1_0.last_modified_by, c1_0.last_modified_date, c1_0.member_id, c1_0.parent_comment_id, c1_0.post_id, r1_0.parent_comment_id, r1_0.comment_id, r1_0.content, r1_0.created_by, r1_0.created_date, r1_0.deleted_date, r1_0.is_deleted, r1_0.last_modified_by, r1_0.last_modified_date, r1_0.member_id, r1_0.post_id, r1_0.type, c1_0.type from comment c1_0 left join comment r1_0 on c1_0.comment_id=r1_0.parent_comment_id where c1_0.post_id=? and c1_0.type=?=====================findComment.getId() = 1findComment.getContent() = commentfindReply content = reply0findReply content = reply1findReply content = reply2findReply content = reply3findReply content = reply4 [질문]제가 궁금한 것을 자세히 말하자면,테이블이 만들어질 때, comment 테이블의 특정 row(대댓글인 컬럼)가 해당 테이블의 다른 row(댓글인 row)의 PK값을 FK로 갖는데, 댓글에 해당하는 row는 객체가 생성되고 DB에 저장될 때 해당 컬럼에 null이 저장됩니다. 이는 Comment 객체의 ID에 @GenerateValue 설정을 주어서 그렇습니다. 위의 상황은 실무에서 사용할만큼 적절한가요? 아니라면 대안이 있을까요?jpa와 관련된 질문을 읽다보니, 다대일 관계에서 left join fetch의 경우 where문의 결과에 따라 데이터 일관성의 오류가 나타날 수 있다는 답변이 있었습니다.(해당 글: https://www.inflearn.com/questions/15876/fetch-join-%EC%8B%9C-%EB%B3%84%EC%B9%AD%EA%B4%80%EB%A0%A8-%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4) 제가 작성한 테스트 코드의 쿼리문은 일대다 관계에서 일에 해당하는 엔티티에 별칭을 주어 where문을 적용한 것이므로 일관성의 문제가 발생하지 않는다고 생각했는데 이것이 옳은 생각인가요? 마지막으로 쿼리의 복잡도와 쿼리 개수는 trade off 관점에서, 특정 게시물에 관한 데이터를 반환해야 하는 api 요청이 들어왔을 때, 게시물과 댓글 엔티티가 단방향 관계일 경우 게시물에 관한 데이터와 댓글 및 대댓글에 관한 데이터를 각각의 저장소를 통해 가져오는 것이 좋을까요 혹은 쿼리가 다소 복잡해지더라도 한 번에 가져오는 것이 좋을까요? 아니면 단방향 관계를 양방향으로 만드는 것이 더 나은 선택일까요? 혼자 공부하니 올바른 방식을 찾는게 참으로 어려운 것 같습니다.답변 기다리겠습니다. 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
테스트 환경 통합 질문
안녕하세요. 테스트 환경 통합 강의를 보다가 궁금한 사항이 있어서 질문드립니다. 강의 내용처럼 통합 테스트를 수행할 때 여러번 Spring 서버를 띄우는 것을 효과적으로 개선하기 위해 TestSupport 추상클래스를 상속받아 Repository/Service 계층 테스트시에 통합된 환경을 구축하는게 더 좋은 것은 이해했는데요.Controller 테스트의 경우, 강의 내용처럼 따로 스프링을 띄우지 않고 @WebMvcTest로만 테스트코드를 작성하는 경우라면 공통 추상 클래스를 구현하는게 비효율적일수도 있을 것 같아서 궁금증이 생겼습니다.@WebMvcTest(Controllers="Controller.class") 형식으로 컨트롤러 클래스들을 명시해 줘야 하는데, 클래스가 수십개로 많아질수록 매번 추가해야 하고, Controllers에 많은 클래스를 넣어야 하고(패키지 단위로 지정한다든가 등의 방식은 없는것 같더라구요), 각 클래스에서 사용하는 MockBean이 많아질수록 필드가 많아져서 본문이 길어 보기 힘들수도 있을 것 같아서요. @SpringBootTest처럼 서버를 띄우는 비용이 발생하지 않으므로, 각각의 컨트롤러 테스트마다 명시적으로 @WebMvcTest를 사용하고, 해당 클래스에서 사용할 Mockbean 또한 명시적으로 지정하는 방식도 괜찮을까요? 실무적인 관점으로 볼 때, 제 생각대로 Controller 클래스의 테스트는 통합하지 않고 각각 구현하는 건 어떨지 궁금합니다.
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
깃허브에 올려봐도 괜찮을까요?
안녕하세요 선생님!깃허브 경험을 쌓고 싶어서 배운 내용을 출처를 남기고 깃허브에 올리면서 해봐도 괜찮을까요??
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
@Valid , bindingResult
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]memberController의 create 부분의 로직을 저는 아래처럼 이해했는데 맞게 이해한게 맞을까요 ?@Valid 어노테이션이 있어서 MemberForm의 @NotEmpty 조건을 validation하고 이 단계에서 NotEmpty 조건을 충족시키지 못했을 때 에러가 나는데 그 에러에 대한 내용을 bindingResult로 인해서 에러 정보를 result란 값에 저장한 뒤 result에 에러 정보가 있을 시에 members/createMemberForm으로 페이지 이동 후 createMemberForm html 파일에서 result에 name 에 대한 에러 값이 있을 때 에러메시지를 바인딩하는데 그게 NotEmpty 어노테이션 적용 시 적어뒀던 메시지를 띄우는 걸까요 .. ?전체적인 로직을 제가 맞게 이해한건지 잘 모르겠어서요 ..
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
ORDERS와 ORDER_ITEM 테이블 두개가 존재하는 이유
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요 테이블 구조에 대해 의문이 들어서 글 남기게 되었습니다MEMBER 테이블과 ITEM 이 N:M 관계이고 그렇기 때문에 주문(MEMBER_ITEM) 이라는 관계 테이블을 이용해서 MEMBER와 ITEM 간의 N:M 관계를 표현해주면 안되나요?ORDERS 라는 테이블이 존재하는 이유를 모르겠습니다..
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
양방향 통신에서의 주인
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]양방향 통신에서 member를 주인으로 하셨는데 예를들어 memberA의 팀이 TeamA였는데 TeamB로 변경할 때 Team entity의 memberList(members)는 읽기만 해야한다고 하셨는데 그러면 memberA.setTeam("TeamB")를 해주고 쿼리를 날리면 디비에서 자동으로 참조해서 바꿔주고 객체의 TeamA의 memberList에 있는 memberA를 삭제하고 TeamB의 memberList에 memberA를 추가해주나요??
-
미해결생산성을 향상시키는 스프링부트 기반의 API 템플릿 프로젝트 구현
feignClient의 consumes와 @RequestHeader는 같은 역할인가요?
카카오 토큰 발급 구현(2) 5:27 부분feignClient의 PostMapping에consumes = "application/json"이 있는데이 부분이 외부 api 요청시 헤더 지정 역할인 것으로 알고 있습니다. 그런데 카카오에서는 x-form-urlencoded로 요청하라고 해서 @RequestHEader("Content-Type)을 따로 지정해주는 것으로 이해했느데요 consumes="x-form-urlencoded"로 하고 파라미터에서 contentType을 제거해도 되나요?