[SpringStudy] 3. EntityManager, EntityManagerFatory

진주·2022년 3월 23일

SpringStudy

목록 보기
3/3

Entity

Entity는 DB의 테이블과 매칭되는 개념이라고 보면 된다.

예를들어 Database에 id, name, age를 필드 값으로 가지고 있는
Member 테이블을 CREATE 하려면 아래와 같은 QUERY로 작성해야한다.

CREATE TABLE `Member` (
  `id` BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
)

하지만 JPA를 통해 위의 쿼리문을 Java 코드로 만들면 아래 코드와 같다.

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private int age;
    
    ... Getter, Setter
}

기본적으로 JPA는 모든 필드(id, name, age)를 불러오게 구현되어있다.

하지만, 모든 케이스에서 모든 필드를 다루지 않고, id, name 필드만 다루는 경우가 있을 수 있다.

@Entity
@Table(name = "Member")
public class MemberOnlyName {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private long id;
    
    @Column(nullable = false)
    private String name;
    
    ... Getter, Setter
}
    

따라서 Member 테이블은 1개이지만, Entity는 경우에 따라 여러개를 만들 수 있다.


EntityManager

EntityManager는 위에 언급한 Entity를 관리(CRUD = 저장, 조회, 수정, 삭제)하는 역할을 수행하는 클래스이다.

말 그대로 Entity를 저장하는 가상의 DB로 생각하면 된다.

DB를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 1개만 생성한다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("해당이름");
EntityManager em = emf.createEntityManager();

위의 코드를 통해 EntityManagerFactory를 통해 필요할 때마다 EntityManager를 생산할 수 있다.

이렇게 생성된 EntityManager는 DB 연결이 필요하기 전까지 Connection을 얻지 않는다.

Hibernate를 포함한 JPA 구현체들은
EntityManagerFactory를 생성할 때 DB Connection 풀도 함께 만드는데,
해당 정보는 1) maven project인지 2) spring project인지에 따라 설정 방법이 다르다.


1) Maven Project인 경우

resources - META-INF 폴더 하단에 persistence.xml 파일을 생성하여 DB를 설정해 줘야했다.

<?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="jpa3"> <!-- 프로젝트 이름 -->
      <properties>
          
          <!-- DB와 연결하기 위한 필수 속성 -->
          <!-- MySQL 설정 -->
        <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver" />
		<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC"/>
		<property name="javax.persistence.jdbc.user" value="root"/>
		<property name="javax.persistence.jdbc.password" value="1234"/>
        
        <!-- DB Dialect(방언) 설정(MySQL) -->
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />

        <!-- 옵션(선택) 속성 -->
        <!-- hiberante의 실행 과정에서 콘솔에 테이블 생성, DDL, DML 등 SQL문을 출력하도록 -->
        <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>

[참고]

  1. <persistence-unit name="jpa3"> 에서 name이 프로젝트 명과 동일해야 한다!

  2. <property name="hibernate.hbm2ddl.auto" value="create"/> 에서 TABLE을 CREATE하고 난 뒤에는, update로 변경해줘야 한다!
    : "create"를 사용하면 존재하는 테이블을 DROP한 뒤, 새로운 테이블을 CREATE 한다.


2) spring project인 경우

resources 하단의 application.properties 파일에 DB를 설정해줘야했다. (MySQl 사용)

[참고]

  1. spring.datasource.url=jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC 에서 testdb로 설정되어 있기 때문에,
    생성되는 새로운 TABLE들은 testdb DB에 저장된다.

  1. spring.jpa.hibernate.ddl-auto=create 로 서버를 한 번 실행 해준 뒤, create를 update로 변경해주어야 기존 TABLE이 DROP되지 않는다.
    : create = DROP + CREATE

영속성 컨텍스트(Persistence Context)

EntityManager는 내부에 존재하는 영속성 컨텍스트(Persistence Context) 에 엔티티들을 관리한다.

영속성 컨텍스트 = "엔티티를 영구 저장하는 환경"이라는 뜻이다.

하지만 개발자가 직접 영속성 컨텍스트를 뜯어보거나 그 내부를 들여다 볼 수는 없어서, 약간은 논리적이거나 추상적인 개념으로 바라봐야 한다.

EntityManager로 엔티티를 저장하거나, 조회하면
EntityManager는 영속성 컨텍스트(Persistence Context)에 엔티티를 보관하고 관리한다.


EntityManger와 Persistence Context가 엔티티 관리하는 방법 ?

public class Join {

    public void join(String name, int age) {
        // 아직까지는 해당 엔티티를 엔티티 매니저가 관리하지 않는다.
        Member member = new Member();
        member.setName(name);
        member.setAge(age);
        
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("해당폴더명");
		EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        try {
            // 엔티티 매니저에서 수행하는 모든 로직은 트랜잭션 안에서 수행돼야 한다.
            tx.begin();
            
            // 이렇게 하면 해당 엔티티 매니저의 영속성 컨텍스트에 위에서 만든 member 객체가 저장된다.  
            // 이제 member 엔티티는 엔티티 매니저의 관리 대상이 되고, 영속성을 가졌다고 말할 수 있다.
            em.persist(member);
            
            // 영속성 컨텍스트에 있던 모든 SQL쿼리가 실행된다.
            // Commit하지 않으면 DB에 data가 저장되지 않는다.
            tx.commit();
        } catch(Exception e) {
            // 어떤 이유에서 오류가 났다면 트랜잭션을 롤백 시켜줘야한다.
            tx.rollback();
        } finally {
            // 엔티티 매니저 종료
            em.close();
        }
    }
}

이건 왜 적은지 정확하게 모르겠지만 한번 보면 좋을듯 하다.


EntityManagerFatory

EntityManager는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다.

내가 데이터를 수정하고 있는데 다른 스레드에서 해당 데이터를 미리 수정해버리면 안 되기 때문이다.

따라서 EntityManager는 하나를 공유하면 안 되고, 상황에 따라서 계속해서 만들어줘야한다.

EntityManager를 만들어 주는 것이 바로 EntityManagerFactory이다.

public class Join {
    public void join(String name, int age) {
        // 아직까지는 해당 엔티티를 엔티티 매니저가 관리하지 않는다.
        Member member = new Member();
        member.setName(name);
        member.setAge(age);
        
        // META-INF/persistence.xml에서 이름이 db인 persistence-unit을 찾아서 엔티티 매니저 팩토리를 생성 
        EntityManagerFatory emf = Persistence.createEntityManagerFactory("db");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        try {
            // 엔티티 매니저에서 수행하는 모든 로직은 트랜잭선 안에서 수행돼야 한다.
            tx.begin();
            
            // 이렇게 하면 해당 엔티티 매니저의 영속성 컨텍스트에 위에서 만든 member 객체가 저장된다.  
            // 이제 member 엔티티는 엔티티 매니저의 관리 대상이 되고, 영속성을 가졌다고 말할 수 있다.
            em.persist(member);
            
            // 트랜잭션을 커밋한다.
            tx.commit();
        } catch(Exception e) {
            // 어떤 이유에서 오류가 났다면 트랜잭션을 롤백 시켜줘야한다.
            tx.rollback();
        } finally {
            // 엔티티 매니저 종료
            em.close();
        }
        emf.close(); // 마찬가지로 엔티티 매니저 팩토리도 종료
    }
}

공장(EntityManagerFatory)에서 제품(EntityManager)를 찍어내는 개념이라고 보면 될 것 같다.
EntityManagerFatory는 엔티티 매니저와 달리 여러 스레드가 동시에 접근해도 안전하다.

비용도 보면 공장을 짓는 비용은 굉장히 큰 것처럼,
EntityManagerFatory는 DB 당 하나 밖에 사용하지 않는다.


출처 : https://perfectacle.github.io/2018/01/14/jpa-entity-manager-factory/
https://sweets1327.tistory.com/60
https://velog.io/@jaeseok-go/JPA-%EC%98%81%EC%86%8D%EC%84%B1

profile
진주의 코딩일기

0개의 댓글