[자바 ORM 표준 JPA 프로그래밍] 16장_트랜잭션과 락,2차 캐시

Transaction

트랜잭션이란 논리적인 작업의 단위이다. 이 트랜잭션은 ACID 를 보장해야한다.

  1. Atomicity
    트랜잭션 내에서 실행한 작업들은 하나의 작업 처럼, 모두 성공하거나 모두 실패해야한다.
  2. Consistency
    트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야한다.
    예를 들면, 데이터베이스의 무결성 제약 조건을 항상 만족해야한다.
  3. Isolation
    동시에 실행되는 트랜잭션은 서로 영향을 미치지 않아야한다.
  4. Durability
    트랜잭션을 성공적으로 끝내면, 그 결과가 데이터베이스에 항상 기록되어야한다.

문제는 격리성이다. 트랜잭션간에 완벽하게 격리성을 보장하기 위해서는 어떻게 해야할까 ?
트랜잭션을 차례대로 실행해야한다. 그러면, 동시성이 처리 기능이 떨어진다.
그래서 트랜잭션 격리 수준이 등장한다.

Isolation Level

격리 수준이 낮을 수록 더 많은 문제가 발생한다.
READ UNCOMMITTED, READ COMMITTED , REPEATABLE READ, SERIALIZABLE 으로 격리 수준이 높아진다.
애플리케이션은 대부분 동시성 처리가 중요하기 때문에, 데이터베이스들은 보통 READ COMMITTED 격리 수준이 기본이다.

READ UNCOMMITTED

커밋하지 않은 데이터를 읽을 수 있다. DIRTY READ 문제가 발생할 수 있다.

  1. 트랜잭션 1 이 데이터를 수정하고 있다.
  2. 트랜잭션 2 가 수정 중인 데이터를 조회한다.
  3. 트랜잭션 1 이 롤백을 하게 되면 데이터 정합성에 문제가 생긴다.

READ COMMITTED

Read more

[자바 ORM 표준 JPA 프로그래밍] 15장_고급 주제와 성능 최적화

JPA 를 사용하며 주의할 점과, 성능 최적화 내용을 정리한다.

트랜잭션 롤백

트랜잭션이 롤백된 영속성 컨텍스트를 그대로 사용하는 것은 위험하다. 다음 경우를 보자.

  1. 엔티티를 조회해서 수정하는 중에 문제가 발생했다.
  2. 트랜잭션이 롤백된다.
  3. 데이터베이스의 데이터는 원래대로 복구된다.
  4. 수정된 객체는 영속성 컨텍스트에 그대로 남아있다.

그럼 어떻게 해야할까 ?

  1. 영속성 컨텍스트를 새로 생성해서 사용하거나
  2. EntityManager.clear() 로 영속성 컨텍스트를 초기화해야한다.

스프링에서는,

  1. 트랜잭션당 영속성 컨텍스트의 경우 : 트랜잭션을 롤백하면서 영속성 컨텍스트도 함께 종료
  2. OSIV 인 경우 : 트랜잭션을 롤백하면서 영속성 컨텍스트를 초기화

엔티티 비교

영속성 컨텍스트가 같을 때는, 다음 세 조건을 만족한다.

Read more

[자바 ORM 표준 JPA 프로그래밍] 13장_웹 어플리케이션과 영속성 관리

컨테이너 환경에서 JPA 가 동작하는 내부 동작 방식을 이해하고, 문제점과 해결방안을 정리하자.

스프링 컨테이너의 기본 전략

스프링 컨테이너는 트렌젝션 범위의 영속성 컨텐스트 전략을 기본으로 한다.
즉, 트렌젝션을 시작할 때 영속성 컨텍스트를 생성하고 끝날 때 영속성 컨텍스트를 종료한다.
그리고, 같은 트렌젝션 안에서는 항상 같은 영속성 컨텍스트에 접근힌디.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Controller
class HelloController{

@Autowired HelloService helloService;

public void hello(){
//반환된 member 엔티티는 준영속 상태
Member member = helloService.logic();
}
}

@Service
class HelloService{

// 엔티티 메니저 주입
@PersistenceContext
EntityManager em;

@Autowired Repository1 repository1;
@Autowired Repository2 repository2;

//트랜잭션 시작
@Transactional
public void logic(){
repository1.hello();

//Member 는 영속상태
Member member = repository2.findMember();

return member;
}
//트렌젝션 종료
}

@Repository
class Repository1 {

@PersistenceContext
EntityManager em;

public void hello(){
em.xxx(); //영속성 컨텍스트 접근
}
}

@Repository
class Repository2 {

@PersistenceContext
EntityManager em;

public Member findMember() {
return em.find(Member.class, "id1"); //영속성 컨텍스트 접근
}
}

준영속 상태와 지연 로딩

조회한 엔티티가 서비스와 리포지토리 계층에서는 영속성 컨텍스트에 관리되면서 영속 상태를 유지하지만,
컨트롤러나 뷰 같은 프리젠테이션 계층에서는 준영속 상태가 된다.
따라서, 변경감지와 지연로딩이 동작하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Order{
@Id @GeneratedValue
private Long id;

@ManyToOne(fetch = FetchType.LAZY) // 지연로딩
private Member member; // 주문 회원
}

class OrderController {
public String view(Long orderId){
Order order = orderService.findOne(orderId);
Member member = order.getMember();
member.getName(); // 지연로딩 시 예외 발생
}
}

변경감지 기능이 프리젠테이션 계층에서 동작하지 않는 것은 문제가 되지 않는다.
변경 감지 기능이 프리젠테이션 계층에서도 동작하면 애플리케이션 계층이 가지는 책임이 모호해지고, 데이터를 어디서 어떻게 변경했는지 프리젠테이션 계층까지 다 찾아야 하므로 유지보수하기 어렵다.
비즈니스 로직은 서비스 계층에서 끝내야한다.

준영속 상태의 지연 로딩을 해결하는 방법은 두 가지이다.

  1. 뷰가 필요한 엔티티를 미리 로딩
  2. OSIV
Read more

[자바 ORM 표준 JPA 프로그래밍] 12장_스프링 데이터 JPA

데이터 접근 계층 (Data Access Layer) 는 CRUD 로 불리는 등록, 수정, 삭제, 조회 코드를 반복해서 개발해야 한다.
JPA 를 사용해서 데이터 접근 계층을 개발할 때도 문제가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MemberRepository{

@PersistenceContext
EntityManager em;

public void save(Member member) {...}
public Member findOne(Long id) {...}
public List<Member> findAll() {...}

public Member findByUsername(String username) {...}
}

public class ItemRepository{

@PersistenceContext
EntityManager em;

public void save(Item item) {...}
public Member findOne(Long id) {...}
public List<Member> findAll() {...}
}

위 코드를 보면, 회원 리포지토리와 상품 리포지토리가 하는 일이 비슷하다.
이 문제를 해결하려면 제네릭과 상속을 적절히 사용해서 공통 부분을 처리하는 부모 클래스를 만들면 된다. 이것을 보통 GenericDAO 라고 한다.
하지만 이것은, 공통 기능을 구현하는 부모 클래스에 종속되고 구현 클래스 상속이 가지는 단점이 있다.

스프링 데이터 JPA

스프링 데이터 JPA 는 스프링 프레임워크에서 JPA 를 편리하게 사용할수 있도록 지원하는 프로젝트이다.
이 프로젝트는 데이터 접근 계층을 개발할 때 지루하게 반복되는 CRUD 문제를 세련된 방법으로 해결한다.
데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다.

1
2
3
4
5
6
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByUsername(String username);
}

public interface ItemRepository extends JpaRepository<Item, Long> {
}

회원과 상품 리포지토리 구현체는 애플리케이션 실행 시점에 스프링 데이터 JPA 가 생성해서 주입해준다. 즉, 개발자가 직접 구현체를 개발하지 않아도 된다.
일반적인 CRUD 메소드는 JpaRepository 인터페이스가 공통으로 제공하지만,
MemberRepository.findByUsername(…) 처럼 직접 작성한 공통으로 처리할 수 없는 메소드는 스프링 데이터 JPA 가 메소드 이름을 분석해서 JPQL 을 실행한다.

스프링 데이터 프로젝트

스프링 데이터 JPA 프로젝트는 JPA 에 특화된 기능을 제공한다.

Read more

[자바 ORM 표준 JPA 프로그래밍] 8장_프록시와 연관관계 정리

다음 내용들을 정리한다.

  1. 프록시
  2. 즉시로딩, 지연 로딩
  3. 영속성 전이
  4. 고아 객체

프록시

아래 내용을 보기 전에, 우선 프록시에 대한 개념을 파악해야한다.
여기를 참고하자 : https://junhee-ko.github.io/2021/04/17/proxy-pattern/

이제, 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
public class Member {
private String username;
@ManyToOne
private Team team;

// ...
}

@Entity
public class Team {
private String name;

// ...
}

public String printUserBy(String memberId){
Member member = em.find(Member.class, memberId);
System.out.println(member.getUsername());
}

printUserBy 메서드에서, 멤버를 조회할 때 연관된 팀 엔티티까지 DB 에서 같 조회하는게 효율적일까 ?
팀 엔티티를 실제 사용하는 시점에 조회하는게 효율적이다. 이것을 지연로딩이라고 한다.

엔티티를 실제 사용하는 시점까지 데이터베이스 조회를 미루고 싶으면 EntityManager.getReference() 를 사용하면 된다. 이 메서드는, 실제 엔티티 객체를 생성하지 않고 프록시 객체를 반환한다.

프록시 특징

Read more