[JPA] Dirty check, @Transactional, Auditing

Dirty Check( = 상태 변경 확인)

JPA에서는 트렌잭션이 종료되는 시점에 모든 변경 사항을 반영시킨다.

Entity를 조회한다면 조회된 시점에 스냅샷을 남겨두고 트랜잭션 종료 시점에 Entity에 변경 사항이 있다면 update 후 종료한다.

 

예시 코드

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long update(Long id, PostsUpdateRequestDto requestDto){
        Posts posts = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다. id="+id));

        /*update 쿼리가 없이도 업데이트가 된다. => JPA의 영속성 컨텍스트 때문
        *영속성 컨텍스트란 엔티티를 영구저장하는 환경이다. 일종의 논리적 개념이며, JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈린다.
        * EntityManager가 활성화된 상태로 트랜잭션 안에서 데이터베이스에서 데이터를 가져오려면 데이터는 영속성 컨텍스트가 유지된 상태이다.
        * 이 상태에서 해당 데이터의 값을 변경하면 트랜잭션이 끝나는 시점에서 해당 테이블에 변경분을 반영한다.
        * 즉, Entity 객체의 값만 변경하면 별도의 update 쿼리를 날릴 필요가 없다. = 이 개념을 더티 체킹이라고 한다.= 상태변화 체크라고 보면된다.
        * */
        posts.update(requestDto.getTitle(), requestDto.getContent());

        return id;
    }
}

위 코드에서 posts.update를 호출해서 Entity 객체를 변경하긴 했지만 update 쿼리를 실행한 코드는 없다. 그럼에도 불구하고 변경사항이 반영되는 것이 코드 주석에도 적혀있지만, 영속성 컨텍스트가 유지된 상태에서 값을 변경하면 트랜잭션이 끝나는 시점에 변경사항을 반영한다.

 

 

@Transactional

트랜잭션은 ACID 특성을 지닌 "쪼갤 수 없는 업무의 최소 단위"를 뜻한다. 

  • A(Atomicity: 원자성)
    • 트랜잭션이 데이터베이스에 모두 반영되거나, 전혀 반영되지 않아야한다.
  • C(Consistency: 일관성)
    • 트랜잭션 작업 처리 결과가 항상 일관성이 있어야한다.
    • = 트랜잭션이 진행되는 동안에 DB가 변경되더라도 업데이트된 DB가 아닌 트랜잭션이 진행된 시점의 DB로 진행이 된다.
  • I(Isolation: 독립성)
    • 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이라도, 다른 트랜잭션의 연산에 끼어들 수 없다.
    • 하나의 트랜잭션이 완료될 때까지, 다른 트랜잭션이 특정 트랜잭션의 결과를 참조할 수 없다.
  • D(Durability: 지속성)
    • 트랜잭션이 성공적으로 완료됐을 경우, 결과는 영구적으로 반영되어야 한다.

@Transactional을 붙인 메소드는 트랜잭션의 특성을 가지게 된다.

메소드가 성공적으로 완료되면 커밋하고, 실패하면 롤백한다.

 

Auditing

Spring Data JPA에서 제공하는 기능으로 엔티티 생성, 변경 시점과 생성, 변경한 사람을 기록할 수 있다.

서비스를 운영할 때 데이터가 생성되고 수정한 일자를 기록하고 트래킹하는 것은 중요하기 때문에 사용된다.

 

사용방법

 

@EnableJpaAuditing 어노테이션을 Application 클래스에 붙여준다.

@EnableJpaAuditing  //JPA auditing 활성화
@SpringBootApplication  //이 어노테이션을 기점으로 설정을 읽어들이기 때문에 해당 파일은 항상 루트 폴더에 위치해야한다.
public class SpringPracticeApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringPracticeApplication.class, args);
    }
}

 

AuditingEntity 클래스 생성

@Getter
@MappedSuperclass //Entity들이 BaseTimeEntity를 상속할 경우 필드들(생성 시간, 수정 시간)도 컬럼으로 인식하게 된다.
@EntityListeners(AuditingEntityListener.class) //BaseTimeEntity에 auditing 기능을 포함시킨다.
public class BaseTimeEntity {
    //BaseTimeEntity는 모든 Entity의 상위 클래스가 되어 Entity들의 createdDate, modifiedDate를 자동으로 관리한다.

    @CreatedDate    //Entity가 생성되어 저장될 때 자동으로 시간이 저장된다.
    private LocalDateTime createDate;

    @LastModifiedDate   //조회한 Entity의 값을 변경할 때 시간이 자동으로 저장된다.
    private LocalDateTime modifiedDate;
}

 

Auditing 기능을 반영할 클래스에서 AuditingEntity를 상속

@Getter //Entity 클래스에서는 Setter를 생성하지 않는다.
@NoArgsConstructor
@Entity //기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍으로 매칭
public class Posts extends BaseTimeEntity { //시간관리를 위해 BaseTimeEntity 상속
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;    //웬만하면 타입의 Auto_Increment 권장 = 비즈니스 상 유니크 키는 pk보다는 유니크 키로 별도 활용

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;
}

이렇게 코드를 작성하면 Posts 객체 생성 시 createdDate와 modifiedDate가 자동으로 생성되고, 객체 변경 시 modifiedDate가 수정된다.