목표

  •  JPA를 활용하기 위해 가장 중요한 개념인 영속성 컨텍스트의 기능 이해하기
  •  Entity의 생명 주기 이해하기

영속성 컨텍스트(Persistent Context)

영속성 컨텍스트란 엔티티를 관리하고 영속화시키는 환경을 의미합니다.

영속성 컨텍스트가 지원하는 기능은 다음과 같습니다.

  • 지연로딩
  • 동일성 보장
  • 1차 캐시
  • 변경 감지
  • 트랜잭션을 지원하는 쓰기 지연

Entity Manager와 Entity Manager Factory

Entity Manager는 엔티티를 조회하거나 등록, 수정, 삭제 시키기 위해 DB에 접근할 수 있는 객체입니다.

EntityManagerFactory는 Entity Manager를 생성합니다. 객체 생성 비용이 상당히 크기 때문에 한번만 생성하여 애플리케이션 전체에서 사용합니다.


Entity 생명주기

1. 비영속(New) : 영속성 컨텍스트와 관계가 없는 상태

//비영속 엔티티 생성
Employee employee = new Employee();
employee.setName("kim");

 

2. 영속(Managed) : 영속성 컨텍스트에 저장된 상태

저장하거나 조회할 경우 해당 엔티티는 Persistence Context의 1차 캐시에 저장되어 영속화된다.

@Autowired
private EmployeeDao employeeDao; // JpaRepository를 상속받음

public void save(Employee employee){
	employeeDao.save(employee); //employee 객체 영속화
}

public Employee findById(Integer id){
	return employeeDao.findById(id).orElse(null); // employee 객체 영속화
}

 

3. 준영속(Detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태

//준영속 상태를 만드는 3가집 방법
//1. 해당 엔티티를 준영속 상태로 전환
em.detach(entity);

//2.영속성 컨텍스트를 초기화
em.clear()

//3. 영속성 컨텍스트를 종료
em.close

 

4. 삭제(Removed) : 삭제된 상태

em.remove(entity);

영속성 컨텍스트의 구조

영속성 컨텍스트는 1차 캐시 저장소, SQL 저장소로 나누어 집니다.

1차 캐시 저장소

엔티티를 조회하거나 저장할 경우 해당 엔티티는 1차 캐시에 저장됩니다.

1차 캐시에 저장되면 해당 엔티티는 영속 상태가 되며 영속성 컨텍스트가 관리하는 대상이 됩니다.

쿼리문 저장소

insert, update, delete등.. 여러 쿼리문을 저장했다가 commit시 한번에 처리합니다.

DB 접근 회수를 최소화하고 성능상 이점을 얻을 수 있습니다.


영속성 컨텍스트의 기능

1차 캐시

엔티티를 조회하거나 저장할 경우 해당 엔티티는 1차 캐시에 저장됩니다. 그리고 해당 엔티티를 조회 할 때, 1차 캐시에 저장되어 있다면 DB를 거치지 않고 1차 캐시 저장소에서 꺼내 사용합니다.

1차 캐시는 일종의 Map 형태로 관리되며, 엔티티의 @Id 어노테이션이 선언된 필드는 Key가 되고, instance 값이 value가 됩니다.

@id Instance
12 entity@15db9742
15 entity@19db7415

동일성 보장

Employee emp1 = employeeDao.findById(12).orElse(null);
Employee emp2 = employeeDao.findById(12).orElse(null);

System.out.println(emp1 == emp2); // true

id가 12인 Employee 엔티티 조회 시, 동일한 instance를 반환하여 동일성을 보장합니다.


트랜잭션을 지원하는 쓰기지연

엔티티 저장, 수정, 삭제 메소드를 실행할 경우 SQL 저장소에 Query문을 저장하고 있다가 flush()하고 commit()할 때 DB에 반영합니다.


변경 감지

엔티티 메니저는 update메소드가 정의되어 있지 않습니다. 영속화 된 엔티티에 변경사항이 생긴 경우, 변경 감지를 할 뿐입니다.

엔티티 1차 캐시시 엔티티에 대한 참조와 엔티티를 처음 영속상태로 만들었을 떄의 복사본을 가지고 있습니다.

flush()가 호출되면 영속화된 엔티티와 복사본을 비교하여 변경된 값이 있다면 update 쿼리를 생성하여, SQL 저장소에 저장하고 DB에 전달할 때, 함께 보냅니다.


지연 로딩(Lazy Loading)과 프록시(Proxy)

지연 로딩은 참조하는 객체를 Join하지 않고 조회 했다가 참조 객체 접근 시, Select하는 전략입니다.

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column
    private String name;

    @OneToMany(fetch = FetchType.LAZY)
    private Department department;
}

Employee 엔티티는 Department(부서)를 참조하고 Lazy 패치전략을 사용하고 있습니다. 

Employee 엔티티를 조회하면 department 멤버변수에는 프록시 객체가 저장됩니다.

프록시 객체란?
전략에 따라 참조 관계를 맺고있는 객체에 실제 엔티티 대신 저장된 위장 엔티티를 의미합니다.

프록시 객체에 접근 할 때, 영속성 컨텍스트에 참조 엔티티가 생성되어 있지 않다면, DB Select하여 생성합니다.

이를 프록시 초기화라고 합니다.

패치 전략에는 LAZYEAGER이 있습니다.

EAGER 전략은 객체 조회 시 참조 엔티티를 Join하여 함께 조회하는 전략을 의미합니다.


참조 : https://www.slideshare.net/zipkyh/ksug2015-jpa3-jpa

 

Ksug2015 - JPA3, JPA 내부구조

KSUG2015에서 JPA에 대해 발표한 내용입니다. 3편 입니다. 영속성 컨텍스트, 엔티티매니저, 프록시에 대해 다룹니다.

www.slideshare.net

https://siyoon210.tistory.com/138

 

JPA의 영속성 컨텍스트와 엔티티 생명주기

[개요] JPA를 공부하는데 가장 난해한 부분인 '영속성 컨텍스트'에 대해서 알아보겠습니다. 영속성 컨텍스트에 대한 지식이 수반되어야 JPA의 동작방식을 온전히 이해할 수 있습니다. [영속성 컨텍스트 (Persisten..

siyoon210.tistory.com

1. Hibernate란

  • Boss에서 개발한 ORM(Object Relation Mapping)프레임워크 중 한개입니다.
  • ORM이란 객체와 DB 테이블의 매핑을 의미합니다.
  • ORM 매핑을 이용하면 객체로 DB 테이블 조작할 수 있습니다.

2. pom.xml

     ...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
        </dependency>
      ... 
  • Spring Boot 버전 - 2.1.0.RELEASE
  • hibernate-core-5.3.7.Final

프로젝트 구조

 


3. application.yml

spring :
    datasource :
        url : jdbc:mysql://localhost:3306/db_name
        username : (DB User Name)
        password : (DB Password)
    jpa :
        properties :
            hibernate :
                format_sql:true
        show-sql : true        
        hibernate :
            ddl-auto : update    
            # ddl-auto - @Entity 테이블 정보를 실제 DB에 반영 할건지 설정(create/update/none..)

 


4. User.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(length = 20, nullable = false)
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • @Entity 어노테이션은 해당 클래스가 DB Table과 1:1 매핑이 될 수 있게 합니다.
  • @Id - Primery Key 지정
  • @GeneratedValue(strategy = GenerationType.IDENTITY) - 기본키 생성을 DB에게 위임합니다.
    Mysql에 경우, AUTO_INCREMENT를 사용합니다.
  • @Column(length = 20, nullable = false) - 테이블 컬럼을 선언합니다. (길이 20, null을 허용하지 않음)
  • 위 클래스 생성 후, 서버를 실행 시키면 실제 DB에 해당 테이블에 대한 정보가 반영됩니다.

서버 구동 시 아래와 같이 로그에 user테이블 생성 로그가 남았다면, 실제로 테이블이 생성되었는지 확인


5. UserDao.java 생성

import com.example.bamdule.model.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserDao extends JpaRepository<User, Integer> {

}

6. UserController.java 생성

import com.example.bamdule.dao.UserDao;
import com.example.bamdule.model.entity.User;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private UserDao userDao;

    @GetMapping(value = "/save")
    @ResponseBody
    public List<User> saveUser(User user) {
        userDao.save(user);
        
        return userDao.findAll();
    }
}
  • user save 요청 추가
  • "/user/save" 요청 받는 유저를 DB에 저장하고, 유저 리스트를 반환합니다.
  • host/{contextPath}/user/save?name=park

브라우저에 위와 같은 응답이 왔으면 하이버네이트가 정상 동작 하는 것입니다.

+ Recent posts