[JPA] 영속성 컨텍스트

jpa, spring

영속성 컨텍스트 #


MYSQL DB 설정하기 #

application.yml setting

spring:
  h2:
    console:
      enabled: true
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true # 압축된 쿼리를 읽기 좋게 포멧
    generate-ddl: true # 자동으로 entity에서 활용하고있는것을 테이블을 만듦
    hibernate:
      ddl-auto: create-drop # schema.sql이 존재할시 무시됨
    # defer-datasource-initialization: true # for h2 database
  datasource:
    url: jdbc:mysql://localhost:3306/book_manager
    username: root
    password: 1234
server:
  port: 8080

이 후에 contextload test를 해보면 MySQL8Dialect 결과값이 나옴.

Entity Cache #

영속성 컨텍스트 내부에는 캐시가 있는데 이를 1차 캐시라고 한다. 영속 상태의 엔티티를 이곳에 저장한다. 1차 캐시의 키는 식별자 값(데이터베이스의 기본 키)이고 값은 엔티티 인스턴스이다.

@SpringBootTest
public class EntityManagerTest {
    
    @Autowired
    private EntityManager entityManager;

    @Test
    void entityManagerTest() {
        System.out.println(entityManager.createQuery("select u from User u").getResultList());
    }
}

아래와 같은 쿼리를 동작시키는 내용.

userRepository.findAll();

쓰기지연 #

엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 DB에 보낸다. 이것을 트랜잭션을 지원하는 쓰기 지연이라 한다.


flush() #


Entity Life Cycle #

Entity 생애주기 알아보기

비영속 상태 #

@Service
public class UserService {

    @Transactional
    public void put() {
        User user = new User();
        user.setName("new user");
        user.setEmail("newuser@email.com");
    }
}

위와 같이 service를 하나 만들고 아래와 같이 testing을 진행한다면, 영속화되지 않음.

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Test
    void testPut() {
        userService.put();

        System.out.println(">>> " + userRepository.findByEmail("newuser@email.com"));
    }
}

영속화 방법 1. Service에 userRepository 추가하고 save 진행 #

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void put() {
        User user = new User();
        user.setName("new user");
        user.setEmail("newuser@email.com");
        
        userRepository.save(user);
    }
}

영속화 방법 2. EntityManager를 활용하여 persist 진행 #

@Service
public class UserService {
    @Autowired
    private EntityManager entityManager;

    @Transactional
    public void put() {
        User user = new User();
        user.setName("new user");
        user.setEmail("newuser@email.com");        

        entityManager.persist(user);
    }
}

영속화 방법 3. EntityManager가 알아서 영속화 진행 #

@Service
public class UserService {

    @Autowired
    private EntityManager entityManager;

    @Transactional
    public void put() {
        User user = new User();
        user.setName("new user");
        user.setEmail("newuser@email.com");        

        entityManager.persist(user);

        user.setName("newUserAfterPersist");
    }
}

한번의 persist 후에 user.setName("newUserAfterPersist") 은 영속화가 되지않아 DB에 반영되지 않을것같지만, transaction이 종료되는 시점에 별도로 persist 메서드를 호출하지 않더라도 entity manager이 알아서 update 쿼리를 해서 영속화 시킴.


준영속상태 : detached #

원래 영속화되었던 객체를 분리(detach)해서 영속성 컨텍스트 밖으로 꺼내는 동작.

@Service
public class UserService {

    @Autowired
    private EntityManager entityManager;

    @Transactional
    public void put() {
        User user = new User();
        user.setName("new user");
        user.setEmail("newuser@email.com");        

        entityManager.persist(user);
        entityManager.detach(user);

        user.setName("newUserAfterPersist");
        entityManager.merge(user); // (1)
        
        entityManager.clear(); // (2)
    }
}
@Service
public class UserService {
    @Autowired
    private EntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void put() {
        ...
        User user1 = userRepository.findById(1L).get();
        entityManager.remove(user1);
    }
}
    @Test
    void removeTest() {
        userService.put();
        userRepository.findAll().forEach(System.out::println);
    }

예를 들어 위와 같이 Id가 1번인 user1을 repo에서 꺼내와 remove를 실행하여 test에서 findAll을 실행하면 id=1의 값이 지워져서 나오지않음.