Persistence Context(영속성 컨텍스트)
- Context는 프레임워크에서 주로 컨에티너들이 관리하고 있는 내용을 말하며 Persistence Context는 말그대로 Persistence Container가 관리하는 내용을 말한다.
- persistence Context의 가장 핵심은 javax.persistence의 EntityManager 이다.
- Persistence란 사라지지 않고 지속적으로 유지되는 것으로 DB, File등에 저장해 유지하는것을 말한다.
- resources.META-INF.persistence.xml 로 persistence Context의 설정을 할 수 있다.
mysql연동
Dependency
1
runtimeOnly 'mysql:mysql-connector-java'
Application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
spring: h2: console: enabled: true jpa: show-sql: true properties: hibernate: format_sql: true defer-datasource-initialization: true generate-ddl: true #추가 hibernate: #추가 ddl-auto: create datasource: url: jdbc:mysql://localhost username: root password:
- datasource
- url은 기본적으로 jdbc를 가장 앞에 작성해 주는것으로 jdbc api를 사용함을 나타내고, db client driver에 대한 내용을 작성해준다. 이후 마지막에 DB의 url주소를 입력하면 된다.
- generate-ddl
- 자동으로 entity에서 사용하는 table들을 생성해주는 옵션
- hibernate: ddl-auto
- none : ddl-auto를 실행하지 않는 옵션
- create : 항상 새로 생성하는 옵션으로 실행할 때 기존 Table들을 Drop한다.
- create-drop : 생성하고 persistece context가 종료될 때 자동으로 Drop한다.
- update : 실제 스키마와 entity class를 비교해서 변경된 부분만 변경한다.
- ddl-auto가 설정되어 있다면 generate-ddl 옵션은 무시된다.
- generate-ddl은 jpa구현체와 상관없는 범용적은 옵션이고 ddl-auto는 hibernate에서 제공하는 조금 더 세밀한 옵션이다.
- 실제 운영 DB의 경우 자동화 된 ddl구문을 사용하는 것은 위험하므로 false, none으로 모든것을 막아서 사용한다.
- datasource
연동 확인
- test의 contextLoad를 실행해보면 기존 H2Dialect에서 MYSQL57Dialect로 변경된 것을 확인 할 수 있다.
- Dialect
- entity나 repository에서 사용하는 ORM을 실제로 DB Query로 변환해서 JDBC를 통해 DB로 전달하게 되는데 DB별로 Query가 다르므로 DB에 맞게 생성되어야 한다.
- 이러한 작업을 해주는 것이 Dialect이다.
Transactional
- 해당 annotation이 있으면 Test Method가 종료 될 때마다 처리한 데이터를 모두 rollback해준다.
- 만약 Transactional옵션이 없다면 각 Test method에서 save한 데이터들이 모두 db에 남아있어 Test가 실패 할 수 있다.
Entity Cache
Entity Manager
- Context안에서 Entity는 생성되고, 조회 되고, 삭제 된다. 이러한 context내에서 가장 중요한 것이 Entity Manager라는 객체이다.
Entity Manager는 JPA에서 정의하고 있는 interface이다. Query를 위한 Method들이 정의되어 있고, Bean으로 등록 되어 있기 때문에 AutoWired를 통해 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import javax.persistence.EntityManager; @SpringBootTest @Transactional public class EntityManagerTest { @Autowired private EntityManager entityManager; @Test void entityManagerTest(){ System.out.println(entityManager.createQuery("select u from User u").getResultList()); } }
- 위의 code는 userRepository.findAll()과 같은 동작을 수행한다.
- simpleJpaRepository는 개발자가 직접 entityManager을 사용하지 않아도 되도록 Wrapping을 한 것이다.
Entity Cache
Persistence Context내의 entity들을 관리하는 EntityManager는 Entity Cache를 가지고 있다.
1 2 3 4 5 6 7 8 9
@Test void Test1(){ System.out.println(userRepository.findByEmail("kmslkh@naver.com")); System.out.println(userRepository.findByEmail("kmslkh@naver.com")); System.out.println(userRepository.findByEmail("kmslkh@naver.com")); System.out.println(userRepository.findById(1L).get()); System.out.println(userRepository.findById(1L).get()); System.out.println(userRepository.findById(1L).get()); }
- @Transactional 된 위의 Test를 실행해 보면 findById는 실제 DB로 Query는 1번만 보내진다. 따로 cache처리 하지 않았지만 persistence context에서 자동으로 entity에 대해 cache처리 하는 것을 JPA의 1차 cache라고 말한다.
- findByEmail같은 경우 3번의 Query가 모두 DB로 보내진다.
- 이는 1차 cache가 id(key), entity(value)의 map형태로 저장 되기 때문이다.
- key가 id이기 때문에 Id로 조회할 때만 Cache에서 id로 entity가 있는지 확인하고 바로 반환 해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
void Test2(){
User user = userRepository.findById(1L).get();
user.setName("ssssssssss");
userRepository.save(user);
user.setEmail("sadasdasdasd@naver.com");
userRepository.save(user);
userRepository.flush();
}
- 위의 코드에서 실제 update Query는 flush한 순간에 한번만 수행된다. (Transactional을 설정했기 때문에, 만약 설정하지 않았다면 각 save Method에 Transactional이 작성 되어 있기 때문에 save method 마다 db에 반영한다.) → @Transactional을 작성했다면 각 method가 하나의 Transaction이 되는데 위의 경우 Test이기 때문에 method가 끝나면 Rollback되어 update가 되지 않기 때문이다.
- flush는 DB에 아직 반영되지 않은 영속성 캐시에 있는 Entity 변경을 해당 method실행 시점에 DB에 반영하게 한다.
- 너무 많이 사용하면 Cache의 장점을 사용 할 수 없다.
Cache의 내용이 DB에 반영되는 시점
- flush를 사용 하였을 때
- 하나의 Transaction이 종료 되었을 때
- Logic에 @Transactional을 작성하면 해당 Logic들이 하나의 Transaction이 된다.
- @Transactional을 작성하지 않으면 save같은 method의 구현체에서 @Transactional이 작성 되어 있기 때문에 해당 method가 종료 되는 시점에 AutoFlush가 된다.
- ID값이 아닌 JPQL query가 실행 될 때
- JPQL query를 사용할 때 캐시와 DB의 데이터를 비교해 merge하는 것이 아니라 캐시를 모두 flush한 뒤 DB에 Query를 보낸다.
Entity LifeCycle
비영속 상태(New, Transient)
- 영속성 컨텍스트가 해당 객체를 관리하고 있지 않은 상태
- @Transient Annotation으로 해당 필드를 영속화에서 제외할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class UserService {
private EntityManager em;
public UserService(EntityManager em) {
this.em = em;
}
@Transactional
public void lifeCycle(){
User user = new User();
user.setName("newUser");
user.setEmail("newUser@naver.com");
}
}
- 위의 put method안의 user객체는 비영속 상태이다. put method가 종료되면 GC에 의해 삭제 될 객체이다.
- entityManager.persist(user)를 통해 해당 객체를 영속 상태로 만들 수 있다.
- repository객체로 save를 할 때 내부 구현에 em을 통해 persist를 수행하기 때문에 repository구현체를 통해 save만 해도 영속 상태가 된다.
영속 상태(Managed)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class UserService {
private EntityManager em;
public UserService(EntityManager em) {
this.em = em
}
@Transactional
public void lifeCycle(){
User user = new User();
user.setName("newUser");
user.setEmail("newUser@naver.com");
em.persist(user);
user.setName("testtest");
}
}
- 영속성 컨텍스트 내부에서 관리되는 객체는 setter를 통해 객체의 정보가 변경된 경우 Transaction이 완료 되는 시점에 별도의 save없이 DB데이터와 정확성을 맞춰준다.
- 위의 코드를 수행했을 때 Query를 보면 save를 하지 않았음에도 update Query가 실행 되는 것을 볼 수 있다. → 영속성 컨텍스트가 제공하는 dirty check라는 기능
준영속 상태(Detached)
- 원래 영속화 되었던 객체를 분리해 영속성 컨텍스트 밖으로 꺼내는 동작
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class UserService {
private EntityManager em;
public userService(EntityManager em) {
this.em = em;
}
@Transactional
public void lifeCycle(){
User user = new User();
user.setName("newUser");
user.setEmail("newUser@naver.com"); //현재까지 비영속 상태
em.persist(user); // 영속화
em.detach(user);
user.setName("testtest");
em.merge(user);
}
}
- detach로 준영속 상태로 만들었기 때문에 setName으로 인한 updateQuery는 발생하지 않는다.
- 준영속 상태의 Entity는 merge를 통해 명시적으로 데이터 반영을 시킬 수 있다.
clear()을 통해서도 준영속 상태를 만들 수 있지만 clear를 통해 하면 이전까지의 변경 사항을 모두 지워버린다. 따라서 clear를 통해 준영속 상태로 만들 때에는 이전의 변경 사항을 저장 하고싶다면 flush를 먼저 실행해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Service public class UserService { private EntityManager em; public userService(EntityManager em) { this.em = em; } @Transactional public void lifeCycle(){ User user = new User(); user.setName("newUser"); user.setEmail("newUser@naver.com"); //현재까지 비영속 상태 em.persist(user); // 영속화 em.detach(user); user.setName("testtest"); em.merge(user); em.clear(); } }
- persist로 인한 insert query는 실행 되지만 merge로 인한 update는 clear로 삭제되어 적용되지 않는다.
삭제 상태(Removed)
remove를 통해 삭제 상태로 만들 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
@Service public class UserService { private EntityManager em; public UserService(EntityManager em) { this.em = em; } @Transactional public void lifeCycle(){ User user1 = userRepository.findById(1L).get(); em.remove(user1); user1.setName("asdasda"); //em.merge(user1); -> error발생 } }