Home Persistence Context
Post
Cancel

Persistence Context

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으로 모든것을 막아서 사용한다.

연동 확인

  • test의 contextLoad를 실행해보면 기존 H2Dialect에서 MYSQL57Dialect로 변경된 것을 확인 할 수 있다.

test

  • 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에 반영되는 시점

  1. flush를 사용 하였을 때
  2. 하나의 Transaction이 종료 되었을 때
    • Logic에 @Transactional을 작성하면 해당 Logic들이 하나의 Transaction이 된다.
    • @Transactional을 작성하지 않으면 save같은 method의 구현체에서 @Transactional이 작성 되어 있기 때문에 해당 method가 종료 되는 시점에 AutoFlush가 된다.
  3. 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발생
          }
      }
    
This post is licensed under CC BY 4.0 by the author.