[JPA] 영속성 컨텍스트 (1)
영속성 컨텍스트란?
영속성 컨텍스트는 쉽게 풀이하면 “엔티티를 영구 저장하는 환경” 이라는 뜻입니다.
이렇게 정의로만 놓고보면, DB에 엔티티를 저장하는데, DB가 바로 영속성 컨텍스트 아닌가? 하는 생각도 드실 겁니다.
이처럼 영속성 컨텍스트는 JPA에서 가장 중요한 개념이자 헷갈리기 쉬운 개념일 수 있는데요. 아무래도 눈에 보이지 않아서 헷갈리는 부분이 큽니다. 그렇다면 영속성 컨텍스트에 대해서 알아봅시다.
EntityManagerFactory 와 EntityManager
JPA는 데이터 베이스에 접근하기 위해서 EntityManager를 사용합니다. 말그대로 Entity관리자인 셈이죠. JPA는 우리가 데이터베이스를 객체지향적으로 사용할 수 있도록 도와주기 때문에, JPA에서 Entity는 곧 테이블입니다. 따라서 이에 접근하기 위해 도와줄 매니저가 한 명 필요한 것인데요, 이 Entity매니저는 저장, 수정, 삭제, 조회등 Enity에서의 관련된 모든일을 처리합니다.
이러한 매니저들을 만드는 공장이 바로 EntityManagerFactory입니다. 고객의 요청이 들어올때 마다 EntityManager를 한명씩 붙여주고, 이들이 테이블을 관리하게 해주는 것이죠.
공장은 말그대로 공장이니, 규모가 크고 지으려면 오래 걸릴 것입니다. 그래서 반드시 하나의 데이터베이스에는 하나의 공장만을 지어주어야 합니다. 공장을 여러개 짓게 되면 프로그램의 속도가 크게 저하 될 수 있습니다.
그럼 공장을 생성하는 방법을 한번 코드로 작성해볼까요?
1
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
이렇게 하면 우리는 EntityManager들을 생산할 공장을 아주 쉽게 만들 수 있습니다.
여기서 createEntityManagerFactory의 파라미터로 들어오는 값은 JPA를 설정할 때 했던 설정파일인persistence.xml 파일에 있는 persistenceUnitName을 적어주면 됩니다.
그럼 데이터베이스에 접근하기 위해서는 여기 공장에서 매니저를 생산해서 데리고 와야 접근이 가능하다고 했던 내용이 기억나실 것 입니다. 이 공장에서 매니저를 생산하려면 어떻게 하면 될까요?
아래와 같이 생산하시면 됩니다.
1
EntityManager em = emf.createEntityManager();
우리는 이제 EntityManager 라는 클래스를 갖는 em이라는 이름의 매니저를 한명 생산한 것 입니다. 이제 이 매니저의 도움으로 우리는 우리의 데이터베이스에 접근 할 수 있게 되었습니다.
자 그럼 어떻게 하면 우리가 만든 객체를 데이터베이스에 넣을 수 있을까요? 한번 Member라는 클래스를 만들고 이를 Entity로 지정하여 데이터베이스에 넣어 봅시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Entity
public class Member {
@Id
private Long id;
String name;
String age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
간단하게 id와 이름, 나이 정도만 있는 Member라는 클래스를 만들어주어 봅시다.
간편하게 값을 가져오고 세팅하기 위해 Getter와 Setter도 모두 만들어 주도록 하겠습니다.
이제 이 상태로 main 함수를 실행 시켜 볼까요??
아까 Entity는 곧 데이터베이스에서 테이블이라고 했습니다. 그런데 Entity로 분명 Member를 지정해주었는데, 아무런 테이블도 생성되지 않았네요. 왜일까요?
그럼 이번에는 EntityManagerFactory를 하나 한번 만들어볼까요?
1
2
3
4
5
6
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
emf.close()
}
}
다시 한번 위 코드로 main 함수를 실행 시켜 봅시다.
참고로 emf 공장은 프로그램이 종료가 되기 전 반드시 close()를 통해서 공장의 문을 닫아주셔야 합니다. 그렇지 않으면 프로그램이 데이터베이스에 접근 후에도 계속 실행된 상태로 지속됩니다.
로그 창만 봐도 쿼리가 날아간게 보이네요!
혹시 쿼리가 날아가는게 보이지 않는 분들은 persistence.xml 옵션에서 sql이 보이도록 하는 옵션들을 true로 바꿔주세요
그럼 h2 데이터베이스를 확인해 볼까요?
정상적으로 Member 테이블이 생성된 것을 확인할 수 있습니다.
위 코드를 통해서 공장이 생성되야 본격적으로 테이블을 생성하게 됨을 알 수 있습니다.
그럼 이제 매니저를 만들어서 본격적으로 Member라는 클래스의 객체를 만들어서 해당 테이블의 컬럼을 채워보도록 합시다.
먼저, 매니저를 만들어 줍니다.
1
2
3
4
5
6
7
8
9
10
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
em.close()
emf.close()
}
}
아까 배운대로 동일하게 매니저를 만들어주면, 이제 em이라는 매니저가 우리의 데이터베이스 접근을 도와줄 것입니다. 데이터 베이스에 접근하려면 매니저에게 다음과 같은 명령을 해주어야 합니다.
매니저도 마찬가지로 사용이 완료되었다면, 반드시 close()를 사용해서 종료시켜주어야 합니다.
그럼 이제 매니저를 사용하기 위해 트랜잭션을 열어줄 것 입니다. 여기서 또 트랜잭션이라는 새로운 개념이 나옵니다.
트랜잭션
트랜잭션은 “쪼갤 수 없는 업무 처리의 최소 단위”를 말하는데요. 이해가 쉽도록 다음과 같은 과정을 생각해봅시다.
만약 Member1과 Member2를 기록한다고 해봅시다. Member1과 Member2는 커플이라서 무조건 함께 기록되어야 합니다. 우리는 이 커플들을 (1,2), (3,4)와 같이 2의 단위로 묶어서 기록 하기로 했기 때문에, 1,2는 무조건 커플이어야 합니다.
물론 실제에서 이렇게 데이터베이스를 관리하지는 않지만, 이해를 쉽게 하기 위해 이렇게 가정해보겠습니다.
그렇다면 만약 Member1은 정상적으로 기록되었는데, Member2에서 오류가 발생하여 데이터베이스에 접근하지 못하게 되었다면 어떻게 될까요?
그렇다면 데이터베이스에 Member1만 기록될테니, 후에 만약 Member3와 Member4가 온다면 Member1과 Member3가 커플이 되고, Member4는 짝이 없어진 채로 데이터베이스에 기록될 것입니다.
그래서 우리는 이러한 일을 방지하기 위해서, 트랜잭션을 사용합니다.
트랜잭션은 아까 쪼갤 수 없는 업무 처리의 최소 단위라고 했습니다.
따라서 트랜잭션 단위 안에 Member1 과 Member2를 쓰는 코드를 넣어주게 되면, 트랜잭션 안에서 예외가 발생하면 트랜잭션 단위를 통째로 취소합니다. 따라서 Member2에서 에러나 예외가 발생했다면, 트랜잭션 하나가 전부 취소되어 Member1도 기록이 되지 않게 되는 것입니다.
그러면 혹여나 Member2에서 에러가 발생해도 우리가 원하는 작업 단위가 통째로 취소되므로, 후에 들어올 Member3와 Member4는 안전하게 기록될 수 있는 것이죠.
코드로 한번 살펴 볼까요?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
트랜잭션은 위 코드와 같이 사용합니다. 매니저로부터 트랜잭션을 얻어온 뒤, begin()을 통해서 트랜잭션의 시작을 알려주고, try-catch 구문을 사용해서 혹시나 예외가 발생한다면 rollback() 함수를 통해서 트랜잭션의 단위 통째로 취소해버리게 되는 것이죠.
만약 에러가 발생하지 않는다면 commit()을 통해서 트랜잭션을 종료해주면 됩니다.
따라서 우리가 트랜잭션 단위로 처리하고 싶은 작업을 try 구문 안에 넣어주면 됩니다.
그럼 이제 다시 Member 클래스의 객체를 만들어줌으로써 컬럼을 채워봅시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setId(1L);
member.setName("이민재");
member.setAge("26");
tx.commit()
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
main을 실행 시키면 어떻게 될까요?
이상하게도 객체를 만들어주는 작업을 트랜잭션에 넣어 오류없이 완료했음에도 불구하고 테이블에 컬럼이 그대로 비워져있습니다. 왜일까요?
여기서 필요한 것이 바로 영속성 컨텍스트입니다.
영속성 컨텍스트
방금 코드를 사용해서 데이터 베이스에 접근 하려면 어떻게 해야할까요?
이를 영속성 컨텍스트가 관리하도록 해주면 됩니다.
아직 코드를 살펴보면 객체를 만들기만 했지, 이 객체에 대해서 매니저가 어떠한 행동도 취하지 않은 것을 볼 수 있습니다.
다음과 같은 코드를 추가해보겠습니다.
1
em.persist(member)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setId(1L);
member.setName("이민재");
member.setAge("26");
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
이제 위에서 만든 객체를 em 매니저가 영속성 컨텍스트에 추가하게 됩니다. 그럼 이 상태로 main 함수를 한번 실행시켜 볼까요?
이제 정상적으로 만들어준 객체가 컬럼으로 등록되었습니다!
방금 코드에서 em.persist()를 추가하니까 컬럼에 올라갔지만, 사실 persist()를 했을 때 데이터베이스에 접근한 것을 아닙니다. persist는 말그대로 영속성(Persist) 이므로, member라는 객체를 영속성 컨텍스트가 관리하게 해준것이고, 이를 commit() 함수가 실행되었을 때 데이터베이스에 등록하게 됩니다.
따라서 commit()은 영속성 컨텍스트에 의해서 관리 받는 엔티티만을 데이터 베이스에 등록하는 것을 알 수 있습니다.
그렇다면 영속성 컨텍스트란 무엇일까요??
JPA에서 엔티티는 아래와 같은 생명주기를 갖습니다.
위 그림에서 적혀 있는 상태를 4가지로 구분해보자면 다음과 같습니다.
- 비영속 (New)
- 영속 (Managed)
- 준영속 (Detached)
- 삭제 (Removed)
하나씩 설명해 보자면,
비영속이 바로 우리가 아까 객체를 생성한 상태입니다. 아까 코드에서 persist() 를 쓰지않았을때의 객체가 바로 이 상태로, 객체는 생성되었지만 이를 아직 영속상태에 놓지 않았기 때문에 commit()이 되어도 데이터베이스에 기록되지 않습니다.
영속은 persist()를 사용해서 해당 엔티티를 영속성 컨텍스트가 관리 하도록 만든 상태입니다. 이 상태에서 commit()이 되면 데이터 베이스에 해당 변경 사항을 기록하게 되는 것이죠.
영속은 쉽게 말하면 예비 출전 상태라고 생각하면 쉽습니다. 만약 전쟁이 일어났다면, 바로 집에서 전쟁터로 직행하지 않겠죠? 정해진 장소로 우선 모아서 다 모였는지, 아픈 사람은 없는지, 전략은 완벽한지를 모두 체크하고 한번에 모아서 전쟁터로 출전하게 됩니다. 굳이 집에서 한명씩 비효율적으로 전쟁터로 직행 할 필요가 없죠.
준영속은 detach() 함수를 사용해서 만드는 상태입니다. 사실 이 상태는 개발자가 직접 준영속을 만들 경우는 거의 없습니다. 말그대로 영속 컨텍스트에 올라간 엔티티를 다시 떼내어서 영속 상태가 아니도록 만드는 것입니다.
삭제는 말그대로 삭제 상태입니다. 영속컨텍스트에서 떼어내서 commit()에서만 제외하여 DB에 반영하지 않는 준영속과 달리, 실제 DB에서의 삭제를 요청한 상태입니다.
그럼 이제 영속성 컨텍스트를 알게 되었으니, JPA를 사용해서 객체를 만들고 이를 영속상태로 만들어 준 뒤 트랜잭션을 커밋해주면, 우리가 원하는 데이터를 객체지향스럽게 데이터베이스에 기록 할 수 있음을 알게 되었습니다.
그런데 왜 굳이 바로 데이터베이스에 접근하지 않고 영속성 컨텍스트에 올린 뒤에 데이터베이스에 접근 하는 것일까요?
그것은 바로 영속성 컨텍스트를 사용하면 여러가지 데이터를 효율적으로 관리할 수 있는 이점들이 있기 때문입니다.
그러면 어떠한 이점들이 있길래 굳이 한번의 과정을 추가한 것일까요?
이러한 이점들은 다음 게시물에서 알아보도록 하겠습니다.
댓글남기기