JAVA/spring-JPA

Spring JPA : 영속성 컨텍스트 맛보기

Berno 2023. 7. 12. 20:19
728x90
반응형

0. 이 글에서 정리하고자 하는 내용

- 영속성 컨텍스트란 무엇인지

- EntityManager가 무엇인지

 

 

1. 개발 환경 준비

1-1. H2 설치 및 연동

http://h2database.com/html/main.html 

 

H2 Database Engine

H2 Database Engine Welcome to H2, the Java SQL database. The main features of H2 are: Very fast, open source, JDBC API Embedded and server modes; in-memory databases Browser based Console application Small footprint: around 2.5 MB jar file size     Supp

h2database.com

각 환경에 맞는 h2를 설치한다.

설치된 h2에서 bin디렉토리에 있는 h2.sh로 실행한다.

초기 db파일을 생성하기 위해서는 tcp모드가 아닌 일반 모드로 실행을 하고 이후 tcp모드로 전환한다.

 

1-2. Spring 준비

start.spring.io(https://start.spring.io/)에서 아래 라이브러리를 추가한 프로젝트를 다운받는다.

최종적으로 내 gradle은 이렇게 나온다.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.1'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.cvs'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

application.yml은 다음과 같이 설정했다.

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/Desktop/toy/cvs/store
    username: sa
    password:
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        show_sql: true
        format_sql: true
#        default_batch_fetch_size: 100
    logging:
      level:
        org.hibernate.SQL: debug
        org.hibernate.type: trace

 

 

2. 영속성 컨텍스트

이 말을 이해하기 위한 배경으로는 jpa가 무엇인지를 생각해봐야 한다.

 

2-1. JPA와 ORM

JPA : Java Persistence API

이 Persistence(영속성)이라는게 처음 접하면 상당히 생소한 개념이다.

나는 이걸 같은 내용이지만 다른 문장으로 이해를 했다.

"저장하는 성질"

즉 데이터를 저장하는 API다.

그러면 도대체 JPA를 사용하는 것이 왜 좋은가?

이 모든 것을 자동으로 해주기 때문이다. 변경 감지(dirty check)부터 저장까지 원래대로면 사용자가 직접 쿼리를 날려서 db에 저장을 해야 했지만 이 프로세스를 편하게 구축해 둔 API다. 

그러면 이게 코드 상에서는 어떻게 흘러가는가? 우리는 이걸 ORM이라고 부른다.

ORM : Object Relationship Mapping

즉 객체와 관계를 맺어 각 데이터를 객체 단위로 다루는 것이다.

 

다시 정리를 하면 JPA는 java에서 사용하는 ORM 기술이다.

 

2-2. 코드와 함께

나는 앞으로 편의점을 운영하는 백엔드 서비스를 구축하려고 한다.

아래와 같이 직원에 대한 엔티티가 존재한다고 생각하자.

@Entity
@Getter
public class Staff {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "role")
    @Enumerated(value = EnumType.STRING)
    private StaffRole role;
}

직원에 대해 식별 id, 이름, 역할로 구분한다고 생각하자.(시급까지 들어가면 좋겠지만 최대한 간단하게 가져가려고 한다.)

 

그러면 이게 db에 저장이 되어야 할 텐데 저장을 할 수 있는 코드를 작성해 보자.

 

public interface StaffRepository {

    Long save(Staff staff);
}
@Repository
@RequiredArgsConstructor
public class StaffRepositoryImpl implements StaffRepository{

    private final EntityManager em;
    @Override
    public Long save(Staff staff) {
            em.persist(staff);
            return find(staff.getId()).getId();
    }

    @Override
    public Staff find(Long id) {
        return em.find(Staff.class, id);
    }


}

여기서 save를 위해 EntityManager에서 persist를 호출하고 있다.

EntityManager는 말 그대로 Entity들을 관리하는 매니저 클래스이고 persist는 일반 Entity객체를 영속성 컨텍스트 객체로 저장하기 위한 함수이다. 그렇다면 이 저장이 정상적으로 이루어지는지 테스트를 해보자.

 

@SpringBootTest
@Transactional
class StaffRepositoryImplTest {


    @Autowired
    private StaffRepository staffRepository;

    @Test
    public void 직원_저장_테스트() throws Exception{
        // given
        Staff staff1 = new Staff("직원1", StaffRole.NORMAL);
        Staff staff2 = new Staff("매니저1", StaffRole.MANAGER);
        // when
        Long staff1Id = staffRepository.save(staff1);
        Long staff2Id = staffRepository.save(staff2);
        // then
        Assertions.assertEquals(staff1.getId(), staff1Id);
        Assertions.assertEquals(staff2.getId(), staff2Id);
    }

}

여기서 쿼리에 집중해 보자.

Hibernate에서 insert문이 호출되고 있다. EntityManager를 사용하면 실제 db까지 자동으로 반영을 해주고 있다. 해당 쿼리는 persist가 호출되며 발생한 쿼리이다. 즉 우리가 만든 java객체가 실제 db까지 반영이 된다는 것이다.

 

 

그렇다면 이 객체는 동일 객체일까?

 

    @Test
    public void 직원_영속성_객체_테스트() throws Exception{
        // given
        Staff staff1 = new Staff("직원1", StaffRole.NORMAL);
        Staff staff2 = new Staff("매니저1", StaffRole.MANAGER);
        // when
        Long staff1Id = staffRepository.save(staff1);
        Long staff2Id = staffRepository.save(staff2);

        Staff findStaff1 = staffRepository.find(staff1Id);
        Staff findStaff2 = staffRepository.find(staff2Id);

        System.out.println("staff1 :: " + staff1);
        System.out.println("findStaff1 :: " + findStaff1);

        System.out.println("staff2 :: " + staff2);
        System.out.println("findStaff2 :: " + findStaff2);

        // then
        Assertions.assertEquals(staff1, findStaff1, "staff1의 해쉬코드가 일치해야합니다.");
        Assertions.assertEquals(staff2, findStaff2, "staff2의 해쉬코드가 일치해야합니다.");
    }

 

 

동일하다. 이 객체를 가지고 EntityManager에서 영속성 객체로 관리를 하고 있기 때문이다.

 

그렇다면 이 객체의 라이프 사이클은 어떻게 될까?

오라클 공식 문서를 살펴보았다.

https://docs.oracle.com/cd/E29542_01/apirefs.1111/e13946/ejb3_overview_em_lifecycle.html

비영속 상태(New/Transient)

우선 최초에 우리가 만든 객체는 비영속 상태로 존재한다.

 

영속 상태(Managed)

이 비영속 상태의 객체에서 EntityManager에 persist를 통해 객체를 영속성 컨텍스트 객체로 등록한다. 해당 상태가 Managed상태이다.

 

준영속 상태(Detached)

객체가 사라지지는 않았지만 EntityManager의 관리에서 벗어나 영속성 컨텍스트 객체가 아니게 된 상태이다.

 

삭제 상태(Removed)

해당 객체가 정말로 삭제가 된 상태이다. 이는 DB에서 Data까지 삭제됨을 말한다.

 

 

3. 요약 및 마무리

EntityManager는 Entity객체에 대해 영속성 컨텍스트로 관리를 하기 위한 매니저 클래스이다.

영속성 컨텍스트가 된 객체는 EntityManager에서 컨텍스트 생명주기를 관리함에 따라 각 변동이 발생할 시 반영까지 해주며 persist가 된 객체와 find로 찾아낸 객체는 동일 객체이다.

 

 

글을 작성하면서 코드는 현재 보시는 글 보다 템포가 더 빠르게 같이 만들어지고 있습니다.
https://github.com/0113bernoyoun/jpa-cvs

 

728x90
반응형