1. 영속성 컨테스트 ( Persistence )
영속성, 지속성 이라는 뜻이 됩니다.
즉, Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간
이제 직접 SQL을 작성하지 않아도 JPA를 사용하여 DB에 데이터를 저장, 조회, 수정, 삭제 또한 가능합니다.
2. EntityManager ( Entity를 관리 )

영속성 컨텍스트에 접근하여 Entity 객체들을 조작하기 위해서는 EntityManager가 필요합니다.
EntityManager를 사용해서 Entity를 저장, 조회, 수정,삭제 할수 있음.
EntityManager는 EntityManagerFactory를 통해 생성하여 사용할 수 있습니다.
3. EntityManagerFactory

EntityManagerFactory는 일반적으로 DB 하나에 하나만 생성되어 애플리케이션이 동작하는 동안 사용됩니다.
EntityManagerFactory를 만들기 위해서는 DB에 대한 정보를 전달해야합니다.
정보를 전달하기 위해서는 /resources/META-INF/ 안에 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.sparta.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="Aa1213811!"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/memo"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<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>
그리고 java 코드에
EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");
를 넣어서 JPA는 persistence.xml 의 정보를 토대로 EntityManagerFactory를 생성합니다.
EntityManager em = emf.createEntityManager();
EntityManagerFactory를 사용하여 EntityManager를 생성할 수 있습니다.
4. 트랜잭션이란?
트랜잭션은 DB 데이터들의 무결성과 정합성을 유지하기 위한 하나의 논리적 개념입니다.
( DB의 데이터들을 안전하게 관리하기 위해서 생겨난 개념입니다. ).
가장 큰 특징은 여러 개의 SQL이 하나의 트랜잭션에 포함될 수 있다는 점입니다.
모든 SQL이 성공적으로 수행이 되면 DB에 영구적으로 변경을 반영하지만 SQL 중 단 하나라도 실패한다면 모든 변경을
(undo)되돌립니다.
SQL 데이터 조회는 되는 데 실제 데이터베이스에는 저장 된지 않은 상태
Transection start/// functions ..... (insert / add / delete) select 는 예외
COMMIT; 를 해야 데이터 베이스에 반영. (오류가 있으면 Role Back Error)
영속성 컨텍스트에 Entity 객체들을 저장했다고 해서 DB에 바로 반영 되지는 않습니다.

DB에서 하나의 트랜잭션에 여러 개의 SQL을 포함하고 있다가,마지막에 영구적으로 변경을 반영하는 것 처럼
JPA에서도 영속성 컨텍스트로 관리하고 있는 변경이 발생한 객체들의 정보를 쓰기 지연 저장소에 전부 가지고 있다가
마지막에 SQL을 한번에 DB에 요청해 변경을 반영합니다.
성공 예시.
EntitiyTest.java 안에 test() 한수 에 코드 추가.
@Test
@DisplayName("EntityTransaction 성공 테스트")
void test1() {
EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.
et.begin(); // 트랜잭션을 시작합니다.
try { // DB 작업을 수행합니다.
Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다.
memo.setId(1L); // 식별자 값을 넣어줍니다.
memo.setUsername("Robbie");
memo.setContents("영속성 컨텍스트와 트랜잭션 이해하기");
em.persist(memo); // EntityManager 사용하여 memo 객체를 영속성 컨텍스트에 저장합니다.
et.commit(); // 오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출합니다.
// commit 이 호출되면서 DB 에 수행한 DB 작업들이 반영됩니다.
} catch (Exception ex) {
ex.printStackTrace();
et.rollback(); // DB 작업 중 오류 발생 시 rollback 을 호출합니다.
} finally {
em.close(); // 사용한 EntityManager 를 종료합니다.
}
emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.
}
Memo.java 에 getter/setter 추가
Entity class 는 setter 를 만든다면, 조심히 사용 해야합니다.
( setter 는 이미 생성된 객체에 정보를 수정 하기 때문이고,
Entity class 는 DB 랑 매핑 하고 있기 때문에
유도치않에 데이터 베이스에 실수로 변경된 데이터가 들어갈수있음. )
<--------------------------------------------------------------------------------------------------------->
테스트 할때는 auto increment 빼줘야함.
@GeneratedValue(strategy = GenerationType.IDENTITY)
(Memo id 위에 있는거) 제거
<--------------------------------------------------------------------------------------------------------->
persistence.xml
<property name="hibernate.hbm2ddl.auto" value="create" />
create // 기존 테이블 삭제 후 다시 생성 DROP + CREATE
를
<property name="hibernate.hbm2ddl.auto" value="update" />
update // 변경분만 반영(운영DB에는 사용하면 안된다), 추가만 된다. 지워지는건 안됨.
로
다른 기능들도 있는데 설명하자면 :
create-drop : create와 같지만 종료시점에 테이블 DROP, 아예 없앤다.
validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
none : 사용하지 않음
실패 예시.
@Test
@DisplayName("EntityTransaction 실패 테스트")
void test2() {
EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.
et.begin(); // 트랜잭션을 시작합니다.
try { // DB 작업을 수행합니다.
Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다.
memo.setUsername("Robbert");
memo.setContents("실패 케이스");
em.persist(memo); // EntityManager 사용하여 memo 객체를 영속성 컨텍스트에 저장합니다.
et.commit(); // 오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출합니다.
// commit 이 호출되면서 DB 에 수행한 DB 작업들이 반영됩니다.
} catch (Exception ex) {
System.out.println("식별자 값을 넣어주지 않아 오류가 발생했습니다.");
ex.printStackTrace();
et.rollback(); // DB 작업 중 오류 발생 시 rollback 을 호출합니다.
} finally {
em.close(); // 사용한 EntityManager 를 종료합니다.
}
emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.
}
식별자 값을 넣어주지 않아 오류가 발생했습니다. 라고 뜸.
// 영속성 컨텍스트 Debugging으로 확인하기
테스트 시작 위치 가장 왼쪽을 클릭하여 빨간색 원을 만듭니다. 그리고 해당 원에 우측클릭 후 Thread 부분을 클릭한 다음 Make Default 클릭 후 Done을 누릅니다.


초록색 화살표 시작 버튼을 클릭 후 Debug를 클릭합니다

체크 부분을 클릭하여 순차적으로 코드를 진행합니다.

///
5. 영속성 컨테스트 ( Persistence ) 기능
( Entity 객체를 효율적으로 관리하고 있는지 살펴보겠습니다. )
영속성 컨텍스트 는 내부적으로 캐시 저장소를 가지고 있습니다.
캐시 저장소는 Map < @Id , Entity >
5.1 Entity 저장
(SELECT SQL 커멘드 제외 모든 SQL 커멘드 는 Transection 환경일 필수)
persist(Class);
memo Entity 객체를 캐시 저장소에 저장합니다. (그냥 add 라고 보시면 됨)
@Test
@DisplayName("1차 캐시 : Entity 저장")
void test1() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
Memo memo = new Memo();
memo.setId(1L);
memo.setUsername("Robbie");
memo.setContents("1차 캐시 Entity 저장");
em.persist(memo);
et.commit();
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
5.2 Entity 조회
find(Class.class, int);
출 시 캐시 저장소를 확인 한 후 해당 값이 없다면?
DB에 SELECT 조회 후 해당 값을 캐시 저장소에 저장하고 반환합니다.
@Test
@DisplayName("Entity 조회 : 캐시 저장소에 해당하는 Id가 존재하지 않은 경우")
void test2() {
try {
Memo memo = em.find(Memo.class, 1);
System.out.println("memo.getId() = " + memo.getId());
System.out.println("memo.getUsername() = " + memo.getUsername());
System.out.println("memo.getContents() = " + memo.getContents());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
em.close();
}
emf.close();
}
5.3 Entity 삭제
em.remove(Class);
@Test
@DisplayName("Entity 삭제")
void test5() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
Memo memo = em.find(Memo.class, 2);
em.remove(memo);
et.commit();
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
find(Class.class, 2); 호출 하여 memo 객체를 캐시 저장소에 저장한 후
( entityEntry를 확인해 보시면 memo Entity 객체가 영속성 컨텍스트가 관리하는 MANAGED 상태인 것을 확인할 수 있습니다. )
쓰기 지연 저장소 (ActionQueue)
JPA가 트랜잭션 처럼 SQL을 모아서 한번에 DB에 반영한다는 것을 배웠습니다.
JPA는 이를 구현하기 위해 쓰기 지연 저장소 만들어 SQL 모아두고
트랜잭션 commit 후 한번에 DB에 반영.
실제로 기록을 확인해보면 트랜잭션 commit 호출 전까지는 SQL 요청이 없다가 트랜잭션 commit 후 한번에 Insert SQL 2개가 순서대로 요청된 것을 확인할 수 있습니다.
(commit 호출 하면 flush 가 자동으로 됩니다.)
.flush() : 메서드가 호출되자 바로 DB에 쓰기 지연 저장소의 SQL이 요청되었습니다.
트랜잭션 commit 후 쓰기 지연 저장소의 SQL들이 한번에 요청됨을 확인했습니다.
트랜잭션 commit 후 추가적인 동작이 있는데 바로 .flush()
영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행합니다.
변경 감지(Dirty Checking)
영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면?
하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적입니다.
그럼? JPA는 어떻게 Update를 처리할까요? (.update() 는 존재하지 않는다)
1. JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장합니다.
2. 트랜잭션이 commit되고 가 .flush() 호출되면 Entity의 현재 상태와 vs 최초 상태를 비교합니다.
3. 변경 내용이 있다면 Update SQL을 생성 & 지연 저장소에 저장 & 쓰기지연 저장소의 SQL을 DB에 요청합니다.
4. DB의 트랜잭션이 commit 되면서 반영.

따라서 변경하고 싶은 데이터가 있다면
1. 먼저 데이터를 조회
2. 해당 Entity 객체의 데이터를 변경 (Setter 만 사용해도 자동으로 변경 감지 / COMMIT 때 업데이트 함.)
3. 자동으로 Update SQL이 생성되고 DB에 반영.
이라고 보면 됩니다.
( 이러한 과정을 변경 감지, Dirty Checking이라 부릅니다. )
@Test
@DisplayName("변경 감지 확인")
void test8() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
System.out.println("변경할 데이터를 조회합니다.");
Memo memo = em.find(Memo.class, 4);
System.out.println("memo.getId() = " + memo.getId());
System.out.println("memo.getUsername() = " + memo.getUsername());
System.out.println("memo.getContents() = " + memo.getContents());
System.out.println("\n수정을 진행합니다.");
memo.setUsername("Update");
memo.setContents("변경 감지 확인");
System.out.println("트랜잭션 commit 전");
et.commit();
System.out.println("트랜잭션 commit 후");
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
'Spring' 카테고리의 다른 글
| Spring 기초 8 (SpringBoot 의 JPA) (0) | 2024.01.21 |
|---|---|
| Spring 기초 7 (Entitiy 상태) (Entitiy 영속/비영속 기능) (0) | 2024.01.20 |
| Spring 기초 5 (Entity) (0) | 2024.01.20 |
| Spring 기초 4 (MVC) (JPA) (BEAN) (@Componet @Autowired) (Hibernate) (0) | 2024.01.20 |
| Spring 기초3 (데이터 받는 method) (Json 변환 String&Object) (@RequestParam @RequestBody) (HTTP 를 객체로) (0) | 2024.01.19 |