JPA(Java Persistence API)는 JAVA에서 사용하는 ORM기술의 표준 사양 입니다.
JPA표준 사양을 구현한 구현체로는 Hibernate ORM
, EclipseLink
, DataNucleus
등이 있습니다. 보통 JAP의 API에 구현체 자신만의 API를 추가적으로 구현되어 있습니다.
Persistence
는 영속성, 지속성이라는 뜻으 가지고 있습니다. 즉, 무언가를 금방 사리지지 않게 하고 오래 지속되가한다
가 Persistence
의 목적입니다.
JPA에서는 테이블과 캐핑되는 엔티티 객체 정보를 영속성 컨텍스트(Persistence Context)
에 보관해서 애플리케이션 실행중에 장기간 보관할 수있도록 합니다.
영속성 컨텍스트는 1차 캐시
와 쓰기 지연 SQL 저장소
로 구성되어 있습니다.
ORM을 이용하여 JPA API를 호출하여 엔티티 객체를 저장하면 1차 캐시에 저장되고 쓰기 지연 SQL 저장소
에 SQL문이 따로 저장되어 관리되게 됩니다.
Spring Data JPA
를 사용하기 위해서는 build.gradle
에 먼저 외부 의존 라이브러리를 추가해야 합니다.
dependencies{
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
...
}
이전에 Spring Data JDBC
와 마찬가지로 인메모리 DB
인 h2
를 이용하여 서버 실행마다 손쉽게 데이터베이스를 세팅할 수 있도록 application.properties
를 application.yml
로 확장자를 변경합니다.
spring:
h2:
consle:
enabled: true
path: /h2
datasource:
url: jdbc:h2:mem:test
jpa:
hibernate:
ddl-auto: create
show-sql: true
ddl-auto: create
: 스키마를 직접 지정할 필요 없이 JPA가 자동으로 데이터베이스에 테이블을 생성해 줍니다.show-sql: true
: SQL쿼리를 실행창에 출력합니다.@Configuration
public class JpaBasicConfig{
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory){
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
//실행 코드
};
}
}
@Configuration
: Spring에서 설정 정보 클래스로 등록합니다. 설정정보 클래스 내의 @Bean
애너테이션이 붙은 메서드를 실행하여 스프링 컨테이너에 빈 객체를 추가하고, 빈 객체간 의존 주입하는등 빈 객체를 관리 할 수 있도록 정보를 제공합니다.EntityManager
: 영속성 컨텍스트를 내장하고 있어 엔티티 클래스들을 영속성 컨텍스트에 저장하여 엔티티 클래스들을 관리하는 클래스입니다.EntityManagerFactory
: 엔티티 메니저를 여러 스레드가 동시에 접근하지 못하게 스레드마다 엔티티르 생성하여 전달할 수 있도록 엔티티를 만드는 역할을 수행합니다.CommandLineRunner
: 서버 구동 시점에 초기화작업으로 어떤일을 할것인지 정의하는 인터페이스 입니다.@Configuration
애너테이션을 이요하여 설정 정보 클래스를 만들고 서버 구동시 초기화 작업으로 수행할 일들을 지정함으로써 엔티티와 테이블 매핑을 확인할 수 있습니다.
@Getter
@Setter
@NoArgsConstructor
@Entity
public class 엔티티{
@Id
@GeneratedValue(GerationType.IDENTITY)
private long 엔티티Id;
privaet String email;
}
@Entity
: 해당 클래스가 엔티티 클래스임을 알리는 에너테이션입니다.@Id
: 테이블의 기본키에 속하는 필드와 매칭됩니다.@GeneratedValue(GerationType.IDENTITY)
: SQL의 AUTO_INCREMENT와 동일합니다. 값 생성을 데이터베이스에 위임합니다.@Entity
와 @Id
에너테이션을 추가하면 JPA에서 해당 클래스를 엔티티 클래스로 인식합니다.
@Configuration
public class 설정정보클래스{
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommaindLineRunner 러너(EntityManagerFactory emFactory){
this. em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
엔티티 엔티티 = new 엔티티(매개변수);
tx.begin();
em.persist(엔티티);
엔티티 엔티티 = em.find(엔티티.class,@Id값);
엔티티.set필드(새로운값);
em.remove(엔티티);
tx.commit();
};
}
}
EntityTransaction
: 영속성 컨테이너 내에서 트랜젝션을 관리하는 클래스입니다.tx.begin();
: 트랜잭션 단위를 시작합니다.tx.commit();
: 트랜잭션 단위르 종료하고 쓰기 지연 SQL 저장소
에 있는 SQL문을 실행후 삭제합니다. 내부적으로 em.flush()
메서드가 호출됩니다.em.persist(엔티티)
: 엔티티를 영속성 컨텍스트내의 1차 캐시
에 저장하고 해당하는 SQL문을 쓰기 지연 SQL 저장소
에 저장합니다. 쓰기 지연 SQL 저장소
에 INSERT
문이 저장됩니다.em.find(엔티티.class,@Id값);
: 엔티티 클래스에 해당하는 엔티티 객체들중 기본키 값(아이디 값)에 해당하는 엔티티 객체를 반환합니다. 쓰기 지연 SQL 저장소
에 SELECT
문이 저장됩니다.엔티티.set필드(새로운값);
: 1차 캐시
내에 있는 객체의 해당 필드를 수정합니다. setter만으로도 업데이트 로직이 완성됩니다. 쓰기 지연 SQL 저장소
에 UPDATE
문이 저장됩니다.em.remove(엔티티);
: 1차 캐시
내에 존재하는 엔티티 객체 제거를 요청합니다. 쓰기 지연 SQL 저장소
에 DELETE
문이 저장됩니다.@Entity(name = "USERS")
@Table(name = "USERS")
public class 엔티티{
@Id
private Long 필드;
}
@Entity
해당 클래스가 엔티티 클래스임을 JPA에게 알리는 역할을 합니다.name
속성은 1차 캐시에 해당 이름으로 엔티티 클래스를 저장할 것임을 명시합니다.@Table(name = "USERS")
해당 엔티티 클래스와 매핑될 데이터베이스의 테이블 명을 명시합니다.name
속성 생략시 엔티티 클래스와 동일한 테이블과 매핑됩니다.@Id
: 기본키와 매핑되는 필드를 지정합니다.@Entity
에너테이션과 @Id
에너테이션은 엔티티 클래스를 지정하는데 필수적이며 생략시 에러를 발생시킵니다. 반면에 @Table
에너테이션은 생략이 가능합니다.
JPA에서 @Id
에너테이션으로 지정한 필드가 테이블에서 기본키 컬럼이 되는데 해당 키 값을 어떻게 지정할 것인가는 여러 가지 전략이 존재합니다.
전략 | 설명 |
---|---|
직접 할당 | 엔티티 객체에 직접 값을 할당하는 방식입니다. |
IDENTITY | 기본키 생성을 데이터베이스에 위임하고, AUTO_INCREMENT처럼 숫자를 +1씩 증가시키는 방식입니다. |
SEQUENCE | 데이터베이스의 시퀀스를 사용해서 기본키를 생성합니다. |
TABLE | 별도의 키 생성을 위한 테이블을 사용합니다. |
@NoArgsConstructor
@Getter
@Entity
public class 엔티티{
@Id
private Long 필드;
public 엔티티(Long 필드){
this.필드 = 필드;
}
}
엔티티 클래스
@Configuration
public class 설정정보클래스{
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner 러너(EntityManagerFactory emFactory){
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
tx.begin();
em.persist(new 엔티티(1L)); //직접 할당
tx.commit();
};
}
}
설정정보 클래스
엔티티 객체를 생성하면서 인자로 필드의 값을 직접 넣어줌으로써 값을 할당하고 있습니다.
@NoArgsConstructor
@Getter
@Entity
public class 엔티티{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long 필드;
public 엔티티(Long 필드){
this.필드 = 필드;
}
}
엔티티 클래스
@Configuration
public class 설정정보클래스{
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner 러너(EntityManagerFactory emFactory){
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
tx.begin();
em.persist(new 엔티티()); //IDENTITY : 1 -> 2 -> 3 -> 4 -> ...
tx.commit();
};
}
}
IDENTITY 전략이 적용되었기 때문에 별도의 기본 키 값을 할당 하지 않아도 자동으로 +1 씩해가며 할당됩니다.
@NoArgsConstructor
@Getter
@Entity
public class 엔티티{
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long 필드;
public 엔티티(Long 필드){
this.필드 = 필드;
}
}
IDENTITY
전략과 마찬가지로 SEQUENCE
저략도 엔티티 객체 생성시 별도의 기본 키 값을 할당하지 않아도 데이터베이스가 자동으로 값을 +1 씩 증가시켜가며 할당합니다.
차이점이라면 시퀀스에서 기본키를 제공
을 하냐 테이블 자체 제공
을 하냐 차이 입니다.
@GeneratedValue(strategy=GenerationType.AUTO)
JPA가 데이터베이스의 Dialect에 따라 적절한 전략을 자동으로 선택합니다. 표준으로 정해진 것이 아니라 데이터베이스에 따라 다릅니다.
엔티티 클래스와 테이블을 매핑할 수 있었다면 클래스의 멤버변수, 테이블의 속성들을 서로 매핑할 수 있을 것입니다.
엔티티 클래스의 카멜 표기법
-> 테이블의 언더바 표기법
으로 이름으로 매핑이 가능합니다.
SQL의 DDL
에서 알 수 있듯이 테이블의 속성을 정의 할 때 여러 옵션들으 줄수 있습니다. NULL이 가능한지 수정 가능한지, 후보키인지 등 자세한 속성들을 정의할 수 있습니다.
Spring Data JPA는 애너테이션으로 다양한 옵션들을 제공합니다.
@Column
: 필드멤버가 매핑되는 속성에 어떤 옵션을 줄지 에너테이션 내 에트리뷰트로 줄 수 있습니다.@Transient
: 테이블의 컬럼과 매핑되지 않는 필드를 나타냅니다.@Enumerated
: 필드로 열거형인 경우 값을 어떻게 저장할 지 선택 합니다.@Column | 에트리뷰트 |
---|---|
nullable | false : 빈 값으로 입력이 불가능 합니다. true (디폴트) : 빈값으로 입력이 가능합니다. |
updatable | false : 사용 중 수정이 불가능 합니다. true (디폴트) : 사용 중 수정이 가능합니다. |
unique | false (디폴트) : 후보키가 아닙니다. true : 후보키 입니다. |
length | (디폴트:255) 문자의 길이를 지정할 수 있습니다. |
name | 매핑되는 테이블의 컬럼명을 필드명이 아닌 임의로 지정할 수 있습니다. |
@Enumerated | 에트리뷰트 |
---|---|
EnumType.ORDINAL | enum의 순서를 나타내는 숫자를 테이블에 저장합니다. |
EnumType.STRING | enum의 이름을 테이블에 저장합니다. |
enum에 새로운 상수가 추가된다면 새롭게 추가된 위치의 다음 상수들의 순서가 바뀌게 되므로 문제가 발생할 수 있습니다. 따라서 EnumType.ORDINAL
보단 EnumType.STRING
의 권장됩니다.
드디어 JPA를 처음 배웠습니다. 이전에 Spring Data JDBC를 미리 학습해서 그런지 새로운 에너테이션과 인터페이스는 낯설었지만 ORM과 매핑등은 어렵지 않게 이해가 되었습니다.
private!