Spring Data JPA Paging

Spring Data JPA 를 이용해서 paging 처리를 할 때,
부가적인 count query 를 발생시키지 않는 방법을 정리한다.

Setup

아래와 같이 의존성으로, Spring Data JPA 와 H2 를 추가하자.
테스트를 위해 Embedded DB 로 H2 를 사용한다.

Entity 로 Book 을 정의하자.

1
2
3
4
5
6
7
8
@Entity
class Book(

@Id @GeneratedValue
val id: Long? = null,

val name: String
)

그리고, BookRepository 를 정의하자.
Paging 을 위해, PagingAndSortingRepository interface 를 상속한다.

1
interface BookRepository : PagingAndSortingRepository<Book, Long>

Test

JPA 관련 테스트이기 때문에, @DataJpaTest 를 활용해보자.

Read more

Spring Data JPA CRUD

Spring Data JPA 를 이용해서, repository 의 CRUD 를 직접 테스트한다.

Requirements

의존성을 다음과 같이 추가한다.

1
2
3
4
5
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

그리고, application.yml 에 다음과 같이 정의한다.
테스트 코드 실행시 수행된 SQL 을 확인하기 위함이다.

1
2
3
spring:
jpa:
show-sql: true

도서를 “생성/조회/수정/삭제” 하는 기능을 만들기 위해, Book 클래스를 정의하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Book {

@Id
@GeneratedValue
private Long id;

private String name;

public Book() {
}

public Book(String name) {
this.name = name;
}
}

그리고 repository interface 를 정의하자.

1
2
3
public interface BookRepository extends JpaRepository<Book, Long> {

}
Read more

[자바 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

[자바 ORM 표준 JPA 프로그래밍] 4장_앤티티 매핑

엔티티와 테이블을 정확히 매핑하기 위해 JPA 는 다양한 어노테이션을 지원한다.

  1. 객체와 테이블 매핑 : @Entity, @Table
  2. 기본 키 매핑 :@Id
  3. 필드와 컬럼 매핑 : @Column

@Entity

JPA 를 사용해서 테이블을 매핑할 클래스는 @Entity 를 필수로 붙여야한다.
그리고, @Entity 적용시 기본 생성자는 필수이다.

@Table

엔티티와 매핑할 테이블을 지정한다.

데이터베이스 스키마 자동 생성

JPA 는 클래스의 매핑 정보와 Database Dialect 을 사용해서 데이터베이스 스키마를 생성한다.
create / create-drop / update / validate / none 옵션이 있다.
다음과 같이 설정하면 기존 테이블은 삭제하고 새로 생성한다. ( DROP + CREATE )

1
spring.jpa.hibernate.ddl-auto=create

Primary Key 매핑

Read more

[자바 ORM 표준 JPA 프로그래밍] 3장_영속성 관리

Entity 를 Entity Manager 를 통해 어떻게 사용하는지 정리하자.

Entity Manager Factory, Entity Manager

엔티티 메니저 펙토리는 한 개만 만들어서 애플리케이션 전체에서 공유한다.
엔티티 메니저 펙토리는 서도 다른 스레드 간에 공유해도 되지만
엔티티 메니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안된다.

Persistence Context

엔티티를 저장하는 환경이다.
엔티티 메니저로 엔티티를 저장하거나 조회하면 엔티티 메니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

엔티티의 생명주기

네 가지 상태가 있다.

  1. 비영속
    영속성 컨텍스트와 관계 없는 상태
  2. 영속
    영속성 컨텍스트에 저장된 상태
  3. 준영속
    영속성 컨텍스트에 저장되었다가 분리된 상태
  4. 삭제
    삭제된 상태

영속성 컨텍스트의 특징

  1. 영속 상태는 식별자 값이 반드시 있어야한다.
  2. 보통 Transaction 을 Commit 하는 순간 영속성 컨텍스트에 저장된 엔티티를 데이터베이스에 반영한다. (Flush)
  3. 1차 캐쉬 / 동일성 보장 / 쓰기 지연 / 변경 감지 / 지연 로딩
Read more

패러다임의 불일치

객체 모델과 관계형 데이터베이스 모델은 지향하는 페러다임이 서로 다르다.
페러다임의 불일치 문제를 해결하기 위한 결과물이 ‘JPA’ 이다.
객체 모델과 관계형 데이터베이스 모델의 페러다임 차이를 비교해보자.

Granularity (밀도)

  • 객체
    • 다양한 크기의 객체
    • 커스텀한 타입
  • 릴레이션
    • 테이블
    • 기본 데이터 타입 (UDT는 비추)

Subtype

  • 객체
    • 상속 구조
    • 다형성
  • 릴레이션
    • 테이블 상속 無 (상속 기능을 구현했다 하더라도 표준 기술이 아닙니다.)
    • 다형적인 관계 無

Identity

  • 객체
    • 레퍼런스 동일성 (==)
    • 인스턴스 동일성 (equals() 메소드)
  • 릴레이션
    • 주키 (primary key)

Association

  • 객체
    • 객체 레퍼런스로 관계 표현
    • 방향이 존재
    • 다대다 관계 가능
  • 릴레이션
    • Foreign Key 로 관계 표현
    • 방향이라는 의미가 無
    • 다대다 관계 불가능. (조인 테이블 또는 링크 테이블을 사용해서 두개의 1대다 관계로 풀어야 )

Data Navigation

Read more