스프링 데이터 JPA가 제공해는 공통 인터페이스의 구현체가 존재한다.
아래의 경로로 들어가보자
아래의 SimpleJpaRepository가 JPA의 구현체이다.
그리고 SimpleJpaRepository는 Repository가 이미 걸려있는데 두가지 의미가 있다.
- Spring Bean의 컴포넌트 스캔 대상이 되는 것 => 스프링이 읽어들여서 컨테이너에 올리게 되는 것이고
- 두번째는 JPA나 JDBC 둘 다 exception이 틀리기 때문에 이게 터지면 영속성 계층에 있는 것들을 Spring에서 쓸 수 있는 예외들로 다 바꿔준다. 서비스 계층이나 컨트롤러 계층에 있는 것들을 넘길 때 JPA exception이나 JDBC exception이 올라가는 것이 아니고 Spring Framework이 제공하는 exceptiond으로 바껴서 올라간다.
- 즉 하부기술을 JDBC에서 JPA로 바껴도 exception을 처리하는 매커니즘이 동일하다.(Service나 Controller나 Repository 개체를 가져다 쓰는 입장에서는 ) => 하부 구현기술을 바꿔도 기존 비즈니스 로직에 최대한 영향을 주지 않는다.
밑에는 Transactional(readOnly = true)라고 되어있는데 SpringDataJPA의 모든 기능을 트랜잭션 걸고 시작한다는 뜻이다.
서비스 계층에 트랜잭션을 걸고 들어오면 트랜잭션을 이어받아서 동작하는데
트랜잭션이 없다해도 SpringDataJPA는 그냥 자신의 리포지토리 계층에서 트랜잭션 계층을 시작해버린다.
결론적으로 transactional은 조회를 하는 곳에서는 readOnly=true라고 되어있는데
데이터를 변경하는 곳에서는 그냥 transactional이 들어가게 된다.
- @Repository 적용: JPA 예외를 스프링이 추상화한 예외로 변환
- @Transactional 트랜잭션 적용
- JPA의 모든 변경은 트랜잭션 안에서 동작
- 스프링 데이터 JPA는 변경(등록, 수정, 삭제) 메서드를 트랜잭션에서 처리
- 서비스 계층에서 트랜잭션을 시작하지 않으면 리파지토리에서 트랜잭션 시작
- 서비스 계층에서 트랜잭션 시작하면 리파지토리는 해당 트랜잭션을 전파 받아서 사용
- => 스프링 데이터 JPA를 사용할 때 트랜잭션이 없어도 데이터, 등록, 변경이 가능했다.
- (사실 트랜잭션이 리포지토리 계층에 걸려있는 것)
@Transactional(readOnly = true)를 건다는 것은 db connection에다가 setAutocommit을 하고 False라는 옵션으로 넘긴다.
- 플러시를 생략해서 약간의 성능 향상을 얻음(데이터를 단순히 조회만 하고 변경하지 않는 트랜잭션에서 `readOnly = true`옵션을 사용하면)
- 원래는 트랜잭션이 끝나면 JPA의 영속성 컨텍스트에 있는 정보들을 db에 flush를 하고 commit을 한다.
- readOnly = true가 있으면 flush를 안한다!!(db에 Dirty Checking(변경감지)도 안하고 데이터도 보내지 않는다는 것이다!)
SImpleJpaRepository에 있는 save 메서드를 보자.
- save() 메서드
- 새로운 엔티티면 저장(persist)
- 새로운 엔티티 아니면 병합(merge) => db에 있는 데이터를 가져오고 save한 데이터로 바꿔치기
- 즉 merge를 호출하는 순간 db에 있는 데이터가 save로 저장된 파라미터로 다 교체가 된다.
- 트랜잭션 끝날 때 db에 반영이 되는 과정이 일어남
가급적이면 merge를 쓰지말자.(영속상태 엔티티가 어떤 이유로 영속상태를 벗어났는데 다시 영속상태가 되어야 할 때 써야 한다.)
데이터가 업데이트 할 때는 쓰지말자.
데이터의 변경은 변경감지를 써야한다. => 트랜잭션이 끝날 때 엔티티의 값만 자동으로 바뀌는걸 써야 한다.
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
// 만약에 새로운 앤티티 이면
if (entityInformation.isNew(entity)) {
// persist를 하고
em.persist(entity);
return entity;
// 아니면(db에 한번 들어갔다 나온것이면)
} else {
// merge를 호출
return em.merge(entity);
}
}
<출처 김영한: 실전! 스프링 데이터 JPA >
'Spring > SpringDataJPA' 카테고리의 다른 글
Specifications(명세) (0) | 2022.04.19 |
---|---|
새로운 엔티티를 구별하는 방법 (0) | 2022.04.18 |
Web 확장 - 도메인 클래스 컨버터 & 페이징과 정렬 (0) | 2022.04.18 |
사용자 정의 리포지토리 구현 (0) | 2022.04.18 |
JPA Hint & Lock (0) | 2022.04.17 |