CH02 JPA 시작
2장은 책의 목차와 내용이 생각보다 부실하고 오히려 인프런 강의가 정리가 잘 되어있어서, 강의 중심으로 정리한다.
JPA에서 알아서 처리해주는 트랜잭션을 코드로 구현해보기
편리한 기능을 제공해준다고 해서 결과만 인식하고 사용하다보면 원리를 깊게 이해하기도 어렵고 문제가 발생 했을때 원인을 찾을 내공이 부족하게 된다.
@Transactional 을 이용해서 한 단위의 트랜잭션으로 묶어서 처리를 하곤 하는데 이렇게 어노테이션으로 트랜잭션 단위를 정하는 행위가 실제 내부적으로 어떻게 처리가 되는 것인지에 대해 알 수 있도록 raw 한 수준에서 코드로 작성해보는 기회가 되었다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("persistenceUnitName");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
// business logic
transaction.commit();
} catch (Exception exception) {
transaction.rollback();
} finally {
entityManager.close();
}
핵심은 트랜잭션의 시작과 커밋이 try 내에 존재한다는 것이다. 예외 발생시 catch 에서 롤백이 된다. 어노테이션에서 어떤 예외일때 롤백할지도 정할 수 있는데 그런 경우에는 여기서 catch 가 조금 더 세분화가 될 것이다.
나중에 백기선님 강의나 김영한님 강의에서도 계속 다루고 있는데, EntityManager 는 스레드에 따라 독립적으로 할당된다. (이 부분은 아래에 다시 정리)
JPA 구성요소간 관계도

외울 것도 없고 흐름에 맞춰서 생각해보면 당연한 내용들이다. Persistence Unit 은 아래와 같이 Database 접속 정보를 가지고 있는 configuration 이라고 보면 된다.
<persistence-unit name="hello">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.user" value="berry"/>
<property name="javax.persistence.jdbc.password" value="straw"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/jpa-practice-schema"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
</properties>
</persistence-unit>
위 구조에서 entityManagerFactory의 경우 Bean으로 자동 생성된다. Runner 에서 아래와 같이 직접 확인해본다.
@Override
@Transactional
public void run(ApplicationArguments args) throws Exception {
String[] beans = applicationContext.getBeanDefinitionNames();
for (String bean : beans) {
System.out.println("bean : " + bean);
}
}
...(중략)...
bean : entityManagerFactoryBuilder
bean : entityManagerFactory
...(중략)...
EntityManager 는 @PersistenceContext 를 통해서 아래와 같이 entityManagerFactory 로 생성해서 주입받아 사용한다. 하지만 실무에서는 굳이 직접 EntityManager 를 사용할 일은 적어도 나의 경험 선에서는 없었다.
@PersistenceContext
private EntityManager entityManager;
책에서도 강조하듯 EntityManager는 데이터 베이스 커넥션과 밀접하게 작동하므로 스레드간에 공유하거나 재사용을 하면 안된다. 아래와 같이 EntityManager 를 변수명을 달리 하여 두 개를 생성해봐도 결국 주소값을 비교해보면 동일한 EntityManager 임을 확인할 수 있다.
@Component
public class JpaRunner implements ApplicationRunner {
@PersistenceContext
private EntityManager entityManager1;
@PersistenceContext
private EntityManager entityManager2;
@Override
@Transactional
public void run(ApplicationArguments args) throws Exception {
System.out.println(">>>>>");
System.out.println(entityManager1);
System.out.println(entityManager2);
System.out.println("<<<<<");
}
}
>>>>>
Shared EntityManager proxy for target factory [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@103478b8]
Shared EntityManager proxy for target factory [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@103478b8]
<<<<<
결국 entityManagerFactory 만 빈으로 주입받아고 여기서 스레드마다 별도로 entityManager 를 할당 받고 이 entityManager 가 위에서 원형 코드를 본 것처럼 트랜잭션을 관리하기 때문에 1개의 트랜잭션을 만들고 이것을 try 내에서 시작 후 커밋까지 한다. catch 에서 롤백이 발생하며 결과가 어떻게 되든 finally 에서 리소스를 반환한다.
Last updated