[Spring] 영속성

yoon·2024년 1월 26일

spring-boot

목록 보기
14/41
post-thumbnail

✅ 영속성(persistence)

✔ 영속성 컨텍스트

JPA(Java Persistence API)에서 의미하는 persistence란 한글로 번역하면 영속성, 지속성이라는 뜻이다.
객체의 관점에서 보면 '객체의 생명이나 공간을 자유롭게 유지하고 이동할 수 있는 객체의 성질'을 의미한다.

JPA는 일련의 과정을 효율적으로 처리하기 위해 '영속성 컨텍스트'라는 공간에 객체들을 저장하고 관리하면서 DB와 소통한다.

이점
애플리케이션과 데이터베이스 사이에 중간계층을 만들어 버퍼링, 캐싱등을 할 수 있는 장점이 생긴다

✔ EntityManager

영속성 컨텍스트에 접근하여 객체들을 조작하기 위해서는 EntityManager가 필요하다.
EntityManager는 EntityManagerFactory를 통해 생성하여 사용할 수 있다.
EntityManagerFactory는 일반적으로 DB하나에 하나만 생성되어 애플리케이션이 동작하는 동안 사용된다.

🔎 db 정보 전달
/resources/META-INF/ 위치에 persistence.xml 파일을 만들어 db 정보를 전달한다.

// persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="memo">
        <class>com.ss.entity.Memo</class>
        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="jakarta.persistence.jdbc.user" value="root"/>
            <property name="jakarta.persistence.jdbc.password" value=""/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/db"/>

            <property name="hibernate.hbm2ddl.auto" value="update" />

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

다음 코드를 통해 EntityManager를 생성하여 사용할 수 있다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");
EntityManager em = emf.createEntityManager();

✔ JPA 트랜잭션

트랜잭션은 db데이터의 무결성과 정합성을 유지하기 위한 논리적 개념이다.
여러 개의 SQL이 하나의 트랜잭션에 포함될 수 있다.
이때, 모든 SQL이 성공적으로 수행되면 db에 영구적으로 반영하지만 단 하나라도 실패한다면 모든 변경사항을 되돌린다.

JPA의 영속성 컨텍스트에서도 변경이 발생한 객체들의 정보를 쓰기 지연 저장소(Action Queue)에 전부 가지고 있다가 마지막에 SQL을 한번에 db에 요청해 변경을 반영한다.

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


public class EntityTest {
	EntityManagerFactory emf;
    EntityManager em;
    
    @BeforeEach //각각의 테스트 코드가 실행되기 전에 실행됨
    void setUp() {
        emf = Persistence.createEntityManagerFactory("memo");
        em = emf.createEntityManager();
    }
    
    @Test
    void test1(){
    	EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.
        et.begin();
        try{
        	...
        	em.persist(객체); //EntityManager 사용하여 객체를 영속성 컨텍스트에 저장
        	et.commit(); //오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출
        
        } catch(){
        	...
            //DB 작업 중 오류 발생 시 rollback 을 호출
            et.rollback();
        } finally {
        	em.close(); //사용한 EntityManager 종료
        }
        emf.close(); // 사용한 EntityManagerFactory 를 종료
    }

}

✅ 영속성 컨텍스트 기능

✔ 1차 캐시

영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있다.
캐시 저장소는 Map 자료구조 형태로 되어있다.

  • key에는 @Id로 매핑한 기본 키, 식별자 값 저장
  • value에는 해당 Entity 클래스의 객체 저장

1. entity 저장

em.persist(객체) 메서드가 호출되면 객체를 캐시 저장소에 저장한다.

debugger를 통해 위의 정보를 확인할 수 있다.

2. entity 조회

em.find() 메서드가 호출되면 우선 캐시 저장소에서 해당 객체를 찾는다.
만약 캐시 저장소에 없다면 db select를 이용하여 객체를 조회하고, 캐시 저장소에 저장한다.

3. entity 삭제

삭제할 객체를 조회한 후 캐시 저장소에 없다면 db에 조회해서 저장한다.
em.remove() 메서드를 호출하면 객체를 deleted상태로 만든 후 트랜잭션 commit 후 delete sql이 db에 요청된다.

debugger를 통해 정보를 확인해보면 MANAGED > DELETED로 변하는 과정을 볼 수 있다.

4. 1차 캐시 장점

  • db 조회 횟수 ↓
  • 1차 캐시를 사용해 db row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)

✔ 쓰기지연장소(action queue)

et.commit() 을 실행하기 전에는
em > actionQueue를 확인해보면 insertions > executables에 저장한 객체가 담겨있는 것을 볼 수 있다.

et.commit()을 호출하면 insert sql이 순차적으로 실행된다.

◾ flush()

영속성컨텍스트 변경내용을 데이터베이스에 반영하는 역할 수행한다.
즉, 쓰기 지연 장소에 있는 sql들을 db에 요청해주는 메서드이다.

보통 et.commit()이 호출되면 flush()를 자동 호출한다.
em.flush()를 직접 호출하면 바로 sql이 요청된다.

❌트랜잭션을 설정하지 않고, flush 메서드를 호출하면 오류가 발생한다. Insert, Update, Delete와 같이 데이터 변경 SQL을 DB에 요청하려면 반드시 트랜잭션 설정을 해줘야한다.


✔ 변경감지(Dirty Checking)

객체가 변경될 때 마다 Update SQL을 쓰기 지연 저장소에 저장한다면, 하나의 SQL로 처리할 수 있는 상황을 여러번으로 나눠서 진행하게 되어 비효율적이다.

그래서 em.update()와 같은 메서드는 지원하지 않는다.

그렇다면 어떻게 변경을 감지할까?

JPA에서는 변경하고 시은 데이터가 있다면 먼저 데이터를 조회하고, 해당 객체의 데이터를 변경하면 자동으로 update SQL을 생성하여 db에 반영한다.

반드시 트랜잭션 환경을 만들어줘야한다.

profile
하루하루 차근차근🌱

0개의 댓글