java ORM JPA (java persistence api) programming

6 분 소요

2. JPA 시작

github 예제코드

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는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다.

  1. 변경 감지기가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾습니다. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록합니다.
  2. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.(등록, 수정, 삭제 쿼리)

영속성 컨텍스트를 flush하는 방법 3가지

  1. em.flush()를 직접 호출
  2. tx.commit() 시 flush가 자동 호출됩니다.
  3. JPQL 쿼리 실행 시 flush가 자동 호출됩니다.
    • JPQL 실행 전에 영속성 컨텍스트를 flush해서 변경 내용을 데이터베이스에 반영을 해야 JPQL에서 반영된 결과를 가져올 수 있기 때문입니다.

3.6 준영속

준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없습니다. 준영속 상태로 만드는 방법 3가지

  1. em.detach(entity) 특정 엔티티만 준영속 상태로 전환합니다.
  2. em.clear() 영속성 컨텍스트를 초기화 합니다.
  3. 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();
}

예제코드

github 예제코드

태그:

카테고리:

업데이트:

댓글남기기