본문 바로가기

Spring/Data

엔티티 매니저 팩토리와 엔티티 매니저

EntityManagerFactory

엔티티 매니저 팩토리의 주요 역할은 엔티티 매니저가 인스턴스화 되도록 지원하는 것이다. 엔티티 매니저 팩토리는 하나의 데이터베이스를 위해 구성되며 리소스를 효율적으로 관리(커넥션 풀 관리 등)함으로써 해당 데이터베이스에 대해 여러 엔티티 매니저를 구성하는 효율적인 방법을 제공한다. 

 

생성 비용이 크기 때문에 한 번 생성 후 재사용되며 Thread-Safe 하게 구현되어 있어 구성된 전체 애플리케이션을 지원할 수 있다. 애플리케이션을 종료할 때는 반드시 종료해주어야 하며 엔티티 매니저 팩토리가 종료되면서 엔티티 매니저들도 함께 종료된다.

 

생성 시점에는 persistence.xml 파일에 설정된 정보를 읽어온다. 

persistence.xml 설정 정보

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="persistenceUnitName">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value=""/>
            <property name="javax.persistence.jdbc.user" value=""/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value=""/>
            
            <!-- 방언 -->
            <property name="hibernate.dialect" value=""/>

            <!-- 옵션 -->
            <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.jdbc.batch_size" value="10"/>

            <!-- 운영 장에비는 validate, none 이외에는 절대 사용하면 안된다.
                 개발 단계   : create, update
                 테스트 단계 : update, validate
             -->
            <!-- 애플리케이션이 로드될 때 테이블이 drop 후 create 한다. -->
            <!--<property name="hibernate.hbm2ddl.auto" value="create" /> -->
            <!-- 애플리케이션이 로드될 때 테이블이 drop 후 create / 종료 후 테이블 drop -->
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <!-- 변경분만 반영(운영DB에는 사용하면 안됨) -->
            <!--<property name="hibernate.hbm2ddl.auto" value="update" />-->
            <!-- 엔티티와 테이블이 정상 매핑되었지만 확인 -->
            <!--<property name="hibernate.hbm2ddl.auto" value="validate" />-->
            <!-- 사용하지 않음 -->
            <!--<property name="hibernate.hbm2ddl.auto" value="none" />-->

            <!-- n + 1 해결 (테이블 수에 맞게끔 쿼리 실행) -->
            <!-- <property name="hibernate.default_batch_fetch_size" value="100"/>-->
        </properties>
    </persistence-unit>
</persistence>

설정 정보는 버전에 따라 조금씩 달라지며 해당 파일은 2.x 버전을 기준으로 작성되었다.

persistence-unit 별로 명칭을 주어 관리할 수 있고, 설정 정보는 크게 데이터베이스 정보, 방언 설정, 엔티티에 따른 테이블 생성 조건, JPA에 적용할 옵션 등이 있다. 이 중 내가 생각하는 JPA의 가장 큰 장점은 dialect 옵션인데 JPQL, QueryDSL를 사용하면 해당 데이터베이스의 쿼리로 변경해준다. 즉 한 데이터베이스에 종속되지 않는다는 큰 장점이 있다.

 

Spring은 환경 설정에 따라 보다 세분화된 옵션을 제공하며, Spring 3.1부터는 persistence.xml 설정 파일이 더 이상 필요하지 않게 되었다고 한다.

EntityManagerFactory 생성과 삭제

EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnitName");
emf.close();

생성과 삭제는 정말 간단히 위의 코드를 작성해주면 끝이다. persistence-unit 명을 지정하여 생성하면 엔티티 매니저를 바로 사용할 수 있다. close()를 해주면 엔티티 매니저 팩토리의 리소스를 정리해준다. (스프링 부트를 쓰고 있다면 알아서 관리해준다.)

EntityManager

엔티티 매니저는 간단히 말하면 CRUD를 해주는 역할을 한다. 쿼리를 날리기 위해 데이터베이스의 커넥션과 트랜잭션을 얻고 트랜잭션 안에서 발생한 비즈니스 로직에 따라 생성, 조회, 변경, 삭제 작업이 일어난다. Thread-Safe 하지 않기 때문에 반드시 매번 새로운 엔티티 매니저를 생성하고 삭제해주어야 한다.

 

더 나아가 엔티티 매니저는 엔티티를 관리하는 역할을 한다. 엔티티 매니저는 영속성 컨텍스트를 통해 엔티티를 관리하며 흔히 JPA의 이점인 1차 캐시, 변경 감지, 동일성 보장 등의 기능을 누릴 수 있게 된다.

EntityManager 생성과 삭제

EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
EntityTransaction tx = em.getTransaction(); //트랜잭션 얻기
tx.begin(); //트랜잭션 시작

/* ... 비즈니스 로직 실행 */

tx.commit(); //트랜잭션 커밋
em.close(); //앤티티 매니저 삭제

엔티티 매니저를 생성 후 비즈니스 로직을 실행하기 전 반드시 트랜잭션을 시작해주어야 한다. 비즈니스 로직이 종료가 되면 마찬가지로 반드시 트랜잭션 커밋과 함께 앤티티 매니저를 삭제해주어야 한다. 

 

만약 삭제를 하지 않았을 경우 다른 스레드에서 해당 엔티티 매니저를 재사용할 수 있게 된다. 그렇게 되면 엔티티 매니저가 관리하는 엔티티의 데이터가 어떻게 변경될 지 모르니 반드시 삭제해주어야 한다.

 

스프링 부트를 쓰고 있다면 @Transaction만 잘 적용해주면 되고 엔티티 매니저를 직접 꺼내 쓰는 일은 드물다.

다만 영속성 컨텍스트의 생존 범위를 구분 짓는 OSIV에 대해 알아두어야 한다.

스프링에서 사용되는 OSIV(Open Sesion In View) 옵션

spring.jpa.open-in-view : true (기본값) 

트랜잭션 시작처럼 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지한다.영속성 컨텍스트가 트랜잭션 범위 밖에서도 살아있기 때문에 영속성 컨텍스트의 이점(지연 로딩 등)을 사용할 수 있다.

 

하지만 이 전략은 너무 오랜시간동안 데이터베이스 커넥션 리소스를 사용하기 때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있다.

 

spring.jpa.open-in-view: false

OSIV를 끄면 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환한다. 따라서 커넥션 리소스를 낭비하지 않는다.
OSIV를 끄면 모든 지연로딩을 트랜잭션 안에서 처리해야 한다. 따라서 지금까지 작성한 많은 지연 로딩 코드를 트랜잭션 안으로 넣어야 하는 단점이 있다. 그리고 view template에서 지연로딩이 동작하지 않는다. 결론적으로 트랜잭션이 끝나기 전에 지연 로딩을 강제로 호출해 두어야 한다. 


참고 자료

JPA 강의 로드맵

JPA EntityManagerFactory Interface With Example