[JPA] JPA 개요와 영속성 컨텍스트

2022. 12. 18. 21:01·Spring/JPA
728x90
반응형

누군가 저에게 서버가 무엇인가요? 라고 물어본 적이 있습니다. 정말 추상적인 질문이지만, 저는 이 질문에 항상 "서버는 그냥 Data 저장하는 곳입니다" 라고 답변을 합니다. 수많은 Data 가 어딘가에는 저장되어 있어야 하는데, 그 곳이 서버라는 뜻으로 하는 말이며, 수많은 서버의 역할들이 있지만, 결국 모든 서버는 Data 를 위해 동작한다는 생각이 많이 듭니다. 

 

IT 서비스를 제공하는 가장 중심에는 Database 가 있습니다. 결국 우리는 우리의 정보를 제공하는 대가로, 서비스를 제공받는 형태로 이루어지는 서비스가 많습니다. 그리고 DB를 어떻게 보여주고 가공하냐에 따라서 서비스는 그들의 기능을 제공해줍니다. 

 

Database 는 여러분들이 아시는 관리 시스템을 통해서 관리됩니다 (MySQL, MariaDB, Oracle ..... ). 하지만 이런 관리 시스템이 직접 "어떤 Data를 달라" 라는 요청을 수신하고, 이해하고, 응답을 보내는 매우 복잡한 과정의 TCP 통신을 지원하지 않습니다. 따라서 서버에는 소켓이 제어되고 TCP (일반적으로) 통신이 이루어져 요청 / 응답을 지원하는 프로그램이 있어야 하는데, 이를 백엔드 앱 (Back End Application) 이라고 부릅니다. Spring 은 이 Back End App 을 제작할 수 있도록 지원해주는 대표적인 프레임워크입니다. 

 

 

Back End의 역할, ORM 그리고 JPA

 

 

개인적으로 Back End의 가장 대표적인 역할을 두가지로 구분하자면 다음과 같습니다 .

 

1. 웹서버로부터 넘어오는 요청을 수신하고, 요청을 수행하고, 응답은 송신한다.
2. 요청에 해당하는 Data 를 효율적으로, 탄력적으로 DB와 통신하여 가져온다.

 

많은 사람들이 백엔드하면 1번의 역할을 대표적으로 떠올리고 생각하지만, 1번의 업무는 사실 웹서버와 연동이 되는 프레임워크라면 큰 어려움 없이 설계를 할 수 있습니다. 즉, 2번의 역할이 굉장히, 매우 중요한 백엔드 앱의 역할이라고 생각합니다.

 

백엔드 앱은 대부분 Java, Python 등의 객체지향 언어를 사용하는 프레임워크로 제작이 됩니다. 또한 대부분의 DB는 RDB 즉, 관계형 Database 를 사용하는데, 이 객체지향과 관계형 DB는 매우 비슷할 것 같으면서도 매우 다르다는 것을 현업을 하면 할수록 느껴가게 됩니다. 이 둘 사이를 자동으로 연결해주는 백엔드 앱의 기술을 ORM (Object Relational Mapping) 이라고 부릅니다. 그리고 Spring 의 대표적인 ORM 기술로는 Hibernate, JPA 가 있습니다. 그리고 이 카테고리의 포스트들은 이 JPA (Java Persistence API)에 대해서 다뤄볼 것입니다. (JPA와 Hibernate 는 역할체와 구현체의 관계입니다 (이 부분의 자세한 설명을 보시려면 맨 아래 출처 포스트 참고))

 

 

시작하기에 앞서, 위에서 언급한 ORM은 Spring 이 지원해주는 매우 대표적인 기술 중 하나입니다. 즉, 우리가 할 일이 아니고, 우리가 관계형 DB의 Table 과 객체지향 언어인 Java 의 Object 를 직접 매핑하는 방법을 알아볼 필요는 없습니다 (물론 ORM을 더욱 깊이 배우시는 분들은 화이팅!!). 하지만, Spring 안에서 이 ORM이 어떤 과정을 거치는지, 어떤 문제들을 어떻게 극복해내려 하는지, 어떤식으로 구현이 되는지를 매우 정교하게 이해하지 못하면, 백엔드 앱과 RDBMS 사이에서 일어나는 정말 (진짜 진짜 정말 많고 어렵습니다) 많은 문제들을 파악할 수가 없습니다. JPA가 어떤 Base 를 가지고 동작을 하는지 확실하게 알아볼 수 있도록 합시다 (물론 이해했다 싶을 때 또다른 문제가 발생하곤 합니다..) 

 

 

 

Persistence Context

 

 

JPA 에서 영속성 컨텍스트란, 엔티티를 영구적으로 저장하는 환경이라는 의미합니다. JPA에서 Persist 화 한다라는 말을 많이 사용하는데, 쉽게 "DB에 저장한다"의 의미로 보기도 하지만, 사실 "영속성 컨텍스트를 통해서 대상 엔티티를 영속화한다" 라는 의미를 가지고 있습니다. 즉, Entity 를 DB가 아니라 영속성 컨텍스트에 저장한다는 의미입니다.

 

 

Spring JPA의 요청을 관리하는 모습

 

 

 

 

실제로 컨텍스트를 보면서 살펴보겠습니다. 영속성 컨텍스트는 쉽게 말하면 앱에서 한 Database-Schema 를 관리하기 위해 Spring에서 해당 DB 통신을 전적으로 담당하는 공간이라고 볼 수 있을 것 같습니다 (뇌피셜입니다). 다음과 같이 metadata 를 작성하여 "helloJpa"라는 이름의 영속성 컨텍스트를 설정해볼 수 있습니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence
        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://java.sun.com/xml/ns/persistence/persistence_2_2.xsd"
        version="2.2"
>
    <persistence-unit name="helloJpa">
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/jpa-db"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!--옵션-->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <!--            <property name="hibernate.hbm2ddl.auto" value="create"/>-->
        </properties>
    </persistence-unit>
</persistence>

 

스프링 부트를 경험해보신 분들은 알겠지만, application.yml에 세팅해 주던 항목들을 많이 볼 수 있습니다. 즉, Spring Boot 에서는 DB 연결 정보를 설정할 시 그 앱에서 사용할 특정 DB url과 설정을 적어줬다면 (물론 여러 DB를 세팅할 수 있다), Boot 가 없는 그냥 JPA에서 영속성 컨텍스트는 위와 같이 metadata로 설정을 해서 통신할 DB에 영속성 컨텍스트를 직접 부여하는 모습인 것을 알 수 있습니다.

 

Spring에서는 @PersistenceContext를 통해서 쉽게 manager를 형성했다면, Spring 없이 원래 JPA에서는 사용하는 [영속성 컨텍스트]를 통해서 EntityManagerFactory를 뽑아내고, 그  팩토리를 통해서 Manager를 가져오고, 그  매니저가  해당 [영컨]과 통신할 프록시 역할을 수행해 준다고 보면 될 것 같다.

 

@Test
@DisplayName("META-INF JPA TEST")
void test1() {
    EntityManagerFactory helloJpa = Persistence.createEntityManagerFactory("helloJpa");

    EntityManager manager = helloJpa.createEntityManager();

    manager.close();

    helloJpa.close();
}


그래서 그 사이에서 Member Entity 객체를 하나 (직접 만들어서, id, name 담고) 추가하려고 해보면 에러가 발생한다. 

 

@Test
@DisplayName("META-INF JPA TEST")
void test1() {

    EntityManagerFactory helloJpa = Persistence.createEntityManagerFactory("helloJpa");
    EntityManager manager = helloJpa.createEntityManager();

    Member member = new Member();
    member.setId(1L);
    member.setName("memberA");

    manager.persist(member);

    manager.close();
    helloJpa.close();
}



Transaction 때문이다. 꼭 명심하자. 

 

JPA 안에서 DATA를 다루는 모든 일든은 Transaction 안에서 수행이 되어야 한다

 

따라서 위에 코드를 transaction 을 추가하면 다음과 같이 해주면 된다.

 

@Test
@DisplayName("META-INF JPA TEST")
void test1() {

    EntityManagerFactory helloJpa = Persistence.createEntityManagerFactory("helloJpa");
    EntityManager manager = helloJpa.createEntityManager();
    EntityTransaction tx = manager.getTransaction();
    
    tx.begin();
    
    try{
        Member member = new Member();
        member.setId(1L);
        member.setName("memberA");

        manager.persist(member);
		
        tx.commit();
    }catch(Exception e){
        tx.rollback();
    }finally{
        manager.close(); // 내부적으로 DB를 물고 동작하고, 계속 팩토리로 뽑아내기 때문에 꼭 종료시켜야 함
    }
   
    helloJpa.close();
}

 

Persistence Context 이어서 .. 

 

영컨이란 역할체, 객체 같은 개념이 아니라 [논리적인 개념]이다. 눈에 보이지 않고, 엔티티 메니저를 통해서 영속성 컨텍스트에 접근할 수 있는 것이다. 

 

1) 엔티티 매니저 (N:1 로  영컨과 연계되어, 영컨과의 프록시 역할을 한다고 보면 됨) 

 

2) 엔티티 팩토리 (엔티티 매니저를 팩토리 패턴으로 제공받을 수 있는 곳이다) 

 

* 엔티티의 생명 주기

 

1) 비영속 (영컨과 전혀 관계 없는 상태, new 상태) - 그냥 생성만 한 상태?

2) 영속 ( 영컨에 의해 관리가 등록된 상태)

3) 준영속 ( 영컨에 의해 관리가 해지된 상태)

4) 삭제 ( removed, 삭제된 상태 ) 

 

다음과 같은 모습을 보겠다. 

 

tx.begin();

try {

    Member member = new Member();
    member.setId(1L);
    member.setName("memberA"); // 비영속 상태

    System.out.println("BEF==============================");
    em.persist(member); // 영속화 상태 영컨에 의해 이 객체는 관리된다 (DB관리 대상이 아님)
    System.out.println("AFT==============================");

    tx.commit(); // 영컨에 있는 애들이 DB에 등록됨 (INSERT Query 가 실제로 날라가는 곳)

} catch (Exception e) {
    tx.rollback();
} finally {
    em.close(); // 내부적으로 DB를 물고 동작하고, 계속 팩토리로 뽑아내기 때문에 꼭 종료시켜야 함
}

 

위와 같이 비영속 상태화, 영속 상태를 구분할 수 있다. 또 Test 를 해보자면, BEF, AFT print 사이에서는 SQL문이 나가지 않은 것을 볼 수 있다. DB에 SQL들을 던지는 시점은 tx을 commit하는 시점임도 알 수 있다. 

 

왜 이렇게 복잡한 걸까? 지금 앱이랑 DB 사이에서 JPA 즉 영컨이라는게 낑겨서 뭘 열심히 해주는 것 같긴한데, 많이 복잡해 보인다. xml로 생성해줘야 하고, 설정해줘야하고, Factory 꺼내야 하고, 요청마다 Manager 꺼내야하고 꼭 종료시켜야 하고,,, CRUD를 본격적으로 사용하기 전까지만해도 이정도다. 이런걸 왜 쓰는걸까???

 

이제부터 차차 알아가보자. 

 

 

 

영컨의 이점. 쓰는 이유. 

 

1. 1차 캐시

1차 캐시

 

영속성 컨텍스트 (메니저) 에는 1차 캐시라는 공간이 있다. 

 

em.persist(member)를 한다면, 위 그림과 같이 1차 캐시 공간에 persist 하려는 Entity가 저장이 된다.

 

이 때, Map 형태로 저장이 되는데, Key = @Id의 id, Value = Entity 객체 자체가 된다.

 

이 상태에서 find(Member.class, {id}) 을 수행하게 되면,  바로 select SQL을 날리는게 아니라 1차 캐시를 먼저 쭉 뒤지게 된다.

이 이후로, member2를 조회한다고 하면, 1차캐시에 없으므로 바로  SQL을 날려서 DB에서 조회를 한다. 

그리고 이 member2 를 1차 캐시에 Id, Entity 형태로 똑같이 저장한 이후, 하던 곳으로 반환을 해주게 된다.

 

**** 중요 : 지금 이건 EntityManager 내에 있는 공간이다 *****  계속해서 앱 내에서 1차캐시를 매번 재사용 하는게 아니다 ****

 

그럼 이런 의문을 가질 수 있다. 

 

"아니 어차피 한 요청 끝나면 종료된다면서? 그러면 뭐 얼마 쓰지도 못하는건데 별로 소용 없는 거 아녀? SQL 아끼려 그런거 같은데.. "

 

맞는 말이다. Manager는 반드시 사용되고 종료되어야 하기 때문에 그 요청 내에서만 1차 캐시가 소용이 있다. 100명이 요청하면 100개 생기는거다. 고객 전체적으로 공유 되는 것은 JPA에서 2차 캐시라는 곳이 따로 있다. 어쨌든 그 찰나의 순간적으로만 이득을 볼 수 있는 영역이 1차 캐시.

 

Member member = new Member();
member.setId(1L);
member.setName("memberA"); // 비영속 상태

System.out.println("BEF==============================");
em.persist(member); // 영속화 상태 영컨에 의해 이 객체는 관리된다 (DB관리 대상이 아님)
System.out.println("AFT==============================");

Member member1 = em.find(Member.class, 1L);

System.out.println("member1 = " + member1.getId());
System.out.println("member1 = " + member1.getName());

tx.commit();

이렇게 진행이 된다면, select 쿼리는 날라가지 않는다. 

어쨌든 EntityManager 가 수행할 때에는 1차 캐시가 있다. 

 

2. 영속에 등록된 엔티티의 동일성 보장. 

 

1차 캐시라는 공간에 있으면 [영속 등록되었다] 고 말할 수 있다. 왜냐하면 새로 객체를 형성하거나, 조회를 하게 된다면, 1차 캐시에 들어가고, 그  이후 호출되는 그 객체에 대해선 1차 캐시에 있는 것을 이용하기 때문이다. 따라서 1차 캐시에 있는 녀석을 tx.commit()  전까지는 계속 사용하므로, 동일한 객체 (약간 미니 싱글톤) 를 보장해준다. 

 

Member member1 = em.find(Member.class, 1L);

System.out.println("member1 = " + member1.getId());
System.out.println("member1 = " + member1.getName());

Member member2 = em.find(Member.class, 1L);

System.out.println("result = " + (member1 == member2)); // result = true

 

3. 쓰기 지연

 

persist를 하거나, setter를 통해 1차캐시에서  보관중인 객체의 값을 바꾸면, 바로 SQL이 나가지 않고, 필요한 SQL을 쌓아두게 된다. 

trasaction.commit() 을 하는 순간, 엔티티 메니저는 em.flush()를 진행하여 쌓아둔 SQL들을 한번에 DB에 푸시를 한다. 그 이후 "실제 Database Transaction이 Commit된다" 고 하는데, 그 부분을 이해하지 못하겠다. 뭔말인지 모르겠슴.

 

어쨌든 왜 이렇게 하냐? 

 

1. 수정 등의 작업 때문. 

> 생성하고, 등록된  값에서 수정해야하는 일이 생기면, 쓰기 지연 로딩에서 SQL을 조금 줄일 수 있음. 

 

2. 버퍼링 작업 때문 ( 주요 )

> SQL을 모아놓았다가 한번에 Transaction 으로 보내버릴 수 있음. ( Batch 형 ) 

> hibernate.batch_size 라는 옵션을 줄 수 있는데, 그 size 만큼 모아서 DB 한번의 통신으로 보내고 commit 칠 수 있음. (commit 친다는게 뭐임???)

> 버퍼링 같이 모아서 보낼 수 있음. (SQL 은 IO 작업이기 때문에 오래걸리는 일임. 최소화시키는게 중요함!) 

 

 

4. 변경 감지 

 

더티 체킹

 

> tx.commit 을 하면, 엔티티와 스냅샷을 비교한다. 

> 1차 캐시 안에는 pk, entity, 가 있지만, SNAPSHOT이라는 값도 존재한다. 

> 스냅샷이란, 내가  어떻게든 1차 캐시안에 객체값을 넣은 시점에, 그 객체 값을 동일하게 본떠놓는 것이다. (조회해서 등록되든, em.persist해서 등록되든) 

> 그리고  tx.ocmmit 시 비교를 해서, 바뀌었으면 바뀐 객체들에 대하여 UPDATE 쿼리를 쓰기지연저장소에 저장해둡니다. 

> 신기방기 

 

 

내 생각에는 여기까지 봤을 때 가장 중요한 것은, 이 모든 것이 한 "요청" 안에 이루어지는 이점들이라는 점이다. JPA 및 Persistence Context 를 "실무"에서 사용할 때 Annotation들로 많이 사용하는데, 이 한 요청 안에 이루어진다는 내용을 생각해야 이해가 되는 부분들이 많았던 것 같다. 한번 더 강의를 들으면서 느껴지는 것은 "EM"은 한 요청을 끝내면 죽는다는 것이정말 중요한 것 같다. 그래서 비즈니스 로직을 설계할 떄 잘 생각하자. 

 

================================v

 

Flush 란?

별다른게 아니고, 영속성 컨텍스트의 변경내용을 데이터 베이스에 반영하는 것.

일어나는 일

 

1) 변경 감지 (Dirty Checking - SNAPSHOT 과 비교) 

2) 1)에 따른 SQL 쓰기지연 저장소에 등록

3) 쌓아둔 쿼리를 DB에 전송

 

Flush 이후에 DB 커밋이 일어남. (DB 커밋이 계속 등장하는데, 이게 뭔지?) 

 

<Database 이론 내용>

DB 커밋이란 이번 트랜젝션을 확정짓고 종료하겠다는 의미입니다. 커밋 전의 변경 내역들에 대해서는 Rollback 이 가능함. 

 

=====================================

 

Flush 가 발생하는 경우


1) Em.Flush() 를 직접 호출  (직접 할일은 거의 없음) 

2) tx. commit() 시 

3) JPQL 쿼리가 실행될 시 (이건 좀 별개의 이유인데, persist 만 해놓다가 JPQL 을 실행해서 조회를 하게되면 persist만 해놓은 애들은 DB에 날라간 적이 없으니 조회가 안된다. 따라서 이런것들을 방지하고자 JPQL 문이 실행되면 무조건 일단 flushing 을 진행한다) - 이건 em.setFlushMode 로 껐다 킬 수 있음 

 

void test(){
    tx.begin();
    try{
    
        Member mem = new Member(200L, "200Member");
        em.persist(mem);
        em.flush();
        
        System.out.println("===========================");
        tx.commit();
        
    catch(Exception e){
        tx.rollback();
    }finally{
        em.close();
    }
    emf.close();
}

 

이전에는 flush 없이 했을 경우, tx.commit()시 쓰기지연 저장소에서 모두 내보내 줬었지만, flush 를 직접 넣으니, ================ 위에 SQL이 날라가는 모습을 볼 수 있음. 

 

 FAQ : Flush 를 하게되면 1차 캐시가 지워지나요?

-- 아님. FLUSH 는 오직 쓰기 더티 감지 이후 지연 SQL 저장소에 있는 쿼리들이 DB에 반영이되는 과정

-- 어쨌든 이번 트랜젝션을 커밋하기 전에만 동기화하면 되니까, 그 과정이라고 그냥 간단히 보면 됨. 

 

이후에 DB 커밋이 일어나야 이 트랙섹션이 종료되면서 EM이 사라짐. 

 

======================================= 준영속 상태 (말이 준영속이지 그냥 아무것도 아닌거임 ㅋㅋ) 

 

1. em.persist 시 영속 상태가 됨. 

2. em.find, 혹은 queryDsl 을 사용하여 조회를 하였을 때도 영컨 안에 없으면 등록시켜줌 = 영속 상태가 됨. 

 

준영속 상태는 이 영속성 컨텍스트에서 분리 detach 시키는 행위, 기능 지원 없음

 

void test(){
    tx.begin();
    try{
    
        Member foundMember = em.find(Member.class, 150L); // 이후 영속성 대상
        foundMember.setName("AAAA"); // update 쿼리가 나갈 예정 (변경 사항이 발생) 
        
        em.detach(foundMember) // 이게 있으면 update 쿼리 안나가지. 더티체킹을 안해주니까
        
        System.out.println("===========================");
        tx.commit();
        
    catch(Exception e){
        tx.rollback();
    }finally{
        em.close();
    }
    emf.close();
}

 

detach 시 commit() 하여 flush 되는 시점에 더티 체킹이 진행되지 않는다. 따라서 update 쿼리가 안나가는 모습을 볼 수 있음. 

 

em.clear()를 하면 영컨을 쭉 비우기 때문에 초기화 되기도 함. 

em.close()는 영컨을 종료하기 떄문에 당연히 초기화 됨. 

clear 한번만 보면

void test(){
    tx.begin();
    try{
    
        Member mem = em.find(Member.class, 100L);
        mem.setName("AAAA")
        
        em.clear()
        
        Member mem2 = em.find(Member.class, 100L);
        
        System.out.println("=====================");
        tx.commit();
        
    catch(Exception e){
        tx.rollback();
    }finally{
        em.close();
    }
    emf.close();
}

 

이렇게하면 똑같은 member 를 조회함에도 select 쿼리가 두번 발생하는 걸 볼 수 있다. 

 

사실 지금 단계에서 이런것들을 정확히 제어하고 그 이유를 아는 것은 어렵다. 

 

 

정리

 

  • JPA에서는 매핑과정과 영속성 컨텍스트 (동작 원리)가 가장 중요하다 
  • 영컨이란 엔티티들을 관리하는 환경이라고 보면 된다. 영컨안에서 요청마다 em을 배치하여 활용하는 논리적인 그림
  • 비영속, 영속, 준영속이 있다. (비영속과 준영속은 사실 결론만 보면 같은 얘기) 
  • 이점은 1차캐시, 더티 체킹, 쓰기 지연 저장소 등의 이점이 있다. 
  • flush, commit 등에 대한 이해도 중요하다. 

 

 

 

 

https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/

 

JPA, Hibernate, 그리고 Spring Data JPA의 차이점

개요 Spring 프레임워크는 어플리케이션을 개발할 때 필요한 수많은 강력하고 편리한 기능을 제공해준다. 하지만 많은 기술이 존재하는 만큼 Spring 프레임워크를 처음 사용하는 사람이 Spring 프레

suhwan.dev

 

728x90
반응형

'Spring > JPA' 카테고리의 다른 글

[JPA] - 값 타입  (0) 2022.12.29
[JPA] 프록시와 연관관계 - 2  (0) 2022.12.28
[JPA] 프록시와 연관관계 정리  (0) 2022.12.26
[JPA] 연관관계 매핑 -2  (0) 2022.12.26
[JPA] - 엔티티 매핑  (1) 2022.12.21
'Spring/JPA' 카테고리의 다른 글
  • [JPA] 프록시와 연관관계 - 2
  • [JPA] 프록시와 연관관계 정리
  • [JPA] 연관관계 매핑 -2
  • [JPA] - 엔티티 매핑
문케이크
문케이크
    반응형
  • 문케이크
    누구나 개발할 수 있다
    문케이크
  • 전체
    오늘
    어제
    • 전체 보기 (122)
      • CS 이론 (13)
        • 운영체제 (8)
        • 네트워크 (2)
        • 알고리즘 (0)
        • Storage (3)
      • Spring (26)
        • Spring 기본 (12)
        • Spring 심화 (0)
        • JPA (11)
        • Spring Security (3)
      • 리액티브 (0)
        • RxJava (0)
      • SW 설계 (14)
        • OOP (0)
        • UML (3)
        • OOAD (0)
        • Design Pattern (11)
      • Java (8)
      • 웹 운영 (17)
        • AWS (15)
        • 운영 구축 (2)
      • Testing (3)
        • Unit (3)
      • Extra (3)
        • API 적용 (1)
      • 인프라 기술 (5)
        • Kubernetes (2)
        • Elasticsearch (3)
      • Logging (7)
        • Spring (5)
        • 인프라 (2)
      • 일상 (2)
        • 음식점 리뷰 (2)
        • Extra (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • 문케이크의 블로그
  • 인기 글

  • 태그

    junit
    Composite
    composition
    elasticsearch
    runtime exception
    김영한
    Spring
    decorator
    Java
    JPA
    spring boot
    spring container
    디자인 패턴
    OOAD
    GoF
    Configuration
    lazy loading
    SRP
    단위테스트
    OOP
    lombok
    Design Pattern
    di
    k8s
    analyzer
    객체지향
    n+1
    BEAN
    mockito
    Setter
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
문케이크
[JPA] JPA 개요와 영속성 컨텍스트
상단으로

티스토리툴바