강의를 듣고 Repository에 대한 단위 테스트를 진행하면서
"EntityManager.persist(member) 하여도 insert문 안나가고 DB transaction commit 할 때 query가 나갑니다." 라는 설명을 듣고 개념들을 하나씩 찾아보기로 했다.
@Transaction
데이터베이스에서 논리적 상태 변화, 즉 Insert, Update, Delete로 데이터베이스의 데이터가 변화가 있는 것을 트랜잭션(transaction)이라고 한다.
Transcation은 Transaction은 2개 이상의 쿼리를 하나의 커넥션으로 묶어 DB에 전송(commit)하고, 이 과정에서 에러가 발생할 경우 자동으로 모든 과정을 원래대로 되돌려 놓는다.(rollback) 이러한 과정을 구현하기 위해 Transaction은 하나 이상의 쿼리를 처리할 때 동일한 Connection 객체를 공유하도록 한다.
persist 함수를 통해서 데이터를 입력 -> 중간에 에러를 발생 -> 트랜잭션(transaction) 범위에서 commit을 실행하지 않음 -> rollback를 실행 -> 데이터베이스의 데이터가 변하지 않고 그대로 트랜잭션(transaction)범위를 설정한 이전으로 돌아옴 |
즉, transaction.commit(); 하는 시점에 쓰기 지연 SQL 저장소에 있던 데이터들이 flush가 되고 DB에 쿼리가 날라가는 것
회원 Service 구현하면서 알게된 사실
- class level에 @Transactional annotation을 쓰면 그 안의 public method는 모두 transaction에 걸려들어간다.
- 옵션 readOnly = true 시 : jpa가 조회하는 곳에서는 성능 최적화됨 (dirty checking, 영속성 관련 등), 데이터 변경 불가능하므로 회원가입과 같은 method는 false로 설정
- class level보다 method level에서 설정한 annotation이 우선권 갖는다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
@Transactional //preference
public Long join(Member member) {
validateDuplicateMember(member); //check duplicate member
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException(member.getName() + " is an existed member.");
}
}
}
영속성 Context
Entity를 영구 저장하는 환경으로 EntityManager를 통해서 영속성 컨텍스트에 접근하고 Entity가 영속성 컨텍스트에 관리된다.
private final EntityManager entityManager;
//EntityManager를 통한 모든 Data 변경은 항상 Transaction 안에서 일어나야함
//@PersistenceContext spring이 Entity Manager를 만들어서 injection해줌
public Long save(Member member) {
entityManager.persist(member);
//영속성 context에 member entity를 넣는다.
//transaction이 commit되는 시점에 db에 반영됨(insert query 날아감)
return member.getId();
}
- 영속성 컨텍스트와 식별자 값
- 엔티티를 식별자 값(@id로 테이블의 기본 키와 매핑한 값)으로 구분
- 영속 상태는 식별자 값이 반드시 있어야 한다.
- 식별자 값이 없으면 예외 발생.
- 영속성 컨텍스트와 데이터베이스 저장
- JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영
- 플러시(flush)
- 영속성 컨텍스트가 엔티티를 관리하는 것의 장점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
Commit vs Flush
flush는 쿼리를 전송하는 역할이고, commit은 내부적으로 flush를 수행한 뒤 트랜잭션을 끝내는 역할이다.
즉 flush로 전송된 쿼리는 rollback할 수 있지만 commit은 트랜잭션을 끝내므로 rollback 할 수 없다.
EntityManager
The EntityManager API is used to access a database in a particular unit of work. It is used to create and remove persistent entity instances, to find entities by their primary key identity, and to query over all entities. This interface is similar to the Session in Hibernate. |
EntityManager는 특정 작업을 위해 데이터베이스에 액세스 하는 역할을 한다.
엔티티 매니저를 사용함에 있어 주의할 점은 여러 쓰레드가 동시에 접근하면 동시성 문제가 발생하므로 쓰레드 간에는 무조건 공유하면 안된다. 또한 EntityManager는 별도의 UPDATE 메서드를 제공하지 않는다.
EntityManager 함수
find()
find() 메서드는 영속 컨텍스트에서 엔티티를 검색하고 없을 경우 DB에서 데이터를 찾아 영속 컨텍스트에 저장한다. 여기서 식별자는 Entity 클래스에서 @Id 애노테이션으로 지정한 값을 사용해야 한다.
public find(Class entityClass, Object primaryKey)
persist()
persist() 메서드는 엔티티를 영속 컨텍스트에 저장 후 INSERT 쿼리를 실행하는데, Transaction 범위 내에서 실행해야 한다.
실행시점에 영속 Context에 Entity를 저장하고, Transaction을 commit()하는 시점에 insert 쿼리가 실행되므로 Transaction 범위에서 실행하지 않는다면 실제로 DB에 반영되지 않는다.
remove()
remove() 메서드는 Entity class를 영속 context에서 삭제 후 DELETE 쿼리를 실행하며, Transcation 범위 내에서 실행해야 한다.
public void remove(Object entity)
참고
Transaction 방법과 commit에 대해 설명이 되어있음
https://nowonbun.tistory.com/550
https://mangkyu.tistory.com/50
영속성 Context
https://ppomelo.tistory.com/146
entity manager
https://lng1982.tistory.com/276
https://dev-troh.tistory.com/151
'Web > JPA' 카테고리의 다른 글
[JPA]객체지향 쿼리 (0) | 2021.11.25 |
---|---|
[JPA]JUnit단위 Test 관련 정리 (0) | 2021.11.23 |
[JPA]JPQL(Java Persistence Query Language) (0) | 2021.11.22 |
[JPA]Annotation 정리 (0) | 2021.11.18 |