30%
69,300원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 해결됨스프링 DB 2편 - 데이터 접근 활용 기술
임베디드 모드 테스트 시 sql 스크립트
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예[질문 내용]여기에 질문 내용을 남겨주세요.영한 님의 강의를 수강한 뒤 간단한 게시판 프로젝트를 작업해보고 있습니다.말씀해주신 임베디드 모드를 테스트에 적용하기 위해 다음과 같이 설정해봤습니다.test의 resources/application.properties에는 단순히 로그와 관련된 것만 입력해두었습니다.logging.level.org.springframework.jdbc=debug # Can check SQL which Hibernate run and create logging.level.org.hibernate.SQL=DEBUG # Can check parameters which binding in SQL logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE멤버 엔티티입니다.package com.devholic22.board.entity; import jakarta.persistence.*; import lombok.Data; @Data @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "user_name", length = 10) private String name; @Column(length = 10) private String password; public Member() { } public Member(String name, String password) { this.name = name; this.password = password; } } 멤버 리포지토리입니다.package com.devholic22.board.repository; import com.devholic22.board.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository<Member, Long> { } 테스트 코드입니다.package com.devholic22.board.repository; import com.devholic22.board.entity.Member; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @Slf4j @SpringBootTest public class MemberRepositoryTest { @Autowired MemberRepository repository; @Test void save() { Member member = new Member("testerA", "1234"); Member savedMember = repository.save(member); log.info(savedMember.toString()); } } 그런데 강의에서 이야기하셨던 SQL 스크립트를 만들어두지 않았는데도, 테스트가 제대로 실행되었습니다.원래 예상했던 결과는 Table "MEMBER" not found와 같은 부분인데, 왜 이런 에러가 발생하지 않았는지 궁금합니다.테스트 코드 결과입니다.2022-12-11T13:15:08.008+09:00 INFO 65418 --- [ main] c.d.b.repository.MemberRepositoryTest : Started MemberRepositoryTest in 7.134 seconds (process running for 9.374) 2022-12-11T13:15:08.427+09:00 DEBUG 65418 --- [ main] org.hibernate.SQL : insert into member (id, user_name, password) values (default, ?, ?) 2022-12-11T13:15:08.501+09:00 INFO 65418 --- [ main] c.d.b.repository.MemberRepositoryTest : Member(id=1, name=testerA, password=1234) 2022-12-11T13:15:08.547+09:00 INFO 65418 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2022-12-11T13:15:08.549+09:00 INFO 65418 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down' 2022-12-11T13:15:08.550+09:00 DEBUG 65418 --- [ionShutdownHook] org.hibernate.SQL : drop table if exists member cascade 2022-12-11T13:15:08.555+09:00 INFO 65418 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2022-12-11T13:15:08.560+09:00 INFO 65418 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
해당 강의 중 readOnly 관련 질문입니다!
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]안녕하세요.리드온리 옵션 관련해서 강의를 듣다가 궁금해진건데서비스에서 Select문 만을 호출하는 메서드가 @Transaction(readOnly = true)를 달고 있는 것과 트랜잭셔널 어노테이션을 아예 안달고 있는 것 중에 후자가 성능상 이점이 있나요?아니면 해당 설명은 타입 수준에서 트랜잭션을 걸고 읽기 전용 트랜잭션 메서드를 따로 정의하기 위한 것인가요?
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
Querydsl 프로젝트의 IntelliJ IDEA 빌드 필요성
안녕하세요. 강의 항상 잘 듣고 있습니다. 강사님이 항상 빌드 도구를 Gradle에서 IntelliJ IDEA로 변경하는 것이 좋다고 말씀하신 점은 알고 있어이번 기회에 찾아보니 증분 빌드를 사용해서 변경되는 부분에 대해서만 빌드를 진행하여 빠르다고 하는 것을 알게 되었습니다. Querydsl 설정에서 Gradle로 빌드시 의존성만 추가하면 쉽게 사용 가능하지만 IntelliJ IDEA로 빌드시 build 폴더 하위에 생성되는 것이 아니기에 아래와 같은 3가지 추가 설정이 필요한데요.clean 명령어 작업 추가clean { delete file('src/main/generated') }.gitignore 추가/src/main/generated/main 실행 또는 프로젝트 빌드Build > Build Project강의 내용을 좀 벗어나는 질문인 것 같기는 하지만 무조건적으로 사용할 필요가 있나?라는 의문이 들었습니다. 이전에 실전! Querydsl 강의에서는 com.ewerk.gradle.plugins.querydsl 플러그인을 사용하였는데 src/main/generated 하위가 아니라 똑같이 build 하위에 생성되어서 이런 고민을 못해본 것 같습니다! 감사합니다.
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
jar 실행시 에러가 나서 질문 드립니다.
MyBatis 버전을 빌드하고java -jar Item.jar 로 실행 하면 에러가 나는데 혹시 해결 방안이 있을까요?IDE에선 잘돌아 갑니다.구글링을 해봐도 딱히 안보여 질문 남깁니다.
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
@Transactional 내부 메소드 호출
@Slf4j @Service public class MainService { public void methodA() { log.info("=== methodA 시작... ==="); // 비즈니스 로직 A .... methodB(); log.info("=== methodA 종료... ==="); } @Transactional public void methodB() { log.info("=== methodB 시작... ==="); log.info("-> is methodB transaction active? = {}", TransactionSynchronizationManager.isActualTransactionActive()); // 비즈니스 로직 B .... log.info("=== methodB 종료... ==="); } } @Controller @RequiredArgsConstructor public class MainController { private final MainService mainService; public ResponseEntity<Void> transactionalTestMethod() { mainService.methodA(); return ResponseEntity.ok().build(); } }이러한 MainService 구조에서 MainController에서 mainSerivce.methodA()를 호출하게 되면 methodA는 현재 트랜잭션을 유도하지 않고 있고 그 내부적으로 methodB는 내부 프록시 호출이기 때문에 당연히 methodB에는 트랜잭션 처리가 되지 않음은 이해하였습니다 그런데 이와 관련해서 실험을 하던 도중에@Slf4j @Service public class MainService { @Transactional public void methodA() { log.info("=== methodA 시작... ==="); // 비즈니스 로직 A .... methodB(); log.info("=== methodA 종료... ==="); } @Transactional public void methodB() { log.info("=== methodB 시작... ==="); log.info("-> is methodB transaction active? = {}", TransactionSynchronizationManager.isActualTransactionActive()); // 비즈니스 로직 B .... log.info("=== methodB 종료... ==="); } }이러한 MainService 구조에서 mainService.methodA를 호출할 때 methodA에는 트랜잭션 처리가 됨이 당연한데 그 내부적으로 this.methodB를 호출하면 methodB에도 TransactionSynchronizationManager.isActualTransactionActive()의 결과가 true로 나옴에 따라 트랜잭션 처리가 되는 것 같습니다 제가 알고있던 것은 methodA에서 트랜잭션 처리가 진행이 되더라도 methodB는 내부적으로 호출되는 메소드이기 때문에 트랜잭션 처리가 이루어지지 않는다고 알고있었습니다 물론 Log를 찍어도 methodB 전 후에는 Participating transaction 관련 로그는 없었습니다 하지만 methodB에서 TransactionSynchronizationManager.isActualTransactionActive()의 결과가 true로 나옴에 따라 트랜잭션 처리가 유지되는 것 같은데 혹시 위와 같은 구조에서도 트랜잭션 처리가 유효한건가요?
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
트랜잭션 전파2 활용에서 질문
1. 강의 내용과 관련된 질문인가요? 네2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 네3. 질문 잘하기 메뉴얼을 읽어보셨나요? 네[질문 내용]여기에 질문 내용을 남겨주세요. 질문1.리포지토리안의 각 메소드에서만 @트랜잭션 했을때joinV2 메소드 사용하면/** * MemberService @Transactional:OFF * MemberRepository @Transactional:ON * LogRepository @Transactional:ON Exception */ @Test void outerTxOff_fail_My() { //given String username = "로그예외_outerTxOff_fail_my"; //when memberService.joinV2(username); //예외 터짐 //then: member는 저장, 로그는 안남음 assertTrue(memberRepository.find(username).isPresent()); assertTrue(logRepository.find(username).isEmpty()); }테스트 성공하는데,이렇게 해도 요구사항에 만족하는거 아닐까요?이렇게 했을 때의 문제는 어떤거 인가요? 질문2.위 테스트 전에Log레포에 @트랜잭션 안써주면, 되지 않을까? 해서/** * MemberService @Transactional:ON * MemberRepository @Transactional:ON * LogRepository @Transactional:OFF EX */ @Test void Mytest_ex_success() { //given String username = "로그예외_mytest_success"; //when memberService.joinV2(username); //then: member 저장, log 롤백 assertTrue(memberRepository.find(username).isPresent()); assertTrue(logRepository.find(username).isEmpty()); }정상 유저시 -> 정상 커밋 -> 정상 저장 (유저 존재, 로그 존재)-> 이때 로그가 왜 정상 저장?로그예외 유저시 -> 서비스에서 예외 잡아 처리 -> 로그 정상 커밋됨 -> 로그 없길 기대, but 존재-> 이때도 로그가 왜 정상 저장? - 로그보면 트랜잭션에 참여하진 않음 그래서, 디버깅을 해봤는데엔티티 매니저가 없어서, 멤버가 쓰는 매니저 공유해서 썼고, 트랜잭션에 참여하지 않고, 동기화 매니저에 있는 커넥션으로, 수동 커밋 설정없이 그냥 저장했나? 했는데검색해보니까SimpleJpaRepository에서 @트랜잭션 한다고 해서 디버깅 걸어보니 걸리더라구요SimpleJpaRepository 클래스에도 @트랜잭션 되있고,여기의 save메소드에도 @트랜잭션 되있더라구요. 그럼 레포지토리 메소드에 @트랜잭셔널 안써줘도그 윗단에 올라가면 SimpleJpaRepository가 있고, 여기에 @트랜잭션이 있어서 트랜잭션이 작동한다. 맞게 이해한 건지 궁금해서 여쭤봅니다.감사합니다.
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
DTO 관련하여 질문드립니다.
강의에서 DTO의 위치는 최종 호출자가 소유자라고 말씀주셨는데요, 그렇다면 웹 컨트롤러에서 requestDTO가 서비스의 파라미터로 넘어간다면 requestDTO가 웹 관심사임에도 불구하고 서비스 계층에 두시는지, 그게 아니라면 계층 간의 경계에서 데이터를 주고받을 때 어떤 방식을 선호하시는지 궁금합니다!
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
스프링 트랜잭션 전파 - REQUIRES_NEW 에 궁금한 점
안녕하세요 영한님, 서포터즈님들REQUIRES_NEW 옵션을 공부하면서 궁금한 점이 있습니다.여기서 로직2의 트랜잭션 매니저에서는 내부 트랜잭션이므로 rollbackOnly 옵션을 확인하지 않는 것이 당연한 것으로 알고 있습니다.그런데 외부, 내부 트랜잭션 구별 방법이 이전까지는 신규 트랜잭션인지 아닌지로 구별하였는데, REQUIRES_NEW 옵션에서는 외부,내부 트랜잭션을 구별하는 또 다른 옵션이 존재할까요,,?제 생각에는 있을 것 같아서 내부 트랜잭션에서는 어떤 옵션이 있어서 rollbackonly 옵션을 확인하지 않을 것 같아서 질문 드립니다!!키워드 알려주시면 찾아보겠습니다. 감사합니다.
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
Querydsl 강의와 연관된 질문
안녕하세요 영한님, 서포터즈님들저는 영한님 Querydsl 강의를 다 듣고 나서 현재 강의를 듣고 있는데, 현재 강의의 querydsl 의 클래스 의존관계와 Querydsl 강의의 클래스 의존관계가 달라 궁금한 점이 생겼습니다.기존 Querydsl 강의에서는 아래의 사진과 같은 클래스 의존관계로 만들었습니다.public interface ItemRepositoryV3 extends JpaRepository<Item, Long>, ItemRepositoryCustom { } public interface ItemRepositoryCustom { } public class ItemRepositoryImpl implements ItemRepositoryCustom { } @RequiredArgsConstructor @Service public class ItemServiceV3 { private final ItemRepositoryV3 itemRepositoryV3; }ItemServiceV3에서는 ItemRepositoryV3를 주입받는데 이때 구현체는 어떤 것인지 궁금해서 코드를 작성해보았습니다.@SpringBootTest class ItemServiceV3Test { @Autowired ItemServiceV3 itemServiceV3; @Autowired ItemRepositoryV3 itemRepositoryV3; @Autowired ItemRepositoryCustom itemRepositoryCustom; @Test void aa() throws Exception { System.out.println("itemServiceV3.getClass() = " + itemServiceV3.getClass()); System.out.println("itemRepositoryV3.getClass() = " + itemRepositoryV3.getClass()); System.out.println("itemRepositoryCustom.getClass() = " + itemRepositoryCustom.getClass()); System.out.println("ProxyUtils.getUserClass(itemRepositoryV3) = " + ProxyUtils.getUserClass(itemRepositoryV3)); System.out.println("ProxyUtils.getUserClass(itemRepositoryCustom) = " + ProxyUtils.getUserClass(itemRepositoryCustom)); } }itemServiceV3.getClass() = class hello.itemservice.repository.v3.ItemServiceV3itemRepositoryV3.getClass() = class com.sun.proxy.$Proxy106itemRepositoryCustom.getClass() = class com.sun.proxy.$Proxy106ProxyUtils.getUserClass(itemRepositoryV3) = class org.springframework.data.jpa.repository.support.SimpleJpaRepositoryProxyUtils.getUserClass(itemRepositoryCustom) = class org.springframework.data.jpa.repository.support.SimpleJpaRepository 그래서 위와 같은 결과를 얻을 수 있었는데요여기서 궁금한 점이 ItemRepositoryCustom 인터페이스는 아무런 제스처도 취하지 않았는데 왜 프록시 클래스가 만들어진건지 모르겠습니다..,,제가 Querydsl 강의에서 놓친 부분이 있다면 어느 부분인지 말씀해주시면 감사하겠습니다.긴 글 읽어주셔서 감사합니다.
- 해결됨스프링 DB 2편 - 데이터 접근 활용 기술
hibernate 버전
안녕하세요 영한님, 서포터즈님들이전 Spring Data JPA에서 hibernate 버전으로 인해서 % like 연산이 제대로 동작하지 않아서 버전을 바꿔줬었습니다.그런데 이번 강의에서 hibernate 버전을 변경하지 않아도 테스트가 제대로 동작을 했습니다.제가 알고 있는 바로는 querydsl은 jpql을 사용하기 쉽게, 컴파일 타임 때 에러를 잡을 수 있게 도와주므로 결국 querydsl -> jpql -> jpa -> hibernate 요렇게 진행방식(?)이 된다고 생각했습니다.그러면 저번에 spring data jpa에서 제대로 테스트 되지 않았던 findItem이 querydsl 을 사용할 때도 안되야하지 않나라는 생각이 들어서 질문드립니다감사합니다
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
트랜잭션 ReadOnly 컨트롤 질문드립니다.
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]Transaction을 어노테이션으로 설정하는 방법 중에 궁금한 점이 있습니다.트랜잭션으로 선언하지 않은 메소드에서 트랜잭션로 선언한 내부 메서드를 불러오는 경우 AOP를 통하지 않기 때문에 @Transactional이 적용되지 않는다는 것은 이해하였습니다. 그리고 이 문제를 해결하기 위해 Class를 분리하여 주입해주는 방법으로 Proxy 클래스에서 메서드를 가져오는 것도 확인하였습니다.확인을 위해 테스트를 작성하던 중 ReadOnly관련해서 질문이 생겼는데요.트랜잭션을 ReadOnly를 True로 선언한 첫 번째 메서드에서 ReadOnly를 False로 선언한 두번째 메서드를 부룬뒤, 트랜잭션 매니저의 isCurrentTransactionReadOnly가 True에서 False로 변경되지 않을까 하였는데요.생각과는 달리 변경되지 않는 것을 확인하였습니다.이런 경우에는 첫번째 트랜잭션으로 이미 감싸져 있는 상태이기 때문에 두번째 트랜잭션이 무시된 것일까요?그리고 TransactionSynchronizationManager.setCurrentTransactionReadOnly 메서드를 통해 ReadOnly의 속성을 변경할 수 있는 것을 확인하였는데, 실무에서 이렇게 TransactionSynchronizationManager를 통해 속성을 변경하여 사용하는 경우가 있을까요?readOnly로 하여 많은 데이터를 가져온 후, 결과를 입력하는 경우에 데이터를 readonly로 가져오는 것이 빠르기도 하고 전체 프로세스를 같은 트랜잭션을 사용해 조금 유용하지 않을까 하여 질문드립니다.
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
테스트 시 테이블 생성 방법
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]테스트시 테이블 초기 생성 질문드립니다.강의에서는 테스트 테이블 세팅을 위해 /test/resources/schema.sql 파일을 생성하고 내부에 DDL을 작성하여서 테이블을 생성해주었는데요.이 방법은 엔티티가 많아질 수록 DDL이 많아져서 관리하기에 어려움이 있을 것 같다는 생각이 듭니다.그래서 test의 application.properties 내부에 spring.jpa.hibernate.ddl-auto=create 설정을 주어 테이블 생성과 validation을 위임하는 것이 좋지 않을까 하는 생각이 있는데, 어떻게 생각하시는지 궁금합니다.
- 해결됨스프링 DB 2편 - 데이터 접근 활용 기술
구조 관련 질문 드립니다!
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]서비스가 스프링 데이터 JPA 와 Querydsl 둘 다 쓸때 repository 인터페이스에 의존하고 싶으면 앞에서 설명해주신 어뎁터를 하나 중간에 넣어서 그 어뎁터가 repository를 implements하면서 안에서 스프링 데이터 jpa와 querydsl을 쓰면 되는건가요??
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
내부 커밋
내부에서 롤백을 하면 스프링에서 rollback-only 처리를 해주는데 내부에서 커밋하는건 의미가 없는거 아닌가요 ? 그렇다면 내부코드에서 commit 하는 코드를 넣지 않아도 되는건가요 ?
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
리턴값
29:31 처럼 Exception을 터뜨리는게 아니라리턴값 String 으로 보내서 이것을 호출한 컨트롤러가 그 다음 분기처리를 하라는 말씀이신가요 ??
- 해결됨스프링 DB 2편 - 데이터 접근 활용 기술
마지막 부분 6:35 질문
이렇게 JPA와 JdbcTemplate을 함께 사용할 경우 JPA의 플러시 타이밍에 주의해야 한다. JPA는데이터를 변경하면 변경 사항을 즉시 데이터베이스에 반영하지 않는다. 기본적으로 트랜잭션이 커밋되는시점에 변경 사항을 데이터베이스에 반영한다. 그래서 하나의 트랜잭션 안에서 JPA를 통해 데이터를변경한 다음에 JdbcTemplate을 호출하는 경우 JdbcTemplate에서는 JPA가 변경한 데이터를 읽기못하는 문제가 발생한다.이 문제를 해결하려면 JPA 호출이 끝난 시점에 JPA가 제공하는 플러시라는 기능을 사용해서 JPA의 변경내역을 데이터베이스에 반영해주어야 한다. 그래야 그 다음에 호출되는 JdbcTemplate에서 JPA가반영한 데이터를 사용할 수 있다.================================같은 하나의 트랜젝션인데변경한 다음에 JdbcTemplate을 호출하는 경우 JdbcTemplate에서는 JPA가 변경한 데이터를 읽기못하는 문제가 발생한다.--이 이유가 데이터를 커밋하지않고 1차 캐쉬에만 변경 한 값을 가지고 있으니까 jdbc 템플릿은 변경 한값을 알 수 없어서 생기는 문제라고 생각하면 되는건가요 ?
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
@BeforeEach 사용할 때 트랜젝션 사이클 문의
안녕하세요!테스트 케이스에 @BeforeEach로 데이터 삽입 후 처리하는 중 오류가 발생해서 문의 드립니다.해당 오류 : java.util.NoSuchElementException: No value present통합 테스트에서만 발생하고 개별로 메소드마다 돌리면 문제 없음.제가 이해하기론 Test class에 @Transactional을 붙여주고 돌리면 테스트 메소드 1개 실행 후 정상 처리되면 롤백. 이렇게 이해하고 있습니다. 그래서 제 테스트가 돌아가는 사이클을@BeforeEach - > 좋아요_개수 Test -> 정상 처리 롤백@BeforeEach - > 좋아요_여부 Test -> 정상 처리 롤백@BeforeEach - > 좋아요_체크 Test -> 정상 처리 롤백@BeforeEach - > 좋아요_해제 Test -> 정상 처리 롤백위와 같이 이해하고 테스트들마다 정상 처리 롤백 후 비어있는 데이터에 다시 @BeforeEach로 데이터를 넣어주기에 넣어주는 데이터들의 ID값(GeneratedValue)이 1부터 시작하는 걸로 이해했습니다.그런데 등록된 데이터의 ID값으로 findOne을 하니 해당 ID가 없다고 뜹니다.무슨 문제인가..하고 디버깅을 돌리니1번 좋아요_개수 테스트의 business ID = 1번, 2번2번 좋아요_여부 테스트의 business ID = 3번, 4번3번 좋아요_체크 테스트의 business ID = 5번, 6번4번 좋아요_해제 테스트의 business ID = 7번, 8번이렇게 ID값이 부여가 됩니다. 롤백 자체가 되지 않았다면 1번을 조회했을 때도 데이터는 나와야 하는데 그건 나오지 않고 ID값만 다음 숫자로 넘어가는데 제가 무언가 놓치고 있을까요? 선생님의 강의는 전부 결제해서 트랜젝션 관련 부분을 아무리 들어봐도 아직 미숙해서 그런지 해당 부분의 문제를 모르겠습니다..클래스 레벨에 @Transactional을 붙여주면 내부에 before() 메소드 안에 트랜젝션들도 전파되고, 정상 처리되는 로직이라면 데이터 등록 후 좋아요_ㅁㅁ Test 로 넘어가고, 그 안에도 정상처리 되면 클래스 레벨에 물리 트랜젝션 커밋 처리 후 다음 테스트 로직이 실행 되는 걸로 이해 했는데 잘못 이해했다면 말씀 부탁드립니다ㅠ.ㅠ BusinessMarkServiceTest@SpringBootTest @Transactional public class BusinessMarkServiceTest { @Autowired BusinessMarkService businessMarkService; @Autowired MemberService memberService; @Autowired BusinessService businessService; @Autowired LoginService loginService; @BeforeEach public void before() { Member member = new Member(); member.setNickname("testMember"); member.setMail("testMember@test.com"); member.setPassword("test1234!"); member.setMember_type("B"); member.setMember_status("J"); member.setHint_password("hint_01"); member.setAnswer_password("answer"); member.setUpdated_at(now()); member.setCreated_at(now()); memberService.join(member); Member member2 = new Member(); member2.setNickname("testMember2"); member2.setMail("testMember2@test.com"); member2.setPassword("test1234!"); member2.setMember_type("B"); member2.setMember_status("J"); member2.setHint_password("hint_01"); member2.setAnswer_password("answer"); member2.setUpdated_at(now()); member2.setCreated_at(now()); memberService.join(member2); Business business = new Business(); business.setBusinessName("테스트밥집"); business.setHomepage("test.com"); business.setPhone("010-1234-5678"); business.setAddress("제주특별자치도 제주시 첨단로 242"); business.setLng((float) 33.450701); business.setLat((float) 126.570667); business.setCreated_at(now()); business.setUpdated_at(now()); businessService.join(business); Business business2 = new Business(); business2.setBusinessName("테스트밥집2"); business2.setHomepage("test2.com"); business2.setPhone("010-1234-5678"); business2.setAddress("제주특별자치도 제주시 첨단로 242"); business2.setLng((float) 33.450701); business2.setLat((float) 126.570667); business2.setCreated_at(now()); business2.setUpdated_at(now()); businessService.join(business2); } @Test public void 좋아요_체크() throws Exception{ //given Member loginMember = loginService.login("testMember2@test.com", "test1234!"); Business findBusiness = businessService.findOne(1L); //when BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId()); businessMarkService.pushBusinessMark(businessMarkDto); //then assertEquals(1, businessMarkService.getBusinessLikeInfo(businessMarkDto).getBusinessLikeNum()); } @Test public void 좋아요_해제() throws Exception{ //given Member loginMember = loginService.login("testMember@test.com", "test1234!"); Member loginMember2 = loginService.login("testMember2@test.com", "test1234!"); Business findBusiness = businessService.findOne(1L); //좋아요 표시하기 BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId()); businessMarkService.pushBusinessMark(businessMarkDto); BusinessMarkDto businessMarkDto2 = new BusinessMarkDto(loginMember2,findBusiness.getId()); businessMarkService.pushBusinessMark(businessMarkDto2); //when //businessMarkDto2가 한번 더 좋아요 눌러서 해제 시키기 businessMarkService.pushBusinessMark(businessMarkDto2); //then assertEquals(1, businessMarkService.getBusinessLikeInfo(businessMarkDto).getBusinessLikeNum()); } @Test public void 좋아요_개수() throws Exception{ //given Member loginMember = loginService.login("testMember@test.com", "test1234!"); Member loginMember2 = loginService.login("testMember2@test.com", "test1234!"); Business findBusiness = businessService.findOne(1L); //좋아요 표시하기 BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId()); businessMarkService.pushBusinessMark(businessMarkDto); BusinessMarkDto businessMarkDto2 = new BusinessMarkDto(loginMember2,findBusiness.getId()); businessMarkService.pushBusinessMark(businessMarkDto2); //when long businessMarkNum = businessMarkService.getBusinessLikeInfo(businessMarkDto2).getBusinessLikeNum(); //then assertEquals(2, businessMarkNum); } @Test public void 좋아요_여부() throws Exception{ //given Member loginMember = loginService.login("testMember@test.com", "test1234!"); Business findBusiness = businessService.findOne(1L); //when BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId()); businessMarkService.pushBusinessMark(businessMarkDto); Boolean check = businessMarkService.getBusinessLikeInfo(businessMarkDto).getCheck(); //then assertEquals(true, check); } } BusinessMarkService@Slf4j @Service @Transactional @RequiredArgsConstructor public class BusinessMarkService { private final BusinessMarkRepository businessMarkRepository; private final BusinessRepository businessRepository; //좋아요 및 취소 public Boolean pushBusinessMark(BusinessMarkDto businessMarkDto) { businessMarkRepository.BusinessMarkSearch(businessMarkDto.getMember().getMail(), businessMarkDto.getBusinessId()) .ifPresentOrElse(businessMark -> businessMarkRepository.deleteById(businessMark.getId()), ()-> { Business business = getBusiness(businessMarkDto); businessMarkRepository.save(new BusinessMark(businessMarkDto.getMember(), business)); }); return true; } //업체 게시글 찾기 @Transactional(readOnly = true) public Business getBusiness(BusinessMarkDto businessMarkDto) { return businessRepository.findById(businessMarkDto.getBusinessId()) .orElseThrow(() -> new IllegalArgumentException("해당 게시글은 존재하지 않습니다.")); } // 좋아요 개수 @Transactional(readOnly = true) public BusinessMarkResponseDto getBusinessLikeInfo(BusinessMarkDto businessMarkDto) { long businessLikeNum = getBusinessLikeNum(businessMarkDto); boolean check = checkPushedLike(businessMarkDto); return new BusinessMarkResponseDto(businessLikeNum, check); } @Transactional(readOnly = true) public Boolean checkPushedLike(BusinessMarkDto businessMarkDto) { return businessMarkRepository.BusinessMarkSearch(businessMarkDto.getMember().getMail(), businessMarkDto.getBusinessId()) .isPresent(); /*Optional<BusinessMark> businessMark = businessMarkRepository.BusinessMarkSearch(businessMarkDto.getMember().getMail(), businessMarkDto.getBusinessId()); if(businessMark != null){ return true; } return false;*/ } @Transactional(readOnly = true) public long getBusinessLikeNum(BusinessMarkDto businessMarkDto) { return businessMarkRepository.BusinessMark(businessMarkDto.getBusinessId()); } } MemberService@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class MemberService { @Autowired private final MemberRepository memberRepository; @Autowired private PasswordEncoder passwordEncoder; /** 회원가입 **/ @Transactional public String join(Member member){ //비밀번호 암호화 후 레포지토리에 넘기기 String encodedPassword = passwordEncoder.encode(member.getPassword()); member.setPassword(encodedPassword); validateDuplicateMember(member);//중복회원검증 memberRepository.save(member); return member.getMail(); } /** 중복회원검증 **/ private void validateDuplicateMember(Member member) { List<Member> findMembers = memberRepository.findByMail(member.getMail()); if (!findMembers.isEmpty()){ throw new IllegalStateException("이미 존재하는 회원입니다"); } } private void validateDuplicateNickname(Member member) { Member findMembers = memberRepository.findOndByMail(member.getMail()); if (!findMembers.getNickname().equals(member.getNickname())){ throw new IllegalStateException("이미 존재하는 회원입니다"); } } /** 회원전체조회 **/ public List<Member> findMembers(){ return memberRepository.findAll(); } public Member findOne(String mail) { return memberRepository.findOndByMail(mail); } /** 회원 수정 **/ @Transactional public void update(String mail, String nickname, String answer_password){ Member member = memberRepository.findOndByMail(mail); if(nickname.equals(member.getNickname())) { validateDuplicateNickname(member); } member.setNickname(nickname); member.setAnswer_password(answer_password); member.setUpdated_at(now()); } /** 회원 탈퇴 **/ @Transactional public String delete(String mail, String password){ Member findUser = memberRepository.findOndByMail(mail); if(!passwordEncoder.matches(password,findUser.getPassword())){ //throw new IllegalStateException("비밀번호가 맞지 않습니다."); System.out.println("암호 실패"); return null; } findUser.setUpdated_at(now()); findUser.setMember_status("D"); return findUser.getPassword(); } } BusinessService@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class BusinessService { @Autowired private final BusinessRepository businessRepository; @Autowired private final MemberRepository memberRepository; @Autowired private PasswordEncoder passwordEncoder; /** 중복업체검증 **/ private void validateDuplicateBusiness(Business business) { List<Business> findBusiness = businessRepository.findByBusinessName(business.getBusinessName()); if (!findBusiness.isEmpty()){ throw new IllegalStateException("이미 존재하는 업체명입니다"); } } /** 업체생성 **/ @Transactional public String join(Business business){ validateDuplicateBusiness(business);//중복회원검증 log.info("business {}", business); businessRepository.save(business); return business.getBusinessName(); } /** 업체 수정 **/ /** @Transactional public void saveBusiness(Business business){ businessRepository.save(business); } **/ @Transactional public void update(Long id, String BusinessName, String homepage, String phone, String address, float lat, float lng, String etc){ Business business = businessRepository.findById(id).orElseThrow(); if(!BusinessName.equals(business.getBusinessName())) { validateDuplicateBusiness(business); //중복 업체명 검증 } business.setBusinessName(BusinessName); business.setHomepage(homepage); business.setPhone(phone); business.setAddress(address); business.setLat(lat); business.setLng(lng); business.setEtc(etc); business.setUpdated_at(now()); } /** 업체 삭제 **/ @Transactional public String delete(Member member, Long id){ Member findUser = memberRepository.findOndByMail(member.getMail()); //Business business = businessRepository.findById(id).get(); Business business = businessRepository.findById(id).orElseThrow(); if(!passwordEncoder.matches(member.getPassword(),findUser.getPassword())){ //throw new IllegalStateException("비밀번호가 맞지 않습니다."); System.out.println("암호 실패"); return null; } business.setStatus("D"); business.setUpdated_at(now()); return findUser.getPassword(); } /** 업체 찾기 **/ public Business findOne(Long id) { Business business = businessRepository.findById(id).orElseThrow(); return business; } } LoginService@Service @Transactional(readOnly = true) @RequiredArgsConstructor public class LoginService { @Autowired private final MemberRepository memberRepository; @Autowired private PasswordEncoder passwordEncoder; public Member findOne(String mail){ return memberRepository.findOndByMail(mail);} /** 로그인 **/ @Transactional //public String login(Member member){ public Member login(String mail, String password){ List<Member> findMember = memberRepository.findByMail(mail); Member findUser = memberRepository.findOndByMail(mail); if (findMember==null){ //throw new IllegalStateException("해당 이메일의 유저가 존재하지 않습니다."); //System.out.println("이메일 실패"); return null; } if(!passwordEncoder.matches(password,findUser.getPassword())){ //throw new IllegalStateException("비밀번호가 맞지 않습니다."); return null; } System.out.println("로그인 완료 :"+findUser.getNickname()); //세션 표시를 위해 닉네임값 넘기기 return findUser; } /** 비밀번호 매치 **/ @Transactional public String passwordMatches(Member member){ Member findUser = memberRepository.findOndByMail(member.getMail()); if(!passwordEncoder.matches(member.getPassword(),findUser.getPassword())){ //throw new IllegalStateException("비밀번호가 맞지 않습니다."); System.out.println("암호 실패"); return null; } return findUser.getPassword(); } /** 비밀번호 변경 **/ @Transactional public void updatePassword(String mail, String editPassword){ Member member = memberRepository.findOndByMail(mail); if (editPassword.equals(member.getPassword())) { throw new IllegalStateException("이전 비밀번호와 동일합니다."); } member.setPassword(editPassword); member.setUpdated_at(now()); } }
- 해결됨스프링 DB 2편 - 데이터 접근 활용 기술
9:15 컴포넌트 대상
9:15에서 지금은 컴포넌트 스캔을 안쓴다는게 무슨 말씀이신가요??test 코드에서@Autowired ItemRepository itemRepository; 대상을 받아서 JpaItemRepository를 쓰는거 아닌가요 ??
- 해결됨스프링 DB 2편 - 데이터 접근 활용 기술
트랜잭션 질문
18:15 듣다가 질문이 생겼습니다(@commit 를 붙여야 업데이트 쿼리가 나간다.)우선 @트랜잭션을 맨위 상단에 선언 했다고 하고69~84 라인은 전체 하나의 단위 트랜잭션이고이 안에서 또 71~73 라인은 하나의 트랜잭션단위76~77 라인은 또다른 하나의 트랜잭션 단위80은 또 다른 하나의 트랜잭션 단위이렇게 생각하는게 맞나요 ?
- 미해결스프링 DB 2편 - 데이터 접근 활용 기술
JPA 하이버네이트
보통은 표준이 먼저나오고 그에대한 구현체가 나오는데JPA는 구현체인 하이버네이트가 먼저나오고추 후 표준이 만들어진건가요 ?