서론

 

이 피드에서 기록하는 공부내용

 

하이버네이트, 스프링데이터 프로젝트 개요

Spring에 JPA 연결하기

 

* 이제 JPA는 Java Persistence API가 아니라 Jakarta Persistence API다.

Oracle이 JDK를 먹으면서 JPA가 Jakarta 패키지로 빠져나왔다.


ORM

 

객체/관계형 매핑(ORM)은 객체지향 시스템과 관계형 데이터베이스 간 호환되지 않는 데이터 표현 방식을

연결하기 위한 프로그래밍 기법


Spring Data

 

스프링 데이터 산하 프로젝트

 

스프링 프레임워크의 원칙을 준수하며 영속성 계층의 구현을 효율적으로 할 수 있게 함

 

Spring Data 개요

 

관계형 DB와 NoSQL DB에 접근을 간소화 하는것을 목표로 하는 Spring Framework의 하위프로젝트

Spring Data는 아래의 하위 프로젝트로 구성되어있다.

 

- 스프링데이터 Commons : 자바 클래스를 영속화 하기 위한 메타데이터 모델과 "기술 중립적인" 리파지토리 인터페이스를 제공

- 스프링데이터 JPA : JPA 기반 리파지토리(JpaRepository Interface)의 구현을 제공

- 스프링데이터 JDBC : JDBC 기반 리파지토리 구현을 제공. 캐싱/지연로딩 같은 JPA 기능을 제공하지 않아 더 간단하고 제한된 ORM을 제공

- 스프링데이터 REST : 스프링 데이터 Repository를 RESTful 리소스로 내보내는 작업을 담당

                                    찾아봤는데, 엔티티와 리파지토리만 만들면 자동으로 엄격한 API를 만들어 주는 프로젝트인것같다.

- 스프링데이터 MongoDB : MongoDB 도큐먼트 DB 접근을 처리. 리파지토리 계층과 POJO 프로그래밍 모델에 의존함

- 스프링데이터 REDIS : Redis에 대한 접근을 제어. 고수준/저수준 추상화 모두 제공


Hibernate

 

객체 및 객체/관계형 매핑의 영속성 관리 API를 정의한 JPA(Jakarta Persistence API)의 구현체

JPA는 객체를 영속화하기 위해 수행해야 하는 작업을 지정하고 있음

 

Hibernate (이하 하이버네이트) 개요

 

자바에서 영속성 데이터를 관리하는 수단을 제공하는 솔루션

하이버네이트 프로젝트는 아래의 하위 프로젝트로 구성되어있다. 아래에 적힌 내용은 모두 "프로젝트 `==. 모듈" 이다.

 

- 하이버네이트 ORM : 다른 하위프로젝트의 기반이 되며 RDB를 통한 영속성을 위한 기반 서비스와 전용 API로 구성

- 하이버네이트 EntityManager(EM) : 표준 JPA의 하이버네이트 구현. 하이버네이트 ORM에서 사용가능한 선택적 모듈

- 하이버네이트 유효성 검사기 : 빈 유효성 검사(JSR 303) 명세의 참조 구현체를 제공. 도메인 모델에 대한 선언적 유효성 검사를 제공

- 하이버네이트 엔버스 : 감사 로깅과 RDB에 데이터의 버전을 관리하는 기능을 제공

- 하이버네이트 검색 : 도메인 모델 데이터의 인덱스를 아파치 루씬 데이터베이스에 저장 및 유지. 이 것을 사용해 전문검색 기능도 사용 가능.

- 하이버네이트 OGM : 객체/그리드(Object/Grid 매퍼). 매핑된 엔티티를 키/값이나 문서, 그래프 형 데이터 저장소에 개별 저장해

                                   NoSQL에서 JPA를 사용할수 있게함

- 하이버네이트 리엑티브 : Non-Blocking 방식으로 DB와 상호작용하는 리엑티브 API.


예제로 시작하는 JPA

 

DB에 Hello World 저장하고 가져오기

책은 MySQL/Maven을 사용했으나, 나는 Postgresql/Gradle로 진행헀다.

우선 의존성 선언

	implementation 'org.hibernate:hibernate-entitymanager:5.6.9.Final'
	testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
	implementation 'org.postgresql:postgresql:42.7.7'

 

* 책에서는 hibernate-entityManager를 implementation하나, Maven 왈 최신버전은 hibernate-core 로 재배치되었다고 한다.

- hibernate-entityManager : JPA 인터페이스 stub과 같이 필요한 다른 모듈에 대한 전이 의존성이 포함되어있음

- junit : 테스트 프레임워크

- postgresql : JDBC 드라이버

 

영속성 단위 구성

 

모데인모델 클래스매핑, 데이터베이스 커넥션, 기타 구성설정을 포함한 한 쌍을 "영속성 단위구성" 이라 칭한다.

모든 APP은 적어도 하나 이상의 영속성 단위구성을 가지며, 여러개일 수 있다.

사실 Spring으로 생각하면 "Datasource Configure" 를 하나 만들라는 소리다.

책에서는 우선 .xml베이스로 구성하고 있다.

 

JPA Datasource .XML 베이스 구성

 

/resources/META-INF/persistence.xml을 작성한다.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

    <!-- 고유한 영속성 단위 이름을 작성한다.-->
    <persistence-unit name="jpaPractice">
        <!-- JPA는 단순한 명세 이다. 어떤 공급자(구현체)를 쓸것인지 선언해야 한다.-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/jpaPractice?serverTimezone=UTC"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root123!"/>

            <!-- DB별로 명령어가 조금씩 다른데, 이를 DB 방언이라 한다. 이렇게 선언하면 Postgresql의 방언을 처리해준다 -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>


            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <!-- 프로젝트 실행 시마다 DB를 처음부터 다시 만들도록 한다. -->
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>

</persistence>

 

영속성 클래스 작성

 

영속화 할(DB에 저장할) 객체를 선언한다.

** entitymanager-5.6.9.Final 에서는 아직 Javax에 속해있는것같다. 

** 내 멋대로 구성했던 hibernate-core:7.1.0  에서는 Jakarta였다.

package org.example.domain.entity;

import lombok.Getter;
import lombok.Setter;

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

// 이 클래스가 영속화 대상임을 명시
@Entity
public class Message {

    // 모든 영속성 클래스는 식별자 속성(PK)가 있어야 하며, @Id 애너테이션으로 지정함.
    @Id
    // 식별자 역할(PK). 또한 GeneratedValue를 쓰면 자동으로 식별자값이 생성됨.
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long messageId;

    private String text;

	// 일반적으로 영속성 클래스는 private 필드와 public Getter/Setter로 구성.
    public long getMessageId() {
        return messageId;
    }

    public void setMessageId(long message_id) {
        this.messageId = message_id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

 

* 이 영속성 클래스는 별도로 영속성 관련 클래스나 인터페이스를 아무것도 구현하지 않았다.

따라서 반드시 Hibernate를 통해서만 쓸 수 있는게 아니라, 일반 Java 클래스처럼 사용할 수 있다.

== > 어떠한 실행 컨텍스트에서도 영속성 클래스를 사용할 수 있으며, 어떠한 특별한 Java Container를 필요로하거나 종속되지 않음을 의미한다.

 

영속성 클래스 저장과 로딩

이 섹션에서는 Junit을 사용한다.

import org.example.domain.entity.Message;
import org.junit.jupiter.api.Test;


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class HelloWorldJPATest {
    @Test
    public void storeLoadMessage() throws Exception{

        /* 1. DB에 접근하려면 EntityManager가 피룡한데, 이 객체는 EntityManagerFactory로부터 생성할 수 있다.
        대부분의 앱은 구성된 영속성 단위 각각에 대해 하나의 EntityManagerFactory를 가지며, 고유한 이름을 가진다.
        이때, EntityManagerFactory는 "Thread-safe" 하며, DB에 접근하려믄 애플리케이션의 모든 코드에서 공유해야 한다. */
        try{
            // 2. EntityManager를 생성해 DB와 세션을 연결한다. *책에서는 모든 영속성 연산의 컨텍스트가 된다* 고 표현한 부분이 있다.
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaPractice");
            EntityManager em = emf.createEntityManager();
            // 3. 표준 Transaction API에 접근 후, 지금 이 스레드에서 DB Transcation을 시작함을 선언
            em.getTransaction().begin();

            // 4. 영속성 클래스를 만들고 필드 값 설정
            Message message = new Message();
            message.setText("Hello World!");


            // 5. 비영속 인스턴스를 영속성 컨텍스트에 등록해서 영속화. 이 시점에는 DB에 저장 할 "예정" 일 뿐, 아직 저장한건 아님
            em.persist(message);
            // 6. Transaction Commit. Hibernate가 자동으로 영속성 컨텍스트를 확인하고 요구된 SQL문을 발행한다.
            // 이시점에서 Insert문이 발행됨
            em.getTransaction().commit();

            // 7. 한번 commit을 했으면, transaction이 종료된것임. 또다른 연산을 하려면 다시 transaction을 열어야함
            em.getTransaction().begin();

            // 8. 직접 쿼리를 아래처럼 만들 수 있고, 어떤 entity에 매핑할것인지 직접 넘겨야 함.
            // 또한 이 쿼리는 SQL이 아니라 JPQL. 쿼리 문자열 내 Message는 Table이 아니라 영속성이름이다.
            List<Message> messages = em.createQuery("select m from Message m", Message.class).getResultList();

            // 9. 이미 한번 로드 된 객체는 영속성 컨텍스트에서 관리함.
            // 이 상태에서 필드의 값을 바꾸면 자동으로 하이버네이트가 감시해서 영속성 컨텍스트에 변경점을 1차 반영
            // 이걸 자동 변경 감지(dirty check) 라고 부른다.
            messages.get(messages.size()-1).setText("Hello JPA WORLD!");

            // 10. 이 상태에서 commit을 날리면 반영된 1차 값에 대해 UPDATE 쿼리가 발행됨
            em.getTransaction().commit();

            // 11. 이부분은 Junit 코드,
            // AssertALL은 일부 검사가 실패해도 모든걸 확인하라는 지시.
            // assertEquals은 "주어진 2개 인자가 같은 값인지 확인하라"
            assertAll(
                    () -> assertEquals(1, messages.size()),
                    () -> assertEquals("Hello JPA WORLD!", messages.get(0).getText())
            );

        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

 

이렇게 작성하고 돌렸는데 문제가 발생했다.

 

1. 의존성 충돌

나는 사실 build.gradle에 다른것도 집어넣어놨다.

dependencies {
	implementation 'org.hibernate:hibernate-entitymanager:5.6.9.Final'
	//testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
	implementation 'org.postgresql:postgresql:42.7.7'
	/*
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'

	implementation 'org.springframework.session:spring-session-jdbc'
	 */
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

이렇다 보니, Spring-boot-starter 내부에 있는 Junit 의존성과 내가 선언한 jupiter-engine 간 충돌이 발생한것.

이에따라 직접 선언한 jupiter 의존성을 주석처리하고 다시 돌렸다.

 

2. auto discovery 문제

 

이걸로 엄청 헤메였다.

일단 내 코드 구조는

이렇게 구성되어있다.

그러다보니 Build 시점에서 아래와 같이 코드가 생성된다.

이렇다보니, chatgpt 말로는 entity가 "auto discovery" 범위 밖에 있어서 찾을 수 없었다는것.

한번 찾아가보자

 

여기서 Intellij 단축키 몇개를 기억할 필요가 있다.

CTRL + H : Call hierarchy (호출 계충구조 / 인터페이스 계층구조 출력)

CTRL + ALT + B : 구현체 찾기 단축

 

사실 코드를 찾아 헤메다가, debug 로그 보는게 낫지않나? 해서 로그로 대체한다.

PU(Persistence Unit) Root URL과, 그 아래에서 scan한 classes names 가 [] 이다.

PU(Persistence Unit) Root URL(persistence.xml 위치) 로부터 그 하위로 스캔을 해봤는데 클래스가 검출된게 없다는 뜻.

 

따라서 책에서는 persistence.xml 아래에 Class를 뒀는지는 몰라도 PU Root URL 아래에서 스캔이 성공했던듯.

책에서 나온대로 간편하게 테스트하려면 persistence.xml의 PU 설정에 <class> 가 추가되어야한다.

 

이렇게 하면

 

정상적으로 Entity가 만들어지고 쿼리 발행이 된것을 볼 수 있다.


Native Hibernate (네이티브 하이버네이트)

Hibernate의 경우, JPA 표준 제정보다 먼저 나와서 상용으로 사용되고있었다.

이 와중, JPA 표준이 제정되었고, 이 표준을 준수하기 위해 기존의 Hibernate API를 유지하면서 JPA스펙을 구현했다. 

이를 비교하기 위해, JPA 표준 제정 전 하이버네이트를 네이티브 하이버네이트, JPA 표준을 구현한 하이버네이트를 그냥 Hibernate라고 부르고 있다고.

고마워요 따봉지피티!

이 네이티브 하이버네이트는 JPA 의존성이나 클래스가 아닌

하이버네이트 자체 API와 의존성을 사용한다.

네이티브 하이버네이트는 표준을 준수한/구현한 것이 아니라 유연성은 떨어지지만, JPA표준에는 없는 더 많은 기능을 쓸수있다고.

 

또한 공부의 엔트리포인트로 남겨두자면,
표준 JPA의 EntityManagerFactory(엔티티관리 및 DB 접근 주체)에 상응하는 객체는

네이티브 하이버네이트의 org.hibernate.SessionFactory 이다.

 

네이티브 하이버네이트는 아래와 같은 기능이 필요할 때 사용한다고 보면 될것같다.

 

1. statelessSession(1차 캐시 없이 DB사용 -> 대용량 배치처리에 유용)

2. @BatchSize, @Fetch(SUBSELECT 전략), @Formula 등등의 어노테이션 기능 필요

3. 다형성 관계 매핑 전략에서 더 다양한 옵션

4. 2차캐시, 쿼리캐시, 세션 레벨 캐싱 세밀하게 제어 가능

 

네이티브 하이버네이트 구성

 

네이티브 하이버네이트 구성은 hibernate.properties 혹은 hibernate.cfg.xml 파일에 작성한다.

// resource/hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">
            org.postgresql.Driver
        </property>
        <property name="hibernate.connection.url">
            jdbc:postgresql://localhost:5432/jpaPractice?serverTimezone=UTC
        </property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root123!</property>
        <property name="hibernate.connection.pool_size">50</property>
        <property name="show_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">create</property>
    </session-factory>
</hibernate-configuration>

 

네이티브 하이버네이트 사용

public class HelloHibernateTest {

    // 1. EntityManager 처럼 DB에 접근하고, 엔티티 영속성 관리를 수행하는 session 객체 생성용 factory
    private static SessionFactory createSessionFactory() {
        Configuration configuration = new Configuration();
        // hibernate.cfg.xml 로드, 엔티티 클래스 수동추가
        configuration.configure().addAnnotatedClass(Message.class);
        // 하이버네이트에 다양한 pluggable 구현체를 제공하는 클래스 build
        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                .applySettings(configuration.getProperties()).build();
        // 서비스 레지스트리 + 구성 cfg 를 통한 sessionFactory 생성
        return configuration.buildSessionFactory(serviceRegistry);
    }

    @Test
    public void storeLoadMessage() {

        try (SessionFactory sessionFactory = createSessionFactory();
             // DB 연결, 새 세션 시작 -> 모든 영속성 연산의 컨텍스트
            Session session = sessionFactory.openSession()) {
            // 표준 transaction API 접근 -> 이 스레드에서 트랜잭션 시작
            session.beginTransaction();


            Message message = new Message();
            message.setText("Hello World from Hibernate!");

            // 비영속 인스턴스를 영속성 컨텍스트에 등록, 영속화.
            // DB를 이시점에 호출하지는 않음.
            session.persist(message);
            // 세션과 DB를 동기화, 커밋 후 세션 종료
            session.getTransaction().commit();

            // 이 스레드에서 트랜잭션 시작
            session.beginTransaction();

            // 프로그래밍 방식으로 쿼리를 생성할 수 있는 CriteriaQuery 생성
            // 예를들면,
            /*
                Root<Member> m = cq.from(Member.class);

                cq.select(m)
                  .where(cb.gt(m.get("age"), 20));  // age > 20
             */
            CriteriaQuery<Message> criteriaQuery = session.getCriteriaBuilder().createQuery(Message.class);
            // Message 엔티티에 해당하는 쿼리 루트 (객체 탐색이 시작되는 루트 엔트리) 설정
            criteriaQuery.from(Message.class);

            // SELECT * from MESSAGE
            List<Message> messages = session.createQuery(criteriaQuery).getResultList();

            // 트랜젝션 커밋
            session.getTransaction().commit();

            // junit 통과조건
            assertAll(
                    () -> assertEquals(1, messages.size()),
                    () -> assertEquals("Hello World from Hibernate!", messages.get(0).getText())
            );
        }
    }

}

 

네이티브 hibernate (hibernate API) 구성으로 JPA 엔티티매니저 만들기. Vice Versa

public class createCross {
    @Test
    // JPA 설정으로 -> Hibernate API 세션팩토리 생성
    private static SessionFactory sessionFactory(EntityManagerFactory entityManagerFactory){
        return entityManagerFactory.unwrap(SessionFactory.class);
    }
    
    // Hibernate API 설정으로 -> JPA 엔티티매니저팩토리 생성
    private static EntityManagerFactory entityManagerFactory(SessionFactory sessionFactory){
        // 신규 구성 생성
        Configuration conf = new Configuration();
        // 관심사 annotated Entity 등록
        conf.configure().addAnnotatedClass(Message.class);
        
        // 기존 프로퍼티로 채울 맵 구성
        Map<String, String> props = new HashMap<>();
        // 하이버네이트 구성에서 설정 조회
        Enumeration<?> propNames = conf.getProperties().propertyNames();
        
        // 기존 Hibernate APi 의 설정을 모두 맵에 추가
        while(propNames.hasMoreElements()){
            String ele = (String) propNames.nextElement();
            props.put(ele, conf.getProperties().getProperty(ele));
        }
        
        // Hibernate API의 설정으로 구성된 JPA 엔티티매니저 팩토리 리턴
        return Persistence.createEntityManagerFactory("jpaPractice", props);
    }
    
    
}

 

Spring Data JPA 사용

 

Spring-Data-JPA의 요구 표준 구성 파일은, Spring-Data에서 요구하는 @Bean을 생성 및 설정을 수행하는 Java 파일이다.

 

먼저, build.gradle에 아래 내용을 선언한다.

    // https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa
    implementation("org.springframework.data:spring-data-jpa:2.7.0")
    // https://mvnrepository.com/artifact/org.springframework/spring-test
    testImplementation("org.springframework:spring-test:5.3.20")

 

다음, @Bean을 선언한다.

 

* SpringDataConfiguration

// 1. JPA 사용을 활성화하고, Repository 계층 패키지를 인자로 제출한다.
@EnableJpaRepositories("org.example.repository")
public class SpringDataConfiguration {
    @Bean
    //2. DB 연결 관리 객체 생성. connectionPool에 대한 책임을 짐
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:postgresql://localhost:5432/jpaPractice?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("root123!");
        return dataSource;
    }

    // 3. 엔티티매니저 팩토리를 기반으로 트랜잭션 매니저 빈을 생성
    // DB의 모든 상호작용은 트랜잭션 경계 내에서 일어나야하며, SpringData는 트랜잭션매니저 빈을 요구함
    // ** 트랜잭션 매니저는 JPA의 EntityManager가 아님!
    // ** 트랜잭션 매니저 -> 정말 트랜잭션만 관리 / JPA EntityManager : 영속성 관리 및 트랜잭션API 제공
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }

    // 4. JPA 구현체와의 통합 설정을 담당
    // JPA 가 하이버네이트와 상호작용에 필요로하는 JPA 공급자 어댑터 빈 생성
    // JPA 구현체에 따라 필요한 속성을 EntityManagerFactory에 주입하는 역할
    // SQL 방언지정, DDL, 로그옵션 등 설정 가능
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
        jpaVendorAdapter.setShowSql(true);
        return jpaVendorAdapter;
    }

    @Bean

    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        // 5. 표준 JPA 컨테이너 부트스트랩 규약에 따른 EntityManagerFactory를 생성하는 팩터리빈
        LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =
                new LocalContainerEntityManagerFactoryBean();
        // 6. datasource 설정(DB Conn pool 정보 주입)
        localContainerEntityManagerFactoryBean.setDataSource(dataSource());
        Properties properties = new Properties();
        properties.put("hibernate.hbm2ddl.auto", "create");
        // 7. JPA 설정 주입
        localContainerEntityManagerFactoryBean.setJpaProperties(properties);
        // 8. JPA Vendor 설정 주입
        localContainerEntityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter());
        // 9. Entity Class 패키지 지정
        localContainerEntityManagerFactoryBean.setPackagesToScan("org.example.domain.entity");
        return localContainerEntityManagerFactoryBean;
    }
}

 

* 이때, transcationManager는 Hibernate의 EntityManager와는 다르다

- Hibernate EntityManager : 영속성 생애주기 관리, Transcation API 제공

- SpringData TransactionManager : Transaction 관리

 

* 그럼 EntityManager는?

주석 5번 EntityManagerFactoryBean으로부터 EntityManager가 만들어진다.

이후, 스프링이 @PersistenceContext 프록시 엔티티 매니저를 꺼내서 리파지토리에 주입

  - 이 프록시는 SharedEntityManagerCreator가 만드는 트랜잭션-스코프 위임 프록시. 현재 스레드에 바인딩 된 실제 EM에게 호출을 위임한다.

실제 일을 하는건 트랜잭션마다 열리는 진짜 EM이 일함.

 

* Repository 계층

import org.example.domain.entity.Message;
import org.springframework.data.repository.CrudRepository;

// Long Type을 Pk type으로 가지는 Message 엔티티 저장소를 의미
// Spring-Data-Jpa는 MessageRepository 인터페이스를 구현하는 프록시 클래스를 생성하고,
// 해당 인터페이스의 메서드를 구현함
public interface MessageRepository extends CrudRepository<Message, Long> {

}


* JpaRepository<>가 아니라 CrudRepository로 예제가 잡혀있다. 쟨 뭐지?
JpaRepository는 CrudRepository를 상속한 다른 Repository interface를 상속한 인터페이스임.

지금 당장은 단순하게 Crud만 할꺼니까 가벼운 애로 실습하는것같다.

 

* TestCode

// 1. Spring Extension을 사용 -> 스프링 테스트 컨텍스트를 Junit5 Jupiter 테스트와 통합하는데 쓰임
@ExtendWith(SpringExtension.class)
// 2. Spring Context 설정파일 지정, 설정파일에서 정의된 빈을 사용함
@ContextConfiguration(classes = {SpringDataConfiguration.class})
public class HelloWorldSpringDataJpaTest {

    // 3. Repository DI 주입
    @Autowired
    private MessageRepository messageRepository;

    @Test
    public void storeLoadMessage() {
        // 4. 도메인 모댈 생성 후, Field값 지정
        Message message = new Message();
        message.setText("Hello World from Spring Data JPA!");

        // 5. 객체 영속화.
        // .save()는 CrudRepository로부터 상속되며, 프록시 클래스가 생성될 때
        // spring-data-jpa에 의해 메서드 본문이 생성됨
        // 그리고 저 .save() 구현부 보면 em.persist()가 호출되고 있음
        messageRepository.save(message);

        // 6. select * from 절이 발행되는 부분 5번과 마찬가지로 메서드 본문이
        // 프록시 클래스가 생성될 때 스프링 데이터 JPA에서 메서드 본문 생성
        List<Message> messages = (List<Message>) messageRepository.findAll();

        // 7. junit 통과 조건 지정
        assertAll(
                () -> assertEquals(1, messages.size()),
                () -> assertEquals("Hello World from Spring Data JPA!", messages.get(0).getText())
        );

    }
}

 

* Spring 쓰면서 본문 코드가 확 줄어든걸 알수있다.

 

정리

JPA : 명세 그 자체

- 일반 JPA API를 사용, 영속성 공급자가 필요함

- 구성에서 영속성 공급자(구현체) 변경 가능

- EntityManagerFactory, EntityManager, 트랜잭션 명시적 관리 필요

- Native Hibernate 구성으로부터 EntityManagerFactory를 생성해 JPA 접근 방식 전환 가능

 

Native Hibernate 

- 네이티브 하이버네이트 API 사용 == 프레임워크에 코드가 종속됨

- hibernate.cfg.xml 혹은 hibernate.properties에서 속성 및 설정 지정

- SessionFactory, Session, 트랜잭션에 대한 명시적 관리가 필요

- EntityManagerFactory에서 SessionFactory를 꺼내거나, EntityManager에서 Session을 꺼내는 방식으로 접근방식 전환 가능

 

Spring Data JPA

- 스프링 의존성을 가짐

- 트랜잭션 매니져를 포함하여 프로젝트에 필요한 빈 생성 도 수행

- Repository 인터페이스를 선언

   -> 스프링데이터가 "DB와 상호작용하는 자동 생성 메서드가 포함된 프록시 클래스" 형태로 리파지토리 인터페이스의 구현체 생성

- 필요한 Repository는 주입되며, 명시적으로 개발자가 생성하지 않음.

 

*단, I/O 벤치마크는 데이터가 늘어나면 늘어날수록 SpringDataJPA가 기하급수적으로 소요시간이 늘어남

 

+ Recent posts