먼저 이론적인 방법을 설명하겠다. 이론적인 방법을 설명하는 이유는 Spring Boot 사용 여부에 따라 EntityManager 사용 방법이 완전히 다르기 때문이다.
지금 설명하는 방법은 Spring Boot를 사용하지 않을 경우 EntityManger를 생성하는 방법인데 Spring Boot를 활용하더라도 이런 개념이 바뀌는 것은 아니고 단순히 Spring Boot가 중간 과정을 담당해주기 때문에 더욱 편리하게 설정 및 EntityManager를 생성할 수 있게 되는 것이다.
JPA도 큰 의미로 보자면 MyBatis의 동작 방식과 유사하다.
MyBatis는 SqlSessionFactory라는 것이 Query가 필요할 때마다 SqlSession을 생성하여 Query를 수행시켰다. 이 때 SqlSessionFactory에 설정 파일을 먹여주기 때문에 Factory가 만드는 SqlSession 또한 설정 파일에 기록된 설정대로 생성되었다.
JPA는 SqlSessionFacotry가 EntityManagerFactory로 변경되고, EntityManagerFactory이기 때문에 Query를 수행할 때마다 EntityManager를 생성한다는 차이점이 존재한다.
그렇다면 MyBatis와 JPA 차이점을 하나씩 비교해보자.
MyBatis는 Config 파일 경로가 고정되어 있지 않았다. 대신 "setConfigLocation" 메서드를 통해 SqlSessionFactoryBean에 Config 파일 경로를 입력해줘야 했다.
JPA는 META-INF/persistence.xml 이라는 고정된 경로와 특정된 파일에 설정값을 지정할 수 있다. 따로 Config 파일 경로를 입력하지 않아도 JPA는 persistence.xml을 찾아 원하는 설정 값을 찾아 EntityManagerFactory를 만들게 된다.
또한 persistence.xml에는 여러 가지 설정값들을 입력한 뒤 name을 통해 여러 가지 설정값 중 원하는 설정의 EntityManagerFactory를 형성할 수 있다.
풀어 얘기하자면, Mybatis는 Config A, Config B가 있을 경우 해당 Config 파일을 따로 만들어 준 뒤 setConfigLocation에 다른 경로를 먹여줌으로써 설정값들을 적용시킬 수 있었다.
하지만 JPA는 Config A, Config B에 대한 설정값들을 모두 persistence.xml에 한 번에 저장한다. 대신 name=A, name=B를 통해 각 Config를 구분할 수 있는 고유 name을 설정한 뒤 Persistence 객체가 EntityManagerFactory를 생성할 때 name을 입력해줌으로써 원하는 설정값을 먹여 줄 수 있다.
이런 설정값을 먹여주는 방법은 아래 코드를 통해 더 자세히 보자
MyBatis는 SqlSessionFactoryBean이라는 객체를 통해 SqlSessionFactory를 생성하였다.
이 SqlSessionFactoryBean에 설정들을 먹여주면 SqlSessionFactoryBean이 알아서 설정들을 적용시켜 내가 원하는 SqlSessionFactory를 생성시키는 방식이었다.
JPA는 Persistence라는 객체가 EntityManagerFactory를 생성하게 된다.
Persistence.createEntityManagerFactory라는 메서드를 통해 EntityManagerFactory를 형성하는데, 이때 META-INF/persistence.xml에 저장했던 여러 설정값에 대한 name 중 내가 원하는 설정값이 저장된 설정값 name을 입력해주면 된다.
Persistence는 META-INF/persistence.xml에 지정한 여러 설정 파일 중 내가 지정한 name의 설정값들을 찾아 해당 설정을 적용시킨 EntityManagerFactory를 형성하게 되는 것이다.
이 과정을 그림으로 표시하면 아래와 같다.
MyBatis는 라는 태그를 통해 객체와 Mapping이 가능했다.
JPA는 DB 데이터를 저장하고 싶은 객체에 어노테이션(@Entity)만 붙여주면 바로 객체와 Table을 Mapping 시킬 수 있다.
implementation 'org.hibernate:hibernate-entitymanager:4.3.10.Final'
Dependencies에 위 문구를 추가하면 된다.
JPA와 Hibernate 실습을 보면 "h2"라는 Database를 사용하는 것을 볼 수 있는데 굳이 h2를 사용하지 않아도 된다.
이전에 MyBatis를 공부할 때 MySQL을 활용하기도 했고, 결국 현실에서는 h2보다는 MySQL을 많이 활용하기 때문에 그대로 MySQL을 통해 JPA를 활용하기로 했다.
Spring Boot를 활용하지 않을 경우 application.properties를 통해 설정을 하는 것이 아닌, 위에서 말한 "META-INF/persistence.xml"파일을 통해 설정값을 먹여줘야 한다.
persistence.xml 파일 분석은 다음 Section에서 진행하도록 하자.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myjpa");
위에서 지정했던 persistence-unit(영속성 유닛) name 중 하나를 입력하여 EntityManagerFactory를 형성하면 된다.
위에서 설명했듯, JPA는 Persistence라는 객체가 EntityMangerFactory를 형성하기 때문에 최종적으로 위 코드처럼 EntityManagerFactory를 생성하게 된다. 이때 EntityManagerFactory와 같이 JPA 구현체에 따라 DB Connection Pool도 생성하게 된다.
Persistence 객체는 persistence.xml에서 "myjpa"라는 name을 가진 영속성 유닛을 찾아 영속성 유닛이 가지고 있는 설정값을 적용한 EntityManagerFactory를 형성하게 되는 것이다.
EntityManagerFactory는 1개 생성할 때 매우 많은 Cost(비용)가 소모된다. EntityMangerFactory는 단순히 EntityManager를 만들어주는 역할을 하기 때문에 여러 Thread가 동시에 접근해도 안전하며 다른 Thread 간에 공유도 가능하다.
즉 EntityManagerFactory를 여러 개 만들 필요가 없다는 것이다.
따라서 처음에 EntityManagerFactory를 생성했다면 처음 생성된 1개의 EntityManagerFactory를 애플리케이션 전체에서 공유할 수 있도록 설계하여 1개의 Factory가 계속해서 EntityManager를 생성하도록 로직을 구현해줘야 한다.
EntityManager em = emf.createEntityManager();
먼저 emf는 위에서 볼 수 있듯 미리 생성해 놓은 EntityManagerFactory 객체이다.
EntityManager는 이전에 말했듯 Thread 간 공유되면 안 되기 때문에 Thread 혹은 Transaction이 요청할 때마다 고유의 EntityManager를 생성해줘야 한다.
EntityMangerFactory와 달리 EntityManager를 생성하는 것은 비용이 거의 들지 않기 때문에 여러 개를 생성해도 큰 문제가 발생하지 않는다.
EntityManager는 DB 연결이 필요한 시점까지 Connection을 얻지 않는다. 즉 Connection Pool에서 Connection을 얻어오는 과정을 최소화하여 시간적 이점을 볼 수 있다.
em.close(); // EntityManager 종료
Transaction이 종료되어 EntityManager를 모두 사용했다면 반드시 종료시켜 줘야 한다.
(참고로 Spring Data JPA는 이런 EntityManger 종료를 자동으로 수행해주기 때문에 Human Error를 줄일 수 있다)
emf.close(); // EntityManagerFactory 종료
Application을 종료할 때 1번만 수행해주면 되는 메서드로써, EntityManagerFactory도 꼭 종료시켜 줘야 한다.
(이 또한 Spring Data JPA에서는 자동으로 종료시켜준다)
일단 제목은 Spring Boot를 사용한 EntityManager 사용법이라고는 해놨지만, 사실상 "Spring Data JPA" 사용법과 유사하다. Spring Boot를 활용할 경우 굳이 EntityManager를 사용할 필요가 없다. 사실, 시간 낭비일 뿐이다.
아래서 설명하겠지만 Annotation을 붙여서 굳이 내가 EntityManager를 활용하겠다는 표시를 해줘야 하기 때문이다.그래도 Spring Boot를 통해 EntityManager를 사용해보는 것이 역으로 Spring Data JPA가 얼마나 편한지 이해할 수 있게 도와준다고 생각하기 때문에 한 번 실습해보는 걸로 하겠다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
build.gradle의 dependencies에 위 의존성을 주입해주면 된다.
Spring Boot를 활용하지 않았을 때는 META-INF/persistence.xml 파일에 여러 가지 설정값들을 넣어주었다. 하지만 Spring Data JPA은 application.properties에 EntityManagerFactory에 대한 설정들을 넣어주면 Spring Data JPA 측에서 알아서 설정들을 인지하고 persistence.xml의 형식에 맞도록 변환해준다.
application.properties도 persistence.xml과 많은 연관성이 있기 때문에 다음 Section에서 persistence.xml을 다룰 때 같이 다루도록 하겠다.
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Autowired
public Member save(Member member) {
em.persist(member);
return member;
}
@Autowired
public Optional<Member> findById(long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
@Autowired
public List<Member> findAll() {
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
return result;
}
}
위 코드를 보면 EntityManager em을 생성자를 통해 주입받아 EntityManager를 활용하는 것을 볼 수 있다.
여기에서 재밌게 봐야 할 점은 "em.close()"라는 EntityManager를 종료시키는 메서드가 존재하지 않는다는 것이다.
Spring Data JPA 측은 EntityManager를 close 시켜야 할 때 자동으로 해주기 때문에 em.close()로 EntityManager를 닫아 줄 필요가 없으며, 이때문에 Human Error를 많이 줄일 수 있다는 장점을 지닌다.
또한 EntityManager를 외부에서 주입받기 때문에 EntityManagerFactory를 호출하여 EntityManager를 생성하는 로직 또한 추가하지 않아도 된다.
단순히 EntityManager를 통해 내가 원하는 Transaction이나 Query에 대한 로직을 짜주기만 하면 부가적인 로직에 대한 고민 없이 바로 활용할 수 있다는 것이다.
@Configuration
public class SpringConfig {
@PersistenceContext
private EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
this.em = em;
}
@Bean
public MemberService memberService() {
return new MemberService(memoryRepository());
}
@Bean
public MemberRepository memoryRepository() {
return new JpaMemberRepository(em);
}
}
먼저 EntityManager em을 보자. 원래라면 @Autowired 같은 Annotation이 붙어야 하는데 EntityManager에는 @PersistenceContext라는 어노테이션이 붙었다.
그리고 이 EntityManager는 Spring Config의 생성자를 통해 주입받을 수 있음을 볼 수 있는데, 이 때 애플리케이션에 존재하는 Bean에 등록된 단 하나의 EntityManagerFactory가 EntityManager를 생성하여 주입해줌을 눈치챌 수 있다.
마지막으로 "new JpaMemberRepository(em)"을 통해 EntityManger를 주입한 MemberRepository를 Bean에 등록시켜주기만 한다면 드디어 EntityManager를 통해 직접 구현한 로직을 활용할 수 있게 되는 것이다.
Spring Data JPA는 application.properties 파일을 설정하고 애플리케이션을 시작할 때 알아서 설정값을 적용한 EntityManagerFactory를 형성하게 된다.
Spring Data JPA는 EntityManagerFactory를 1번만 생성하여 애플리케이션 전역에서 사용할 수 있게 하는 로직까지 미리 구현하였기 때문에 우리는 EntityManagerFactory를 어떻게 애플리케이션 전역에서 활용하게 할지에 대한 로직 고민을 하지 않아도 된다. 단순히 어노테이션을 통해 EntityManager를 주입받고 활용하면 되는 것이다.
Spring Data JPA에서는 위 방법보다는 JpaRepository라는 Interface를 상속받아 Repository Interface를 만들고, 해당 Interface에 @Repository라는 어노테이션을 붙여서 EntityManager를 통한 Transaction 로직을 구현하지 않고 활용하는 경우가 대다수다.
이 경우 Naming Rule(명명 규칙)에 따라 Repository Interface에 메서드를 추가하면 Spring Data JPA 측에서 알아서 메서드 이름에 맞는 로직을 실행시킬 수 있는 Transaction이나 Query문을 구현한 클래스를 만든다.
이때 EntityManager를 활용할 텐데, 사실상 이 부분은 개발자가 직접 구현하는 부분이 아니라 Spring Data JPA 측에서 자동으로 수행하는 과정이기 때문에 개발자가 EntityManager를 직접 활용할 일은 거의 없다고 보면 된다.
즉, Spring Data JPA를 활용하면 EntityManager를 통해 Transaction이나 Query문을 직접 구현할 필요가 없으므로 위와 같은 방법이 있다는 정도만 알고 넘어가도 될 것이다.