java ORM JPA (java persistence api) programming
2. JPA 시작
2.4 객체 매핑 시작
Member.java
package com.github.moregorenine.ch02_jpa_start1;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "MEMBER")
@Data
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String username;
//매핑정보 없을 경우 필드명을 사용해서 컬럼명으로 자동 매핑합니다.
private Integer age;
}
2.5 persistence.xml 설정
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
<persistence-unit name="jpa-programming">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value="123"/>
<!-- <property name="javax.persistence.jdbc.url" value="jdbc:h2:./database/test"/>-->
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.id.new_generator_mappings" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>
속성 | 설명 |
---|---|
hibernate.dialect | DB 방언, SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능 |
hibernate.show_sql | hibernate sql 출력 |
hibernate.format_sql | hibernate sql 정렬 |
hibernate.use_sql_comments | hibernate 주석 출력 |
hibernate.id.new_generator_mappings | JPA 표준에 맞춘 새로운 키 생성 전략을 사용한다. |
2.6 애플리게이션 개발
JpaMain.java
package com.github.moregorenine.ch02_jpa_start1;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
//엔티티 매니저 팩토리 생성
//애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 합니다.
//persistence.xml에서 이름이 'jpa-programming'인 persistence-unit을 찾아서 엔티티 매니저 팩토리를 생성합니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-programming");
//엔티티 매니저 생성
//엔티티를 DB에 CRUD 할 수 있습니다.
//DB connection과 밀접한 관련이 있으므로 스레드간에 공유하거나 재사용하면 안됩니다.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
emf.close(); //엔티티 매니저 팩토리 종료
}
}
public static void logic(EntityManager em) {
String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("moregorenine");
member.setAge(2);
//등록
//1차 캐시에 저장됨
em.persist(member);
//수정
member.setAge(20);
//한 건 조회
//1차 캐시에서 조회
//만약 1차 캐시에 없으면 엔티티 매니저는 DB에서 조회해서 엔티티를 생성합니다.
//그리고 1차 캐시에 저장한 후에 연속 상태의 엔티티를 반환합니다.
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
//목록 조회
//JPA는 SQL을 추상화한 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공합니다.
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
//삭제
em.remove(member);
}
}
3. 영속성 관리
3.1 엔티티 매니저 팩토리 & 엔티티 메니저
JpaMain.java
//공장 만들기, 비용이 아주 많이 듭니다.
//따라서 한 개만 만들어서 애플리케이션 전체에서 공유하도록 설계되어야 합니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-programming");
//공장에서 엔티티 매니저 생성, 비용이 거의 안 듭니다.
//엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유해도 되지만,
//엔티티 매니저는 여러 스레드 동시 접근시 동시성 문제가 발생하므로 스레드간 공유 금합니다.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
3.2 영속성 컨텍스트란?
JPA를 이해하는 데 가장 중요하 용어는 영속성 컨텍스트(persistence context)
입니다.
해석하자면 엔티티를 영구 저장하는 환경
입니다.
엔티티 매니저로 엔티티를 저장
하거나 조회
하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리합니다.
em.persist(member);
persist()
메소드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장합니다.
영속성 컨텍스트는 논리적인 개념에 가깝고 눈에 보이지도 않습니다. 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어 집니다.
그리고 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있습니다.
여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수도 있습니다. 이런 복잡한 상황은 11장을 참조하세요.
3.3 엔티티의 생명주기
상태 | 설명 |
---|---|
비영속(new/transient) | 영속성 컨텍스트와 전혀 관계과 없는 상태 |
영속(managed) | 영속성 컨텍스트에 저장된 상태 |
준영속(detached) | 영속성 컨텍스트에 저장되었다가 분리된 상태 |
삭제(removed) | 삭제된 상태 |
비영속(new/transient)
엔티티 객체를 생성. 순수한 객체 상태이며 아직 저장하지 않았습니다.
따라서 영속성 컨텍스트나 데이터베이스와 전혀 관련이 없습니다.
Member member = new Member();
member.setId(id);
member.setUsername("moregorenine");
member.setAge(2);
영속(managed)
엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장하였습니다.
이렇게 연속성 컨텍스트가 관리하는 엔티티
를 영속 상태라고 합니다.
em.persist(member);
이제 member
엔티티는 비영속 상태에서 영속 상태가 되었습니다. 결국 영속 상태라는 것은
영속성 컨텍스트에 의해 관리된다
는 뜻입니다.
그리고 em.find()나 JPQL을 사용해서 조회한 엔티티
도 영속성 컨텍스트가 관리하는 영속 상태입니다.
준영속(detached)
영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 됩니다.
특정 엔티티를 준영속 상태로 만들려면 em.detach()
를 호출하면 됩니다.
em.close()
를 호출해서 영속성 컨텍스트를 닫거나 em.clear()
를 호출해서 영속성 컨텍스트를 초기화해도
영속성 컨텍스트가 관리하던 영속 상태의 엔티티는 준영속 상태가 됩니다.
em.detach(member);
em.close();
em.clear();
삭제(removed)
엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제합니다.
em.remove(member);
3.4 영속성 컨텍스트의 특징
영속성 컨텍스트와 식별자 값
영속성 컨텍스트는 엔티티를 식별자(@Id로 테이블의 기본키와 매핑한 값)으로 구분합니다.
영속 상태는 식별자 값이 반드시 있어야 하며, 식별자 값이 없으면 예외가 발생합니다.
영속성 컨텍스트와 데이터베이스 저장
영속성 컨텍스트에 엔티티를 저장하면 이 엔티티는 언제 데이터베이스에 저장될까요?
JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데 이를 flush
라 합니다.
영속성 컨텍스트가 엔티티를 관리시 장점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
3.5 flush
flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다.
- 변경 감지기가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾습니다. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록합니다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.(등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 flush하는 방법 3가지
- em.flush()를 직접 호출
- tx.commit() 시 flush가 자동 호출됩니다.
- JPQL 쿼리 실행 시 flush가 자동 호출됩니다.
- JPQL 실행 전에 영속성 컨텍스트를 flush해서 변경 내용을 데이터베이스에 반영을 해야 JPQL에서 반영된 결과를 가져올 수 있기 때문입니다.
3.6 준영속
준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없습니다. 준영속 상태로 만드는 방법 3가지
- em.detach(entity) 특정 엔티티만 준영속 상태로 전환합니다.
- em.clear() 영속성 컨텍스트를 초기화 합니다.
- em.close() 영속성 컨텍스트를 종료 합니다.
4. 엔티티 매핑
어노테이션 | 기능 |
---|---|
@Entity, @Table | 객체와 테이블 매핑 |
@Id | 기본키 매핑 |
@Column | 필드와 컬럼 매핑 |
@ManyToOne @JoinColumn | 연관관계 매핑 |
4.1 @Entity
주의 사항
- 기본 생성자는 필수입니다.(파라미터가 없는 public 또는 protected 생성자)
- fianl, enum, interface, inner 클래스에는 사용할 수 없습니다.
- 저장할 필드에 final을 사용하면 안 됩니다.
4.2 @Table
@Table은 @Entity와 매핑할 테이블을 지정합니다. 생략시 매핑한 엔티티 이름을 테이블 이름으로 사용합니다.
4.3 다양한 매핑 사용
package com.github.moregorenine.ch04_jpa_start2;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "MEMBER_CH04",
uniqueConstraints = {
@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"})
})
@Data
public class MemberCh04 {
@Id
private String id;
@Column(name = "NAME", nullable = false, length = 10)
private String username;
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType; //...1️⃣
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate; //...2️⃣
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate; //...2️⃣
@Lob
private String description; //...3️⃣
@Transient
private String temp;
}
1️⃣ roleType : 자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야 합니다. 속성 값에 대한 자세한 내용은 4.7.2절을 참고하세요.
2️⃣ createdDate, lastModifiedDate : 자바의 날짜 타입은 @Temporal을 사용해서 매핑합니다. 속성에 값에 대한 자세한 내용은 4.7.3절을 참고하세요.
3️⃣ description : 회원 설명 필드는 길이 제한이 없습니다. 따라서 데이터베이스의 CLOB 타입으로 저장해야 합니다. @Lob을 사용하면 CLOB, BLOB 타입을 매핑할 수 있습니다. 자세한 내용은 4.7.4절을 참고하세요.
4.4 데이터베이스 스키마 자동 생성
<property name="hibernate.hbm2ddl.auto" value="create" />
이 속성을 추가하면 애플리케이션 실행 시전에 데이터베이스 테이블을 자동으로 생성합니다.
옵션 | 설명 |
---|---|
create | 기존 테이블을 삭제하고 새로 생성한다. DROP + CREATE |
create-drop | 애플리케이션을 종료할 때 생성한 DDL을 제거합니다. DROP + CREATE + DROP |
update | 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 변경 사항만 수정합니다. |
validate | 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않습니다. 이 설정은 DDL을 수정하지 않습니다. |
none | 스키마 자동 생성 기능을 사용하지 않으려면 hibernate.hbm2ddl.auto 속성 자체를 삭제하거나 유효하지 않은 옵션 값을 주면 됩니다.(none)은 유효하지 않은 옵션 값입니다. |
4.5 DDL(Data Definition Language) 생성 기능
@Entity
@Table(name = "MEMBER_CH04",
uniqueConstraints = {
@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"})
})
@Data
public class MemberCh04 {
@Id
private String id;
@Column(name = "NAME", nullable = false, length = 10)
private String username;
private Integer age;
@Table의 uniqueConstraints 속성이나 @Column의 nullable, length 속성을 포함해서 이런 기능들은 단지 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않습니다.
4.6 기본 키 매핑
5. 연관관계 매핑 기초
default fetch value
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
방향 : 단방향
, 양방향
이 있다. 예를 들어 회원
과 팀
이 관계가 있을 때 회원
➡️팀
, 회원
⬅️팀
둘 중 한 쪽만 참조하는 것을 단방향 관계라하고, 팀
↔️회원
서로 참조하는 것을 양방향 관계라 합니다. 방향은 객체관계에만 존재하고 테이블 관계는 항상 양방향 입니다.
다중성 : n:1
, 1:n
, 1:1
, n:m
이 있다. 예를 들어 여러 회원
은 한 팀
에 속하므로 회원
과 팀
은 n:1(다대일)
관계입니다. 반대로 팀
과 회원
은 1:n(일대다)
관계입니다.
연관관계의 주인 : 객체를 양방향 연관관계로 만들 경우 연관관계의 주인을 정해야 합니다.
5.1. 단방향 연관관계
유용한 skill
생성, 수정 관련 정보
@CreatedBy
@ManyToOne
@JoinColumn(name = "createdUser", updatable = false)
private Account createdUser;
@LastModifiedBy
@ManyToOne
@JoinColumn(name = "updateUser")
private Account updateUser;
@CreatedDate
@Column(name = "createdDate", updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Column(name = "updateDate")
private LocalDateTime updateDate;
max id 가져오기
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
@Query("select coalesce(max(a.id), 1) from Account a")
Long getMaxId();
}
댓글남기기