본문 바로가기
  • 실행력이 모든걸 결정한다
Spring 사전 준비/JPA Hibernate

[JPA] EntityManager / persistence.xml

by 김코더 김주역 2021. 8. 16.
반응형

이번 포스팅에서는 실제로 JPA를 이용하여 간단한 트랜잭션 체계를 만들어볼 것이다.

 

 

1. EntityManager와 EntityManagerFactory

예제에 들어가기 전에 알아둬야 할 클래스들이다.

 

1) EntityManager

(1) 역할

- Entity의 생명 주기와 트랜잭션 등을 관리하며, 영속성 컨텍스트에 접근하는 객체다.

- 가급적 쓰레드 간에 공유해서는 안되고(동시성 문제), 사용하고 나면 바로 버려야 함

- 컨테이너가 관리하는 EntityManager(JavaEE와 서버 필요)와 애플리케이션이 관리하는 EntitiyManager(JavaEE와 JavaSE에서 모두 사용 가능)로 나뉨

 

(2) 메소드

이미지 출처 : 자바 ORM 표준 JPA 프로그래밍 - 김영한

<1> find(Class<T> entityClass, Object primaryKey)

- SELECT 기능 (영속성)

- 먼저 1차 캐시에서 엔티티를 찾고 만약 엔티티를 못찾았다면 데이터베이스에서 조회한다. 데이터베이스에서 조회했다면 엔티티를 1차 캐시에 저장한 후에 영속 상태의 엔티티를 반환한다. 

※ find로 가져오는 객체는 영속성이기 때문에, 이 객체를 수정하고 commit만 다시 해주면 변경 내용이 감지되어 바로 DB에 저장된다.

 

<2> persist(Object entity)

- INSERT 기능 (영속성)

 

<3> merge(Object entity)

- INSERT 기능과 UPDATE 기능

- 인자로 넣은 준영속 엔티티를 참고하여 1차 캐시와 DB에서 동일 식별자를 가지는 엔티티를 찾아서 모든 속성을 update한 뒤, 변경 결과가 반영된 엔티티를 영속 object로 재생성하여 반환함. DB에서도 찾지 못했다면 새로운 엔티티를 생성하여 insert하게 됨.

※ 준영속 상태 : 영속 상태였다가 분리되어 더 이상 영속성 컨텍스트가 관리하지 않는 상태

- 반환된 객체는 영속성을 가지기 때문에 이 객체를 수정하고 commit 했을 때 변경 내용 DB에 저장된다.

 

<4> remove(Object entity)

- DELETE 기능

- Object 객체가 영속성 컨텍스트에서 제거됨

※ 영속성 컨텍스트에서 제거되었다는 것도 변경 사항에 해당되므로 커밋시 DB에서도 제거된다.

- 삭제할 엔티티를 다른 엔티티가 참조하고 있다면 예외가 발생하기 때문에 연관관계를 미리 제거해야 한다.

member.setTeam(null);
em.remove(team);

 

<5> detach(Object entity)

- 영속 상태인 특정 엔티티를 준영속 상태로 변경함

- DB에서 삭제하는게 아님

 

<6> createQuery(String query)

- JPQL 문법의 쿼리를 인자로 넣으면 SQL로 변환되어 DB에 전송되고, 이 과정에서 flush 메소드가 자동으로 호출됨

- 섬세한 쿼리 작성이 필요할 때 쓰임

※ JPQL : DB 테이블을 대상으로 쿼리하는 SQL과 달리 클래스와 필드를 대상으로 쿼리하는 객체 지향 쿼리다. 그리고 대소문자를 명확하게 구분한다.

예시

String jpql = "select m from Member m join m.team t where t.name=:teamName"; // 앞에 :가 붙은 변수에 파라미터를 바인딩 받는다.
List<Member> memberList = em.createQuery(jpql, Member.class)
    .setParameter("teamName", "group"); // :teamName에 파라미터 바인딩
    .getResultList();

 

<7> contains(Object entity)

- 영속성 컨텍스트에서 해당 object의 포함 여부를 boolean형으로 반환함

 

<8> clear()

- 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 변경함

 

<9> flush()

- 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교하여 변경 내용을 강제로 DB에 반영함

※ 이 때, 영속성 컨텍스트의 내용은 비워지지 않고 이후의 변경 내용을 감지할 때 활용됨

- 기본적으로 commit 또는 JPQL쿼리 실행 시에 자동 호출됨

- SQL 기반의 비 ORM 기술과 함께 사용할 때 쓰임

※ JPA와 달리 비 ORM 기술은 보통은 캐시를 적용하지 않기 때문에, 비 ORM 기술과 트랜잭션을 공유하는 경우에 일관성을 맞추기 위해 사용된다. 비 ORM 기술을 사용하는 DAO가 호출될 때마다 flush()를 호출하도록 AOP를 적용하는 방법이 깔끔하다.

 

<10> setFlushMode(FlushModeType flushModeType) 

- Flush 모드를 직접 지정한다. 인자로 javax.persistence.FlushModeType을 사용한다.

FlushModeType.AUTO // (기본값) Commit 또는 JPQL쿼리 실행 시에 flush
FlushModeType.COMMIT // Commit할 때만 flush

 

<11> refresh()

- 해당 엔티티를 DB로부터 다시 조회한다.

 

<12> close()

- 현재 EntityManager의 관리를 종료함

 

 

2) EntityManagerFactory

- EntityManager을 생성하기 위한 클래스

- 하나만 생성해서 애플리케이션 전체에서 공유해야 함

- persistence.xml에서 설정함

 

 

 

2. persistence.xml

persistence.xml은 JPA에 대한 설정 파일이다.

resources 디렉토리에 META-INF 디렉토리를 생성하고, 그 안에 persistence.xml 설정 파일을 생성한다.

Spring Boot에서는 persistence.xml 대신에 application.properties로 편리하게 설정할 수 있다.

 

<persistence.xml>

persistence-unit name은 EntityManagerFactory를 생성할 때의 설정 식별에 필요하다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2">
    <persistence-unit name="hello">
        <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=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:~/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.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

- dialect(방언) : 같은 동작이라도 DB 종류마다 사용하는 SQL 문법과 함수가 다르기 때문에 방언이라고 표현한 것이다. 즉, 해당 DB의 방언으로 소통하기 위해 dialect 설정을 해주는 것이다.

※ 위의 설정의 경우에는 H2 DB를 사용하기 위해 H2 방언으로 설정한 것이다. 오라클 10g를 사용한다면 org.hibernate.dialect.Oracle10gDialect를, MySQL을 사용한다면 org.hibernate.dialect.MySQL5InnoDBDialect를 주로 사용한다.

- show_sql, format_sql, use_sql_comments : SQL 문을 출력하는 방식에 대한 설정

- ddl.auto : DDL 스키마 자동 생성 기능. Entity 객체의 변경 사항을 DB 스키마에 반영하는 방식에 대한 설정

 

hibernate.hbm2ddl.auto 속성

- create : 프로그램 실행 시 기존 테이블을 없애고 새로 생성

- create-drop : create와 비슷한데, 테이블을 종료 시점에 DROP한다는 차이가 있음

- update : 변경한 내용만 반영

- validate : Entity와 테이블을 비교해서 맞지 않으면 경고를 남기고 애플리케이션을 실행하지 않음

- none : 사용 안함(기본값)

※ 운영 서버에서는 validate 또는 none을 사용하는 것이 좋다.

※ 스키마 자동 생성 기능이 DB 종류에 종속되는 문제를 해결하기 위해 JPA 2.1부터는 스키마 자동 생성 기능을 표준으로 지원한다. 하지만 update, validate 옵션은 지원하지 않는다.

<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>

 

 

 

3. Entity 클래스 생성

 

<Member.java>

package com.example.jpastudy.entity;

import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.Date;

@Entity
@Getter
@Setter
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    @Column(unique = true)
    private String name;

    @Enumerated(EnumType.STRING)
    @Column(name = "class")
    private MemberClass memberClass;

    @Temporal(TemporalType.TIMESTAMP)
    private Date date;
}

id는 자동으로 생성시켰고, DB에서는 memberClass를 class로 간략하게 나타내도록 설정했다.

 

※ Entity Annotations 설명

https://kimcoder.tistory.com/356?category=964983 

 

[JPA] Entity Annotations

1. @Entity 설명 : DB랑 매핑하는 클래스 2. @Id 설명 : DB 기본키와 매핑할 필드 3. @GeneratedValue 1) 설명 : @Id와 같이 사용하며, 기본키의 값을 자동 생성하는 방식을 명시 2) 속성 (1) strategy : 기본키..

kimcoder.tistory.com

 

@Getter, @Setter : Lombok 라이브러리에서 제공하는 getter, setter 자동생성 어노테이션

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <scope>provided</scope>
</dependency>

※ SpringBoot가 아닌 Spring으로 진행할 경우에는 version을 명시해야 함

 

 

<MemberClass.java>

package com.example.jpastudy.entity;

public enum MemberClass {
    USER, VIP, ADMIN
}

열거형 클래스이다.

 

 

 

4. EntityManagerFactory 생성

 

<UniqueEntityManagerFactory.java>

package com.example.jpastudy.entity.emf;

import javax.persistence.EntityManagerFactory;

public class UniqueEntityManagerFactory {
    public static EntityManagerFactory emf;
}

하나의 EntityManagerFactory를 애플리케이션 전체에 공유하기 위해 static을 이용했다.

 

 

<JpaApplication.java>

package com.example.jpastudy;

import com.example.jpastudy.entity.Member;
import com.example.jpastudy.entity.MemberClass;
import com.example.jpastudy.entity.emf.UniqueEntityManagerFactory;
import com.example.jpastudy.service.MemberService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.Date;


@SpringBootApplication
public class JpastudyApplication {

	public static void main(String[] args) {
		SpringApplication.run(JpastudyApplication.class, args);

        	// persistence-unit의 이름으로 EntityManagerFactory 생성
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
        	// 애플리케이션 유일 EntityManagerFactory로 설정
		UniqueEntityManagerFactory.emf = emf;

		Member member1 = new Member();
		member1.setName("Lamb");
		member1.setMemberClass(MemberClass.VIP);
		member1.setDate(new Date());

		Member member2 = new Member();
		member2.setName("Jooyeok");
		member2.setMemberClass(MemberClass.ADMIN);
		member2.setDate(new Date());

		MemberService memberService = new MemberService();
		memberService.save(member1);
		memberService.save(member2);

        	// EntityManagerFactory 종료
		UniqueEntityManagerFactory.emf.close();
	}
}

모든 매핑이 끝났을 때 EntityManagerFactory를 종료해주었다.

 

 

 

5. JPA 사용

<MemberService.java>

package com.example.jpastudy.service;

import com.example.jpastudy.entity.Member;
import com.example.jpastudy.entity.emf.UniqueEntityManagerFactory;
import javax.persistence.*;

public class MemberService {

    public void save(Member member){
        EntityManagerFactory emf = UniqueEntityManagerFactory.emf;
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin(); // 트랜잭션 시작과 동시에 커넥션 획득

            em.persist(member); // INSERT

            tx.commit(); // 실제 DB에 저장
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback(); // 이전 commit 상태로 롤백
        } finally {
            em.close();
        }
    }
}

try문 안에서 문제가 발생하더라도 이전 commit 상태로 돌아갈 수 있는 간단한 트랜잭션 기능이 구현되었다. 그리고, EntityManager은 한 세트의 동작마다 생성/삭제가 이루어지도록 했다.

참고로, JPA를 사용할 때는 반드시 트랜잭션 안에서 데이터를 변경해야 한다. 그렇지 않으면 예외가 발생한다. 

 

 

실행 결과

자동으로 생성된 Table

 

자동으로 생성된 INSERT문

 

Table 조회

모든 Entity Annotation들이 잘 적용된 모습이다.

 

 

 

 

+) JPQL 참고할 만한 자료(타블로그)

- JPQL 문법 및 사용법 설명 - https://victorydntmd.tistory.com/205

- 현업에서 많이 쓰이는 JPQL JOIN FETCH -  https://adg0609.tistory.com/42

- JPQL @NamedQuery - https://coding-start.tistory.com/89

 

반응형

댓글