JPA 2

j0yy00n0·2025년 4월 9일
post-thumbnail

2025.03.26 ~ 03.27

JPA

Entity class

Entity class 작성 규칙

  • 프로젝트 내에 다른 패키지에도 동일한 엔티티가 존재할 경우 식별하기 위한 name 필수 지정
  • 기본 생성자는 필수 작성
  • final 클래스, enum, interface, inner class 에서는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안된다.
  • PK가 우선시 되어 컬럼이 생기고, 일반 컬럼은 오름차순으로 생성

JPA Table 생성

  • persistence.xml 먼저 설정
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2">

    <!-- 영속성 유닛 설정 (JPA 설정 단위) -->
    <persistence-unit name="persistenceUnit이름">

        <!-- JPA로 관리할 엔티티 클래스 등록 -->
        <class>엔티티패키지경로1.엔티티클래스명</class>
        <class>엔티티패키지경로2.엔티티클래스명</class>

        <properties>
            <!-- JDBC 설정 -->
            <property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/DB이름"/>
            <property name="jakarta.persistence.jdbc.user" value="DB사용자명"/>
            <property name="jakarta.persistence.jdbc.password" value="DB비밀번호"/>

            <!-- SQL 출력 설정 (디버깅용) -->
            <!-- 실행되는 SQL 출력 -->
            <property name="hibernate.show_sql" value="true"/> 
            <!-- SQL 보기 좋게 정렬 -->
            <property name="hibernate.format_sql" value="true"/>    

            <!-- DDL 자동 생성 전략 (옵션: none, create, update, create-drop 등) -->
            <!-- 개발 중에는 create / 운영에서는 none 또는 validate 권장 -->
            <property name="hibernate.hbm2ddl.auto" value="옵션선택(create 등)"/>
        </properties>
    </persistence-unit>

</persistence>
  • Entity class 생성
  • 테이블 만들기 테스트
public class EntityMappingTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
@Test
public void createEntityTest() {

    <!-- given : 테스트할 엔티티 인스턴스 생성 및 값 설정 -->
    엔티티클래스명 엔티티변수명 = new 엔티티클래스명();
    엔티티변수명.set기본키필드(기본키값);
    엔티티변수명.set필드1("특정값1");
    엔티티변수명.set필드2("특정값2");
    엔티티변수명.set필드3("특정값3");

    <!-- when : 엔티티 저장 (영속성 컨텍스트 등록) -->
    entityManager.persist(엔티티변수명);

    <!-- then : DB에 저장된 데이터와 동일한지 확인 -->
    엔티티클래스명 조회할엔티티변수명 = entityManager.find(엔티티클래스명.class, 엔티티변수명.get기본키필드);
    Assertions.assertEquals(엔티티변수명.get기본키필드(), 조회할엔티티변수명.get기본키필드());
}

JPA Table 제약조건

  • persistence.xml 먼저 설정
  • Entity class 생성
@Entity(name = "엔티티_논리이름") <!-- 선택: 다른 클래스와 이름 겹칠 때 사용 -->
@Table(name = "테이블_이름")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter	 <!-- 실제 서비스에서는 Setter 사용을 제한하는 것이 일반적 (보안/불변성 등) -->
@ToString
public class 클래스명 {

    @Id
    @Column(name = "기본키_컬럼명")
    <!-- @GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가 시 사용 -->
    private 자료형 기본키필드;

    @Column(name = "컬럼명1")
    private 자료형 필드명1;

    @Column(name = "컬럼명2", length = 100)
    private String 필드2;

    @Column(name = "컬럼명3", columnDefinition = "varchar(200) default '기본값'")
    private String 필드3 = "기본값";

    <!-- null 허용 여부 (nullable = false → NOT NULL 제약) -->
    @Column(name = "컬럼명4", nullable = false)
    private 자료형 필드4;

    <!-- 숫자 precision/scale (소수 포함) -->
    @Column(name = "컬럼명5", precision = 10, scale = 2)
    private BigDecimal 필드5;

    <!-- 날짜 타입 컬럼 -->
    @Column(name = "컬럼명6")
    private LocalDate 필드6;

    <!-- 테이블 생성 시 제외 (영속성 컨텍스트에서는 존재하지만 DB에는 없음) -->
    @Transient
    private 자료형 임시필드 = "테이블에 반영되지 않음";
}
  • 컬럼에서 사용하는 속성 테스트
public class ColumnMappingTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }

    @DisplayName("컬럼 속성 테스트")
    @Test
    public void columnMappingTest() {

        <!-- given : 테스트할 엔티티 인스턴스 생성 -->
        엔티티클래스명 엔티티변수명 = new 엔티티클래스명();
        엔티티변수명.set기본키필드(특정값);
        엔티티변수명.set필드1("특정값1");
        엔티티변수명.set필드2("특정값2");
        엔티티변수명.set필드3("특정값3");
        member.setEnrollDate(LocalDate.now());

        <!-- when : 트랜잭션 시작 및 저장 -->
        EntityTransaction transaction변수명 = entityManager.getTransaction();
        transaction변수명.begin();

        try {
            entityManager.persist(엔티티변수명);
            transaction변수명.commit();
        } catch (Exception e) {
            transaction변수명.rollback();
            throw new RuntimeException(e);
        }

        <!-- then : 저장된 엔티티를 조회하여 매핑 확인 -->
        엔티티클래스명 조회된엔티티 = entityManager.find(엔티티클래스명.class, 엔티티변수명.get기본키필드());
        System.out.println("조회결과 = " + 조회된엔티티);

        assertEquals(엔티티변수명.get기본키필드(), 조회된엔티티.get기본키필드());
    }
}

@GeneratedValue

Primary key에는 @Id 어노테이션과 @GeneratedValue 어노테이션을 사용

  • @Id 어노테이션 : 엔티티 클래스에서 primary key 역할을 하는 필드를 지정할 때 사용
  • @GeneratedValue 어노테이션 : primary key 값을 자동으로 생성
  • strategy : 자동 생성 전략을 지정
    - GenerationType.IDENTITY : 기본 키 생성을 데이터베이스에 위임(MySQL의 AUTO_INCREMENT)
    - GenerationType.SEQUENCE : 데이터베이스 시퀀스 객체 사용(ORACLE의 SEQUENCE)
    - GenerationType.TABLE : 키 생성 테이블 사용
    - GenerationType.AUTO : 자동 선택 (MySQL이라면 IDENTITY, ORACLE이라면 SEQUENCE로 선택)
  • generator : strategy 값을 GenerationType.TABLE로 지정한 경우 사용되는 테이블 이름을 지정
  • initialValue : strategy 값을 GenerationType.SEQUENCE로 지정한 경우 시퀀스 초기값을 지정
  • allocationSize : strategy 값을 GenerationType.SEQUENCE로 지정한 경우 시퀀스 증가치를 지정
<!-- 1. IDENTITY 전략 (MySQL) -->
@Id
@Column(name = "기본키필드명")
@GeneratedValue(strategy = GenerationType.IDENTITY)

<!-- 2. SEQUENCE 전략 (Oracle 등 - 시퀀스 객체 사용) -->
@Id
@Column(name = "기본키필드명")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "시퀀스생성기이름") 

<!-- 3. TABLE 전략 (테이블 기반 키 생성) -->
<!-- 잘 사용하지 않음 -->
@Id
@Column(name = "기본키필드명")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "테이블생성기이름")

<!-- 4. AUTO 전략 (자동 선택) -->
@Id
@Column(name = "기본키필드명")
@GeneratedValue(strategy = GenerationType.AUTO)

@TableGenerator(
    name = "생성기이름",                   // @GeneratedValue에서 참조할 이름
    table = "키관리테이블명",              // 실제 DB에 존재하는 키 관리용 테이블 이름
    pkColumnName = "식별자키컬럼명",        // 시퀀스를 구분짓는 키 이름을 저장할 컬럼
    valueColumnName = "현재값저장컬럼명",   // 다음 값을 저장하는 컬럼
    pkColumnValue = "이엔티티를구분할값"    // 이 생성기가 사용할 고유한 키 값
)

@SequenceGenerator(
    name = "시퀀스생성기이름",          // @GeneratedValue에서 참조할 이름
    sequenceName = "DB시퀀스객체이름",  // DB에 있는 시퀀스 이름
    initialValue = 시작값              // 시퀀스 시작 번호 (예: 100)
)

@SequenceGenerator(
    name = "시퀀스생성기이름",          // @GeneratedValue에서 참조할 이름
    sequenceName = "DB시퀀스객체이름",  // DB에 있는 시퀀스 이름
    initialValue = 시작값,             // 초기값 (예: 1)
    allocationSize = 증가단위          // 값이 증가하는 간격 (예: 1, 10, 50)
)
@DisplayName("식별자 매핑 테스트")
@Test
public void identifierMappingTest() {

    <!-- given : 엔티티 인스턴스 여러 개 생성 -->
    엔티티클래스명 엔티티1 = new 엔티티클래스명();
    엔티티1.set필드1("특정값1");
    엔티티1.set필드2("특정값2");

    엔티티클래스명 엔티티2 = new 엔티티클래스명();
    엔티티2.set필드1("특정값3");
    엔티티2.set필드2("특정값4");

    <!-- when : 트랜잭션 시작 후 영속화 -->
    EntityTransaction transaction변수명 = entityManager.getTransaction();
    transaction변수명.begin();

    try {
        entityManager.persist(엔티티1);
        entityManager.persist(엔티티2);
        transaction변수명.commit();
    } catch (Exception e) {
        transaction변수명.rollback();
        throw new RuntimeException();
    }

    <!-- then : JPQL, 쿼리문과 결과의 자료형을 전달 -->
    <!-- 테이블명이 아닌 엔티티를 기준, 별칭은 필수 -->
    String jpql = "select e.식별자필드명 from 엔티티이름 e";
    <!-- 식별자료형은 조회 시 데이터 자료형 -->
    List<식별자자료형> 식별자목록변수명 = entityManager.createQuery(jpql, 식별자자료형.class).getResultList();

    for (식별자자료형 식별자변수명 : 식별자목록변수명) {
        System.out.println(식별자변수명);
    }
}

enumtype

@Enumerated 어노테이션은 Enum 타입 매핑을 위해 사용

  • EnumType.ORDINAL : Enum 타입을 순서로 매핑한다.
  • EnumType.STRING : Enum 타입을 문자열로 매핑한다.
  • ORDINAL : enum 객체 안에 넣어준 순서대로 사용하는 방법
    용량을 아낄 수 있다는 장점, boolen식으로 저장하고 싶을 때 유리, 순서가 바뀌면, 지정된 데이터가 바뀌는 단점
  • STRING : enum 객체를 문자열로 사용하는 방법, 문자열로 지정된 키처럼 사용하는 방법
    enum의 순서가 바뀌거나 enum이 추가되어도 안전
  • Enum : 서로 연관된 상수들의 집합을 의미하는 데이터 타입
    코드 가독성 높이기 가능
    타입 세이프티를 보장 가능
public enum 열거형이름 {
    상수1, 상수2
}
  • Entity 작성
@Entity(name = "엔티티_논리이름") <!-- 선택: 다른 클래스와 이름 겹칠 때 사용 -->
@Table(name = "테이블_이름")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter	 <!-- 실제 서비스에서는 Setter 사용을 제한하는 것이 일반적 (보안/불변성 등) -->
@ToString
public class 클래스명 {

    @Id
    @Column(name = "기본키_컬럼명")
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가 시 사용
    private 자료형 기본키필드;
    
    <!-- enum으로 정의 된 값 -->
    @Column(name = "컬럼명")
    @Enumerated(EnumType.STRING)  // Enum 값을 문자열로 저장
    private 열거형이름 필드명;
  • enumtype 테스트
public class EnumTypeMappingTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
	@Test
    public void enumTypeFieldMappingTest() {

        <!-- given : 테스트할 엔티티 인스턴스 생성 및 Enum 값 지정 -->
        엔티티클래스명 엔티티변수 = new 엔티티클래스명();
        엔티티변수.set필드1("특정값1");
        엔티티변수.set필드2("특정값2");
        엔티티변수.set열거형필드(열거형타입.상수명);  // Enum 값 설정

        <!-- when : 트랜잭션 시작 및 영속화 -->
        EntityTransaction transaction변수 = entityManager.getTransaction();
        transaction변수.begin();

        try {
            entityManager.persist(엔티티변수);
            transaction변수.commit();
        } catch (Exception e) {
            transaction변수.rollback();
            throw new RuntimeException(e);
        }

        <!-- then : DB에서 다시 조회 후 검증 -->
        엔티티클래스명 조회결과변수 = entityManager.find(엔티티클래스명.class, 엔티티변수.get기본키());
        System.out.println("조회결과 = " + 조회결과변수);
        assertEquals(엔티티변수.get기본키(), 조회결과.get필드1());
    }
}

CompositeKey 복합키

EmbeddedKey 방식

@Embeddable 클래스에 복합키를 정의하고 엔티티에 @EmbeddedId 를 이용하여 매핑

  • 복합키의 일부 필드만을 매핑할 수 있다. 따라서 필드 수가 많은 경우 유연하게 매핑 가능하다.
  • 복합키를 객체로 분리
  • 조금 더 객체지향적인 방식 -> 더 많이 쓰이는 방식
  • 복합키 클래스 영속성 컨텍스트가 관리하지 않는다는 특징

EmbeddedKey 방식 사용 방법

  • 복합키 설정 클래스 작성
@Embeddable  // 복합키로 사용할 값 타입 지정, 복합키로 가능하게 한다.
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 복합키클래스명 {

    @Column(name = "컬럼명1")
    private 자료형 필드1;

    @Column(name = "컬럼명2")
    private 자료형 필드2;
}
  • 복합키 Entity 클래스 작성
@Entity(name = "엔티티_논리이름") // 선택: 다른 클래스와 이름 겹칠 때 명시
@Table(name = "테이블명")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 엔티티클래스명 {

	<!-- 복합키 객체를 엔티티의 기본키로 사용 -->
    @EmbeddedId
    private 복합키클래스명 복합키변수명;

    @Column(name = "컬럼명3")
    private 자료형 필드3;

    @Column(name = "컬럼명4")
    private 자료형 필드4;
}
  • EmbeddedKey 복합키 테이블 매핑 테스트
public class EmbeddedKeyTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
    @Test
    public void embeddedIdTest() {

        <!-- given : 복합키 및 엔티티 인스턴스 생성 -->
        엔티티클래스명 엔티티변수명 = new 엔티티클래스명();
        엔티티변수명.set복합키필드(new 복합키클래스명(특정값1, 특정값2));
        엔티티변수명.set일반필드1("특정값1");
        엔티티변수명.set일반필드2("특정값2");

        <!-- when : 트랜잭션 시작 및 영속화 -->
        EntityTransaction transaction변수명 = entityManager.getTransaction();
        transaction변수명.begin();

        try {
            entityManager.persist(엔티티변수명);
            transaction변수명.commit();
        } catch (Exception e) {
            transaction변수명.rollback();
            throw new RuntimeException();
        }

        <!-- then : 복합키 기준으로 다시 조회하여 동등성 검증 -->
        엔티티클래스명 조회결과변수명 = entityManager.find(엔티티클래스명.class, 엔티티.get복합키필드());
        assertEquals(엔티티변수명.get복합키필드(), 조회결과변수명.get복합키필드());
    }

IdClass 방식

복합키를 필드로 정의한 클래스를 이용해서 클래스에 @IdClass를 매핑

  • 키 필드를 엔티티에 직접 정의
  • 복합키를 구성하는 모든 필드를 한번에 매핑 가능하다.
  • 코드가 간결
  • 관계형 데이터 베이스가 가까운 방식
  • 복합키 클래스 영속성 컨텍스트가 관리하지 않는다는 특징

IdClass 방식 사용 방법

  • 복합키 설정 클래스 작성
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 복합키클래스명 {

    private 자료형 복합키필드1;
    private 자료형 복합키필드2;

}
  • 복합키 Entity 클래스 작성
@Entity(name = "엔티티_논리이름")
@Table(name = "테이블명")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
<!-- 복합키 클래스 지정 -->
@IdClass(복합키클래스명.class)
public class 엔티티클래스명 {

    @Id
    @Column(name = "복합키컬럼1")
    private 자료형 복합키필드1;

    @Id
    @Column(name = "복합키컬럼2")
    private 자료형 복합키필드2;

    @Column(name = "일반컬럼1")
    private 자료형 일반필드1;

    @Column(name = "일반컬럼2")
    private 자료형 일반필드2;
}
  • IdClass 복합키 테이블 매핑 테스트
public class IdClassTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
@DisplayName("IdClass를 이용한 복합키 테이블 매핑 테스트")
@Test
public void idClassCompositeKeyTest() {

    <!-- given : 복합키 및 일반 필드 설정 -->
    엔티티클래스명 엔티티변수명 = new 엔티티클래스명();
    엔티티변수명.set복합키필드1(특정값1);
    엔티티변수명.set복합키필드2("특정값2");
    엔티티변수명.set일반필드1("특정값3");
    엔티티변수명.set일반필드2("특정값4");

    <!-- when : 트랜잭션 시작 및 영속화 -->
    EntityTransaction transaction변수명 = entityManager.getTransaction();
    transaction변수명.begin();

    try {
        entityManager.persist(엔티티변수명);
        transaction변수명.commit();
    } catch (Exception e) {
        transaction변수명.rollback();
        throw new RuntimeException(e);
    }

    <!-- then : 복합키 인라인 생성 후 조회 -->
    엔티티클래스명 조회결과변수 = entityManager.find(엔티티클래스명.class, new 복합키클래스명(값1, "값2"));

    assertEquals(엔티티변수명, 조회결과변수);
}

참고

== : 주소값 비교
equals() : 값만 같은지 비교

profile
잔디 속 새싹 하나

0개의 댓글