JPA Hint & Lock
Hint
JAP 구현체에게 제공하는 힌트 (SQL 힌트랑 다름)
Read Only
Hint를 사용하면 영속성 콘텍스트에 저장되는 것을 막고 변경사항이 업데이트되지 않도록 막을 수 있습니다. 말 그대로 읽을 수만 있게 해주는 기능입니다.
실습
Repository에 사용자 이름으로 조회하는 메서드를 추가합니다.
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly",value = "true"))
Member findReadOnlyByUsername(String username);
@QueryHints 어노테이션을 추가해줌으로써 Hint를 사용할 수 있고 hibernate의 readOnly 속성을 true로 지정해야 합니다.
테스트 클래스로 실행을 해보겠습니다.
@Test
public void queryHint() {
Member member1 = new Member("member1", 10);
memberRepository.save(member1);
entityManager.flush();
entityManager.clear();
Member findMember = memberRepository.findReadOnlyByUsername("member1");
findMember.setUsername("member2");
entityManager.flush();
}
Hint를 넣지 않고 동작을 해보면 insert, select , update 순으로 실행이 됩니다. 여기서 hint를 넣고 실행을 하면?
select 쿼리만 실행되는 걸 확인할 수 있습니다. 이렇게 되면 영속성 컨텍스트를 여러 번 거칠 필요가 없기 때문에 메모리를 절약할 수가 있습니다.
Hint를 사용한다고 성능이 극적으로 좋아지지는 않습니다. 단순 조회나 부하가 심한 API에만 적용을 해보고, 성능 튜닝에 관한 부분은 성능 테스트 후, 판단하도록 합시다.
Lock
- 낙관적 잠금 : 낙관적 잠금은 데이터 갱신 시 경합이 발생하지 않을 것이라고 낙관적으로 보고 잠금을 거는 기법
- 비관적 잠금 : 동일한 데이터를 동시에 수정할 가능성이 높다는 비관적인 전제로 잠금을 거는 방식
- 암시적 잠금 : 프로그램 코드상에 명시적으로 지정하지 않아도 잠금이 발생하는 것
- 명시적 장금 : 프로그램을 통해 의도적으로 잠금을 실행하는 명시적 잠금
Lock에는 이렇게 다양한 종류가 있지만 위의 상황에 필요한 비관적 잠금을 사용해보겠습니다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);
Test 코드로 만들어
@Test
void lock() {
memberRepository.save(new Member("member1", 10));
entityManager.flush();
entityManager.clear();
List<Member> member = memberRepository.findLockByUsername("member1");
}
실행을 해보면
2022-07-04 11:44:22.562 DEBUG 38193 --- [ main] org.hibernate.SQL :
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=? for update
아까와 다르게 마지막에 for update가 추가된걸 볼 수 있습니다. 이 구문이 있기 때문에 동시 다발적으로 업데이트가 될 때도 정합성이 보장됩니다.
JPA 확장 기능
사용자 정의 리포지토리 구현
스프링 데이터 JPA가 구현체를 자동으로 생성해주지만 인터페이스를 직접 구현해야 하는 경우가 생길 수 있다. 직접 구현하는 방법을 알아보자
- JPA 직접 사용
- 스프링 JDBC Template 사용
- Mybatis 사용
- 데이터베이스 커넥션 사용
- Querydsl 사용
1. 사용자 정의 인터페이스
public interface MemberRepositoryCustom {
List<Member> findMemberCustom();
}
2. 인터페이스 구현 클래스
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final EntityManager em;
@Override
public List<Member> findMemberCustom() {
return em.createQuery("select m from Member m")
.getResultList();
}
}
여기서 중요한점은 이름 규칙이다. 리포지토리 인터페이스 이름 + Impl
3. 사용자 정의 인터페이스 상속
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom { ... }
이렇게 사용 하면, 여러 인터페이스를 분리해서 사용하는 것이 가능해지고 유지보수가 더 쉬워진다.
Auditing
데이터베이스에서는 시간(생성일, 수정일)이 거의 필수적으로 들어갑니다. 즉 시간 컬럼은 공통적으로 사용한다는 말인데 JPA에서는 시간에 대해서 자동으로 값을 넣어주는 Auditing으로 처리하고 있습니다.
Auditing을 사용하기 위해서는 설정이 먼저 필요합니다.
1. 설정
- @EnableJpaAuditing → 스프링 부트 클래스에 적용
- @EntityListeners(AuditingEntityListener.class) → 엔티티에 적용
2. 적용
public class BaseTimeEntity {
// 데이터 생성 날짜 자동 저장 어노테이션
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
// 데이터 수정 날짜 자동 저장 어노테이션
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
public class BaseEntity extends BaseTimeEntity {
// 데이터 생성자 자동 저장 어노테이션
@CreatedBy
@Column(updatable = false)
private String createdBy;
// 데이터 수정자 자동 저장 어노테이션
@LastModifiedBy
private String lastModifiedBy;
}
- 저장시점에 등록일, 등록자는 물론이고, 수정일, 수정자도 같은 데이터가 저장된다. 데이터가 중복 저장되는 것 같지만, 이렇게 해두면 변경 컬럼만 확인해도 마지막에 업데이트한 유저를 확인할 수 있으므로 유지보수 관점에서 편리하다.
이렇게 스프링 데이터 JPA에 대해 알아봤는데 아직 어디에서 어떻게 써야 효율적인지 모르는 부분이 많습니다. 그래서 토이 프로젝트를 하나 만들고 스프링 데이터 JPA를 적용시킴으로써 효울적으로 다룰 수 있게 노력해보겠습니다.
reference
https://reiphiel.tistory.com/entry/understanding-jpa-lock
'JPA' 카테고리의 다른 글
스프링 데이터 JPA - 다양한 기능들 (0) | 2022.06.26 |
---|---|
스프링 데이터 JPA - 다양한 쿼리 기능 (0) | 2022.06.21 |
JPA 영속성 컨텍스트란? (0) | 2022.06.15 |
JPA를 사용해야 하는 이유 (0) | 2022.06.14 |