[JPA] 트랜잭션(Transaction) 이해하기(2) - JPA
DB의 데이터변경은 '트랜잭션(Transaction)' 단위로 이루어진다.
트랜잭션이란?
DB 데이터 변경은 단 하나의 쿼리만으로 이루어지지 않는다. 입금을 생각해보자. A가 B에게 5만원을 입금하면 두 가지 쿼리가 실행되어야 한다.
1) A계좌에 5만원 출금
2) B계좌에 5만원 입금
1) 과 2)는 독립되어 있지 않다. 1)이 성공해도 2)가 실패하면 1)은 원복되어야 한다. 그러므로 여러 처리를 하나의 단위로 묶는 논리적 단위가 필요한데, 이가 바로 '트랜잭션'이다.
이전 포스팅에서는 트랜잭션을 JDBC와 DB 관점에서 구현해보았다. 이번에는 JPA 관점에서 구현해보겠다. JPA는 상당히 추상화된 문법을 제공하기에 트랜잭션을 간단히 구현할 수 있다.
JPA에서 트랜잭션(Transaction) 구현하기
Account 클래스 ( 엔티티 클래스 )
@Entity
@Data
public class Account {
@Id
@GeneratedValue // 시퀀스 생성
private Long id;
private String owner;
private int amount;
public Account(String owner, int amount) {
this.owner = owner;
this.amount = amount;
}
// 출금 메소드
public void withdraw(int money){
this.amount -= money;
}
//입금 메소드
public void deposit(int money){
this.amount += money;
}
}
엔티티 클래스를 하나 만들어준다.
public class JpaMain {
public static void main(String[] args) {
//EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("h2");
//EntityManager 생성
EntityManager entityManager = emf.createEntityManager();
//EntityTransaction 생성
EntityTransaction tx = entityManager.getTransaction();
tx.begin(); //트랜잭션 시작
try{
Account accountA = new Account("사쿠라",20000); // 엔티티 생성
Account accountB = new Account("김채원", 30000); // 엔티티 생성
entityManager.persist(accountA); // 계좌 영속화 [ INSERT 문 ]
entityManager.persist(accountB); // 계좌 영속화 [ INSERT 문 ]
accountA.withdraw(5000); // 출금 [ UPDATE 문 ]
accountB.deposit(5000); // 출금 [ UPDATE 문 ]
tx.commit(); // INSERT문 2개, UPDATE 문 2개 DB 반영
}catch (Exception e){
tx.rollback(); // 트랜잭션 롤백
}finally {
entityManager.close();
}
}
}
JDBC는 DB와 연결시 커넥션(Connection) 객체를 사용한다. 커넥션 객체가 생성되면 트랜잭션이 시작되고 커넥션 객체가 종료되면 트랜잭션이 종료된다. 커넥션이 트랜잭션을 관리하는 개념이다.
커넥션은 DB드라이버가 생성하는데, 드라이버를 로드하고 커넥션을 생성 및 종료하는 과정이 시간을 많이 소모한다. 그래서 이를 개선하기 위해, DB와 이미 연결된 커넥션을 미리 여러개 생성하여 커넥션풀로 관리하고 클라이언트는 커넥션풀에서 커넥션을 빌리고 반납하는 DBCP 방식으로 구현된다.
SpringBoot의 경우, EntityManager가 HikariCP 프레임워크가 제공하는 커넥션풀에 접근하여 커넥션을 얻는다. 이처럼 JPA는EntityManager가 커넥션에 접근한다.
EntityManager가 getTransaction() 메소드를 호출하여 트랜잭션을 객체를 생성하고 tx.begin()으로 트랜잭션을 시작할 수 있다. 코드를 보면 INSERT문 2번 , UPDATE문 2번이 실행되었다. UPDATE문은 변경감지로 쿼리문이 만들어지는데 이는 다음에 자세히 다루어보겠다.
4개의 쿼리는 영속성 컨텍스트에 존재하는 쓰기지연SQL저장소에 저장된다. SQL문이 생성은 되지만 수행되지는 않는다. ( execute X ) 만약 SQL문을 실행시키고 싶으면 entityManager.flush()를 하면 된다. 플러쉬(flush)는 쓰기지연SQL저장소에 저장되어 있는 SQL문을 실행하는 역할을 한다. 그럼 영속성컨텍스트와 DB가 '동기화'된다.
여기서 트랜잭션이 커밋되면 완전히 DB에 반영이 되고 롤백이 되면 영속성컨텍스트와 더불어, DB도 이전상태로 돌아간다.
- 결과
사쿠라는 20000 ➠ 15000, 김채원은 30000 ➠ 35000 으로 변경되었다.
이처럼 개발자가 직접 쿼리문을 작성하지 않아도, 개발자가 직접 커넥션 객체에 접근하지 않아도 JPA가 중간에서 자동화하여 ORM기술을 구현한다.
참고자료