스프링부트를 활용해서 메모장 백엔드 서버를 만들어보는 실습을 하다가 궁금한 점이 생겼다.
// 메모 생성하기
@Transactional
public Memo createMemo(MemoRequestDto requestDto) {
Memo memo = new Memo(requestDto);
memoRepository.save(memo); // repository에 저장한다.
return memo;
}
// 메모 삭제하기
@Transactional
public Long deleteMemo(Long id) {
memoRepository.deleteById(id); // repository에서 삭제한다.
return id;
}
// 메모 수정하기
@Transactional
public Long update(Long id, MemoRequestDto requestDto) {
Memo memo = memoRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
);
memo.update(requestDto); // memo 객체의 값만 바꾸고 다시 repository에는 안 넣는다?!
return memo.getId();
}
Service에서 Repository에 저장(save)을 하거나, 삭제(delete)를 할 때는 repository 객체의 함수(memoRepository.save
, memoRepository.delete
)를 사용해서 데이터를 직접 저장하고 삭제했다.
그런데 수정을 할 때는 repository에서 데이터를 가져와 Memo 객체에 넣고, 그 객체를 수정한 후 다시 repository에 저장하지 않고 update 메서드를 끝냈다.
repository에서 꺼내 와서 수정한 memo를 다시 repository에 넣지 않았는데도 update 메서드가 끝난 후, DB에는 수정된 값이 잘 들어와있다.
왜 수정이 정상적으로 잘 되었는지 찾아보다가 그 이유가 @Transactional
어노테이션 덕분이라는 것을 알게 되었다.
@Transactional
어노테이션이 무슨 역할을 하기에 memoRepository.save
함수를 호출하지 않아도 변경된 부분이 제대로 업데이트 되었는지, 지금부터 알아보자!
예를 들어, 티켓팅을 생각해보자.
분명히 비어 있는 좌석을 선택했는데, 결제를 하려고 하면 “이미 선택된 좌석입니다”라는 문구를 자주 보게 된다.
자리를 조회할 때는 빈 좌석이었지만, 누군가가 나와 같은 좌석을 선택하고 나보다 빠르게 먼저 결제까지 성공했다면, 나의 결제 시도는 실패하고 내가 선택한 자리는 내 것이 아니게 된다.
내가 선택했던 자리와 결제 정보들이 커밋되지 않고 롤백되는 것이다.
자리를 선택하고 결제까지 완료하는 작업 단위가 하나의 트랜잭션이라고 볼 수 있다.
트랜잭션이 걸려있지 않다면, 같은 자리를 여러 명이 결제하게 되는 문제가 발생할 수 있다.
@Transactional
을 클래스나 메서드에 붙이면, 해당 범위가 트랜잭션이 되도록 보장해준다.처음으로 돌아와서, 이 어노테이션이 데이터를 수정하는 함수에 붙을 때 어떻게 DB에 저장되는지 알기 위해 살펴봐야 할 개념이 있다. 바로 영속성 컨텍스트이다.
save
, findById
등의 안을 살펴보면, 엔티티 매니저가 영속성 컨텍스트에 저장하고 조회하는 내용이 구현되어 있다.@Id
로 기본키(PK)와 매핑된 값)으로 구분한다.영속성 컨텍스트는 내부에 1차 캐시를 갖고 있다.
em.persist(member)
→ member 엔티티가 영속성 컨텍스트 안에 있는 1차 캐시에 저장된다.
em.find()
→ 1차 캐시에 찾는 엔티티가 있으면 DB에 SQL을 수행하지 않아도, 1차 캐시를 통해 엔티티를 조회할 수 있다.
만약 1차 캐시에 찾는 엔티티가 없으면, DB를 조회해서 엔티티를 생성하고 1차 캐시에 저장 후, 영속 상태의 엔티티를 반환한다.
처음 질문에 답변을 찾기 위해 많은 내용을 훑어보았다.
돌아와서 처음 질문에 답을 하자면, 영속성 컨텍스트의 1차 캐시와 변경 감지(dirty checking) 기능 덕분에 repository에서 가져온 엔티티의 데이터를 수정하면, 트랜잭션이 종료될 때(해당 메서드가 끝날 때) 변경된 엔티티의 값이 그대로 데이터베이스에 반영된다.
[참고자료]
<자바 ORM 표준 JPA 프로그래밍>
https://kafcamus.tistory.com/30
https://coding-factory.tistory.com/226
https://ttl-blog.tistory.com/108