스프링/6. 스프링 DB-2

54. 트랜잭션 전파 활용 1

sdafdq 2023. 10. 17. 07:47

비즈니스 요구사항

회원 등록, 조회

회원 데이터가 변경될 때 변경 이력을 DB LOG로 남겨야 함.

 

일단 등록 시에만 LOG 남기도록 함.

 

도메인부터 새로 만듦.

@Entity
@Getter @Setter
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    private String username;
    public Member() {
    }

    public Member(String username) {
        this.username = username;
    }

    
}

Long id 가 @Id이고,

@GeneratedValue 줘서 이거는 DB나 어디선가 할당해 주는 값을 받는 거임. 이거 안 넣어주면 우리가 Id 직접 넣어줘야 함.

 

JPA에서 기본 생성자(비어 있는 거) 있어야 함.

간단하게 username만.

 

 

@Slf4j
@Repository
@RequiredArgsConstructor
public class MemberRepository {
    private final EntityManager em;

    @Transactional
    public void save(Member member){
        log.info("member 저장");
        em.persist(member);
    }

    public Optional<Member> findByUsername(String username){

        return em.createQuery("select m from Member m where m.username = :username", Member.class)
                .setParameter("username", username)
                .getResultList().stream().findAny();
    }
}

리포지토리를 만듦.

이번엔 따로 SpringDataJPA 이용 안하고 그냥 EntityManager, 즉 JPA 이용해서 해 보겠음.

 

저장은 그냥 하면 됨.

 

id로 찾는 건 em에 있어서 상관없는데,

다른 걸로 찾는 건 쿼리(JPQL) 날려줘야 함.

 

쿼리 생성(쿼리, 반환받을타입)

.파라미터셋(키,밸류)

.반환.stream().findAny();

 

.반환.stream().findAny();

이거는 findAny() 위해서 stream()으로 바꾼 것 같은데, 

findAny()가 반환 된 것 중 가장 첫번째 그냥 주는 거고, Optional로 줌.

그래서 사용함.

보통 SpringDataJPA도 optional로 주니까.

 

 

 

@Entity
@Getter @Setter
public class Log {
    @Id @GeneratedValue
    private Long id;

    private String message;

    public Log() {
    }

    public Log(String message) {
        this.message = message;
    }
}

로그도 간단하게 message만.

 

 

@Slf4j
@Repository
@RequiredArgsConstructor
public class LogRepository {
    private final EntityManager em;

    @Transactional
    public void save(Log logMessage){
        log.info("log 저장");
        em.persist(logMessage);

        if(logMessage.getMessage().contains("로그예외")){
            log.info("log 저장 시 예외 발생");
            throw new RuntimeException("예외 발생");
        }
    }

    public Optional<Log> findByMessage(String message){
        return em.createQuery("select l from Log l where l.message = :message", Log.class)
                .setParameter("message", message)
                .getResultList().stream().findAny();
    }
}

똑같음.

Log log로 안한 이유는 lombok이랑 겹쳐서

 

그리고 그냥 테스트를 위해 메시지를 "로그예외"라고 하면 런타임 오류를 뱉게끔.

런타임 오류니 예외를 뱉으며 rollback될거임.

 

find도 똑같음.

 

@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService {
    private final MemberRepository memberRepository;
    private final LogRepository logRepository;

    public void joinV1(String username){
        Member member = new Member(username);
        Log logMessage = new Log(username);

        log.info("== memberRepository 호출 시작 ==");
        memberRepository.save(member);
        log.info("== memberRepository 종료 ==");

        log.info("== logRepository 호출 시작 ==");
        logRepository.save(logMessage);
        log.info("== logRepository 종료 ==");
    }

    public void joinV2(String username){
        Member member = new Member(username);
        Log logMessage = new Log(username);

        log.info("== memberRepository 호출 시작 ==");
        memberRepository.save(member);
        log.info("== memberRepository 종료 ==");


        try{
            log.info("== logRepository 호출 시작 ==");
            logRepository.save(logMessage);
        }catch (RuntimeException e){
            log.info("log 저장에 실패했습니다.", logMessage.getMessage());
            log.info("정상 흐름 반환");
        }
        log.info("== logRepository 종료 ==");

    }
}

서비스.

로그는 멤버 저장할 때 로그도 같이 남게끔 그냥 그런 역할.

둘다 username으로 저장하고, (Log는 message가 username)

 

뭐 그냥 그렇게 저장 하는거임.

 

joinV2는 한번 try catch로 잡아서 처리해 보는.

왜냐하면 로그 남기는 것 보다 Member 정상적으로 등록되고 클라이언트에게 정상적으로 등록되었다고 하는 게 더 중요함.

그래서 그냥 예외 먹어버림. 그러면 로그 롤백되도 그냥 정상 흐름됨.

 

 

뭐 트랜잭션 동작 보려는 것 같은데,

어차피 지금 MemberRepository, LogRepository 트랜잭션 범위가 따로라서 서로 상관 없음.

 

이거 같이 보려면 밖에서 한번 묶어야 함. 서비스나 어디서 @Transactional 해서

 

@Slf4j
@SpringBootTest
class MemberServiceTest {
    @Autowired
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;

    @Autowired
    LogRepository logRepository;

    @Test
    void outerTxOff_success(){
        String username = "outerTxOff_success";
        memberService.joinV1(username);
        assertTrue(memberRepository.findByUsername(username).isPresent());
        assertTrue(logRepository.findByMessage(username).isPresent());
    }
}

테스트.

지금은 그냥 정상적으로 되는 경우 보는 거.

 

이번에 쓴 assertTrue는 말 그대로 true인지 보는 건데,

이거는 core꺼 아니고 junit꺼 Assertions임.

 

Optional 메소드 중에 isPresent(), 즉 존재하는지(null이 아닌지) 여부 검사하는 메소드가 있어서,

존재하는 것이 참인지 확인.

assertThat 했었던 것들이랑 똑같음. 조건에 만족하면 넘어가는 거고 만족하지 않으면 예외 일으킴.

 

저 junit꺼 Assertions도 core 꺼랑 하는 것은 비슷비슷함.

'스프링 > 6. 스프링 DB-2' 카테고리의 다른 글

56. 트랜잭션 전파 활용 3  (0) 2023.10.17
55. 트랜잭션 전파 활용 2  (0) 2023.10.17
53. 트랜잭션 전파 6  (0) 2023.10.16
52. 트랜잭션 전파 5  (0) 2023.10.16
51. 트랜잭션 전파 4  (0) 2023.10.16