프레임워크/Spring & Spring boot
[JPA] 학습테스트로 알아보는 영속과 준영속 (feat. EntityManager)
GOMSHIKI
2024. 7. 2. 14:48
반응형
테스트 환경
의존성
- spring-boot-starter
- spring-boot-starter-data-jpa
- spring-boot-start-test
- h2 database
plugins {
id 'org.springframework.boot' version '2.7.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'com.h2database:h2'
}
test {
useJUnitPlatform()
}
관련 Class
- Entity : Customer
- Test class : PersistentceContextTest
// Test class
@DataJpaTest
class PersistenceContextTest {
@PersistenceContext
private EntityManager em;
@Autowired
private JdbcTemplate jdbcTemplate;
...
// DB 데이터 확인을 위한 메서드
private List<Customer> getCustomersFromDb() {
final String sql = "SELECT * FROM customer";
List<Customer> customers2 = jdbcTemplate.query(sql,
(resultSet, rowNum) -> new Customer(
resultSet.getLong("id"),
resultSet.getString("firstname"),
resultSet.getString("lastname")
));
return customers2;
}
}
// Customer
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstname;
private String lastname;
public Customer() {
}
public Customer(String firstname, String lastname) {
this(null, firstname, lastname);
}
public Customer(Long id, String firstname, String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
public Long getId() {
return id;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public void updateName(String newFirstname, String newLastname) {
this.firstname = newFirstname;
this.lastname = newLastname;
}
}
application.properties
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
영속 상태와 준영속 상태를 테스트 코드를 통해 확인해 보자
1) persist( ) - 객체를 영속화
- 영속 상태는 영속성 콘텍스트가 관리하는 객체를 의미
- 객체를 영속화시키기 위해 persist 메서드를 활용
- 영속성 콘텍스트가 관리한다고 해서 DB에 저장된다는 의미는 아님
- 콘솔창에서 persist 메서드가 호출될 때 insert 쿼리가 실행되지 않는 것을 확인할 수 있음
@Test
void persist(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
assertThat(em.contains(customer)).isFalse();
// new -> Managed (영속성 컨텍스트가 관리하게됨)
em.persist(customer);
assertThat(em.contains(customer)).isTrue();
// persist()로 Managed 상태가 되더라도 DB에는 저장되지 않음.
List<Customer> customers = getCustomersFromDb();
assertThat(customers).hasSize(0);
}
2) flust( ) - 영속 상태에서 DB에 반영
- flush를 통해 영속성 콘텍스트에 있는 객체를 DB에 저장 - insert query 수행
- flush 전 후로 DB 조회 결과가 다른 것을 확인할 수 있음
- flush 하더라도 영속성 컨텍스트에 해당 객체는 남아있음
@Test
void flush(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
// Managed (영속 상태)
em.persist(customer);
List<Customer> customers = getCustomersFromDb();
// persist() 상태는 DB에 저장되지 않음
assertThat(customers).hasSize(0);
// flush() 로 인해 insert query 가 날라감
em.flush();
List<Customer> customers2 = getCustomersFromDb();
assertThat(customers2).hasSize(1);
}
3) detach( ) : 영속상태 → 준영속상태
- 영속 상태였다가 준영속 상태가 되면 영속성 콘텍스트에서는 찾을 수 없음
- 준영속 상태는 식별자(member.id)는 있지만 영속성 컨텍스트에서 찾을 수 없는 상태를 의미
@Test
void detach(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
// persist() 로 영속상태(영속성 컨텍스트에서 해당 객체를 들고 있음)
em.persist(customer);
assertThat(em.contains(customer)).isTrue();
// 영속상태 -> 준영속 상태
em.detach(customer);
assertThat(em.contains(customer)).isFalse();
}
4) merge( ) : 준영속상태 영속상태
- 준영속 상태에서 다시 영속 상태로 변경하기 위해서는 merge 메서드를 활용
@Test
void merge(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
em.persist(customer); // 영속 상태
em.flush(); // insert 쿼리 수행
// 준영속 상태에서 객체 상태 변경 후 다시 영속상태를 만든다
em.detach(customer); // 영속 -> 준영속
customer.updateName("new firstname", "new lastname"); // 객체의 상태 변경
em.merge(customer); // 준영속 -> 영속 상태
em.flush(); // update 쿼리 수행
List<Customer> customers = getCustomersFromDb();
assertThat(customers.get(0).getFirstname()).isEqualTo("new firstname");
}
5) remove( ) : 영속 -> 삭제
- 영속 상태에서 삭제를 하기 위해서는 remove()를 사용
- remove()만 호출한 경우 영속성 콘텍스트에서만 삭제
- DB에도 삭제가 반영이 되려면 flush()를 해야 함
@Test
void remove(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
em.persist(customer); // 영속 상태
em.flush(); // insert 쿼리 수행
assertThat(em.contains(customer)).isTrue(); // 영속상태
em.remove(customer); // Persistence Context에서 해당 객체 삭제, DB는 삭제 X
List<Customer> customers = getCustomersFromDb();
// DB에 여전히 데이터가 있음
assertThat(customers.get(0).getFirstname()).isEqualTo("firstname");
assertThat(em.contains(customer)).isFalse(); // 영속상태가 아님
em.flush(); // delete 쿼리 수행
List<Customer> customers1 = getCustomersFromDb();
assertThat(customers1).hasSize(0); // DB 데이터 삭제
}
영속 상태와 준영속 상태에서 객체 상태 변경 시 DB 반영 여부
1) 준영속 상태에서 객체 상태 변경
- Update 쿼리를 수행하지 않음.
@Test
void detachAgainstPersist(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
em.persist(customer); // 영속상태
em.flush(); // DB 저장
em.detach(customer); // 준영속 상태
customer.updateName("new firstname", "new lastname"); // 객체의 변경 update
em.flush(); // flush()하더라도 update 쿼리가 날라가지 않음.(due to 준영속상태)
List<Customer> customers = getCustomersFromDb();
assertThat(customers.get(0).getFirstname()).isEqualTo("firstname");
}
2) 영속 상태에서 객체 상태 변경
- Update 쿼리 수행
@Test
void persistAgainstDetach(){
// 객체 생성
Customer customer = new Customer("firstname", "lastname");
em.persist(customer); // 영속 상태
em.flush(); // insert 쿼리 수행
customer.updateName("new firstname", "new lastname");
em.flush(); // update 쿼리 수행 (due to 영속상태)
List<Customer> customers = getCustomersFromDb();
assertThat(customers.get(0).getFirstname()).isEqualTo("new firstname");
}
이상 전달 끝!
반응형