K-Digital Training/내일배움캠프

입문 2주차/Spring

hoonssss 2023. 11. 21. 10:26
반응형
SMALL

3 Layer Architecture

Controller

대표사진 삭제

사진 설명을 입력하세요.

Service

대표사진 삭제

사진 설명을 입력하세요.

사용자의 요구사항을 처리(비즈니스 로직)

DB 저장 및 조회가 필요할 때는 Repository에게 요청함

Repository

대표사진 삭제

사진 설명을 입력하세요.

DB관리(연결, 해제, 자원 관리) 함

DB CRUD작업을 처리함.

 

전체적인 흐름

대표사진 삭제

사진 설명을 입력하세요.

Spring의 IoC DI

IoC, DI는 객체지향의 SOLID원칙, GoF의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴

IoC 제어의역전(설계원칙)

DI(의존성주입) 패턴을 사용해서 IoC 설계원칙을 구현하고 있다.

 

필드주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.food = new Chicken();
        consumer.eat();

        consumer.food = new Pizza();
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
 

메서드 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public void setFood(Food food) {
        this.food = food;
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.setFood(new Chicken());
        consumer.eat();

        consumer.setFood(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
 

생성자를 통한 주입

public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer(new Chicken());
        consumer.eat();

        consumer = new Consumer(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
 

IoC(제어의 역전)

필드, 메서드 주입에서는 Consumer -> Food

생성자 주입에서 Food -> Consumer에게 전달해주는 식으로 변경함으로써

Food -> Consumere로 역전되었음 이를 의미

DI(의존성주입)이라는 디자인패턴을 통해서 IoC설계를 구현

외부에서 미래 만든 객체를 주입하는게 DI 패턴

 

@Bean

Spring이 관리하고 있는 객체를 뜻함

IoC container는 Spring IoC Container로 이 Bean들을 모아둔 하나의 Container

대표사진 삭제

사진 설명을 입력하세요.

@Bean 등록 -> @Component or @Service, @(Rest)Controller, @Repository

대표사진 삭제

사진 설명을 입력하세요.

일반 Class를 Spring이 관리하는 Bean개체로 등록 하는 방법

 

MemoService, MemoRepository -> memoService, memoRepository로 IoC Container로 저장

 

대표사진 삭제

사진 설명을 입력하세요.

생성자 주입 -> 객체의 불변성을 지킬 수 있음

Bean 설정 후 MemoService생성자에 Autowired 설정해야 함 하지만 생성자가 한개일때는 생략가능(Spring 4.3이후 버전)

final -> 생성자 주입으로 초기화 가능

 

대표사진 삭제

사진 설명을 입력하세요.

Method 주입

final X , memoRepository 변경 시 필요

 

대표사진 삭제

사진 설명을 입력하세요.

필드주입

private는 외부에서 접근이 안되지만 Autowired 설정 시 Spring에서 접근 가능하게 함(권장안함)

 

대표사진 삭제

사진 설명을 입력하세요.

Lombok활용

@RequiredArgsConstructor -> final을 생성자로 설정함

 

즉 @Autowired를 사용해서 IoC에 있는 Bean객체를 Spring이 주입해줌

 

@Autowired 사용 조건

Spring Container(Component, Service, Repository, (Rest)Controller,,,)에 의해 관리가 되는 Bean Class만 주입 가능

 

수동주입

대표사진 삭제

사진 설명을 입력하세요.

Bean 이름을 통해 가져옴

대표사진 삭제

사진 설명을 입력하세요.

Bean 클래스를 통해 가져옴

 

JPA

Jdbc에서 필드하나를 추가하기 위해서는 SQL 수정 및 변경해야 할 코드가 엄청 많아짐

이를 보완하기 위해 ORM이 등장함

ORM

객체와 DB를 맵핑해주는 도구

반복적인 SQL 수정 및 변경을 줄이기 위해

 

JPA -> Java, ORM기술의 대표적인 표준 명세

JPA를 실제로 구현한 Framework 중 사실상 표준이 Hibernate

대표사진 삭제

사진 설명을 입력하세요.

JPA Entity

Entity는 JPA에서 관리되고 있는 Class, 즉 객체를 의미함

 

Java

초기설정

DB기존 memo사용

대표사진 삭제

사진 설명을 입력하세요.

<?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="1234"/>
            <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>
 

build.gradle

    // JPA 구현체인 hibernate
    implementation 'org.hibernate:hibernate-core:6.1.7.Final'
    // MySQL
    implementation 'mysql:mysql-connector-java:8.0.28'
 

Memo 코드(Java, Spring X)

package com.sparta.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity // JPA가 관리할 수 있는 Entity 클래스 지정
@Table(name = "memo") // 매핑할 테이블의 이름을 지정
public class Memo {
    @Id //테이블의 기본키 PK, Entity를 구분할때 사용,식별자 역할 수행
    @GeneratedValue(strategy = GenerationType.IDENTITY) //auto_incremen id 1씩 증가
    private Long id;

    // nullable: null 허용 여부(false일떄 null을 허용하지 않음)
    // unique: 중복 허용 여부 (false 일때 중복 허용)
    @Column(name = "username", nullable = false, unique = true)
    private String username;

    // length: 컬럼 길이 지정
    @Column(name = "contents", nullable = false, length = 500)
    private String contents;
}
 

test code

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
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"); //Factory 생성 xml기준으로
        em = emf.createEntityManager(); //Manager 생성 xml기준으로
    }

    @Test
    void test1() {

    }
}
 
대표사진 삭제

사진 설명을 입력하세요.

대표사진 삭제

사진 설명을 입력하세요.

 

영속성컨텍스트(PersistenceContext)

영속성=지속성

Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간

영속성컨텍스트라는 곳에서 PK로 Entity를 식별함

대표사진 삭제

사진 설명을 입력하세요.

EntityManager

영속성 컨텍스트에 접근하여 Entity 객체들을 조작하기 위해서는 EntityManager가 필요함

개발자들은 EntityManager를 사용해서 Entity를 저장하고 조회하고 수정하고 삭제할 수 있음(CRUD)

EntityManager는 EntityManagerFactory를 통해 생성하여 사용할 수 있음

대표사진 삭제

사진 설명을 입력하세요.

EntityManagerFactory

일반적으로 DB하나에 하나만 생성되어 App이 동작하는 동안 사용

대표사진 삭제

사진 설명을 입력하세요.

EntityManagerFactory를 만들기 위해서는 DB에 대한 정보를 전달해야 함

정보를 전달하기 위해서는 /resources/META-INF/ 위치에 persistence.xml 파일을 만들어 정보를 넣어두면됨

<?xml version="1.0" encoding="UTF-8"?>
   // void setUp() {
   //     emf = Persistence.createEntityManagerFactory("memo");
   //     em = emf.createEntityManager();
   // } Testcode Persistence를 xml에서 가져옴 name ="memo" 인식해서 밑에 코드 실행
<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="{비밀번호}"/>
            <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>
 
EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");
EntityManager em = emf.createEntityManager();
 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");

해당 코드를 호출하면 JPA는 persistence.xml 의 정보를 토대로 EntityManagerFactory를 생성

 

트랜잭션

DB 데이터들의 무결성과 정합성을 유지하기 위한 하나의 논리적 개념

DB의 데이터들을 안전하게 관리하기 위해 생겨난 개념

모든 SQL이 성공적으로 수행이 되면 DB에 영구적으로 변경을 반영하지만 SQL 중 단 하나라도 실패한다면 모든 변경을 되돌림

대표사진 삭제

사진 설명을 입력하세요.

START TRANSACTION; # 트랜잭션 시작

INSERT INTO memo (id, username, contents) VALUES (1, 'Robbie', 'Robbie Memo');
INSERT INTO memo (id, username, contents) VALUES (2, 'Robbert', 'Robbert Memo');
SELECT * FROM memo;
# COMMIT을 하지 않은 상태에서는 TRANSACTION 내부에 저장
COMMIT; # 트랜잭션을 커밋 -> DB 저장

SELECT * FROM memo;
 

JPA가 트랜잭션을 이용하여 효율적으로 관리함

대표사진 삭제

사진 설명을 입력하세요.

test실행 전

Memo -> Set,Get 추가, @GeneratedValue(strategy = GenerationType.IDENTITY) 제외(setId를 통해 id 값 받아올거임)

persistence.xml -> <property name="hibernate.hbm2ddl.auto" value="create" /> create -> update로

create면 Table을 계속 만듬(Table이 있든 없든 Drop 후 계속 만듬) , update면 update된 것만 Query가 수행

성공테스트

import com.sparta.entity.Memo;
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.DisplayName;
import org.junit.jupiter.api.Test;

public class EntityTest {

    EntityManagerFactory emf;
    EntityManager em;

    @BeforeEach
    void setUp() {
        emf = Persistence.createEntityManagerFactory("memo"); //Factory생성
        em = emf.createEntityManager(); //Manager생성
    }

    @Test
    @DisplayName("EntityTransaction 성공 테스트")
    void test1() { //Debug 실행
        EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.

        et.begin(); // 트랜잭션을 시작합니다.

        try { // DB 작업을 수행합니다.

            Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다. Debug 설정
            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 를 종료합니다.
    }
}
 

실패테스트

식별자값을 주지않아서 실패(@Id)

    @Test
    @DisplayName("EntityTransaction 실패 테스트")
    void test2() {
        EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.

        et.begin(); // 트랜잭션을 시작합니다.

        try { // DB 작업을 수행합니다.

            Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다.
//          memo.setId(1L); // 식별자 값
            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 를 종료합니다.
    }
 

영속성 컨텍스트 - Entity 객체를 효율적으로 관리하기 위해 만들어진 공간

 

영속성 컨텍스트 기능

 

1차 캐시

영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있음

저장하는 Entity 객체들이 1차 캐시 즉 캐시 저장소에 저장됨

캐시 저장소는 Map 자료구조 형태로 되어있음

key에는 @Id로 매핑한 기본 키 즉 식별자 값을 저장

value에는 해당 Entity 클래스 객체를 저장

영속성 컨텍스트는 캐시 저장소 Key에 저장한 식별자값을 사용하여 Entity 객체를 구분하고 관리

대표사진 삭제

사진 설명을 입력하세요.

 

영속성 컨텍스트가 캐시 저장소를 어떻게 활용하고 있는지

1. Entity 저장

test코드 참고

대표사진 삭제

사진 설명을 입력하세요.

@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();
}
 

2. Entity 조회

캐시 저장소에 조회하는 id가 존재하지 않는 경우

DB에 SELECT 조회 후 해당 값을 캐시 저장소에 저장하고 반환

대표사진 삭제

사진 설명을 입력하세요.

import com.sparta.entity.Memo;
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.DisplayName;
import org.junit.jupiter.api.Test;

public class EntityTest {

    EntityManagerFactory emf;
    EntityManager em;

    @BeforeEach
    void setUp() {
        emf = Persistence.createEntityManagerFactory("memo"); //Factory생성
        em = emf.createEntityManager(); //Manager생성
    }

   @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();
}
 

캐시 저장소에 조회하는 Id가 존재하는 경우

값이 있다면 해당 Entity 객체를 반환

대표사진 삭제

사진 설명을 입력하세요.

import com.sparta.entity.Memo;
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.DisplayName;
import org.junit.jupiter.api.Test;

public class EntityTest {

    EntityManagerFactory emf;
    EntityManager em;

    @BeforeEach
    void setUp() {
        emf = Persistence.createEntityManagerFactory("memo"); //Factory생성
        em = emf.createEntityManager(); //Manager생성
    }

@Test
@DisplayName("Entity 조회 : 캐시 저장소에 해당하는 Id가 존재하는 경우")
void test3() {
    try {

        Memo memo1 = em.find(Memo.class, 1);
        System.out.println("memo1 조회 후 캐시 저장소에 저장\n");

        Memo memo2 = em.find(Memo.class, 1);
        System.out.println("memo2.getId() = " + memo2.getId());
        System.out.println("memo2.getUsername() = " + memo2.getUsername());
        System.out.println("memo2.getContents() = " + memo2.getContents());


    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}
 
대표사진 삭제

사진 설명을 입력하세요.

memo1 find ->캐시 저장소 (1차 캐시) -> DB -> 캐시 저장소 저장

memo2가 find -> 캐시 저장소 -> 바로 반환

 

3. Entity 삭제

삭제할 Entity를 조회한 후 캐시 저장소에 없다면 DB에 조회해서 저장

대표사진 삭제

사진 설명을 입력하세요.

em.remove(memo); 호출 시 삭제할 Entity를 DELETED상태로 만든 후 트랜잭션 commit 후 Delete SQL이

DB에 요청

@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();
}
 

select -> delete상태로 전환 -> commit -> delete Query DB에 전달

 

1차 캐시 사용의 장점

DB조회 횟수를 줄임

'1차 캐시'를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)

 

쓰기 지연 저장소(ActionQueue)

@Test
@DisplayName("쓰기 지연 저장소 확인")
void test6() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        Memo memo = new Memo();
        memo.setId(2L);
        memo.setUsername("Robbert");
        memo.setContents("쓰기 지연 저장소");
        em.persist(memo);

        Memo memo2 = new Memo();
        memo2.setId(3L);
        memo2.setUsername("Bob");
        memo2.setContents("과연 저장을 잘 하고 있을까?");
        em.persist(memo2);

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

트랜잭션 commit 전
Hibernate: 
    /* insert com.sparta.entity.Memo
        */ insert 
    into
        memo (contents, username, id) 
    values
        (?, ?, ?)
Hibernate: 
    /* insert com.sparta.entity.Memo
        */ insert 
    into
        memo (contents, username, id) 
    values
        (?, ?, ?)
트랜잭션 commit 후
 

트랜잭션 commit 전

대표사진 삭제

사진 설명을 입력하세요.

em > actionQueue를 확인해보면 insertions > executables에 Insert할 memo#2, memo#3 Entity 객체 2개가 들어가 있는 것을 확인할 수 있음

 

트랜잭션 commit 후

대표사진 삭제

사진 설명을 입력하세요.

트랜잭션 commit 후 쓰기 지연 저장소의 SQL들이 한번에 요청됨

 

트랜잭션 commit 후 추가적인 동작이 있는데 em.flush(); 메서드의 호출

flush 메서드는 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행 즉, 쓰기 지연 저장소의 SQL들을 DB에 요청하는 역할을 수행

flush()

@Test
@DisplayName("flush() 메서드 확인")
void test7() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        Memo memo = new Memo();
        memo.setId(4L);
        memo.setUsername("Flush");
        memo.setContents("Flush() 메서드 호출");
        em.persist(memo);

        System.out.println("flush() 전");
        em.flush(); // flush() 직접 입력 호출
        System.out.println("flush() 후\n");
        

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

flush() 전
Hibernate: 
    /* insert com.sparta.entity.Memo
        */ insert 
    into
        memo (contents, username, id) 
    values
        (?, ?, ?)
flush() 후

트랜잭션 commit 전
트랜잭션 commit 후
 
대표사진 삭제

사진 설명을 입력하세요.

insert, update, delete와 같이 데이터를 변경하는 SQL을 데이터베이스에 요청하고 또 반영하기 위해서는 트랜잭션 환경이 꼭 필요함

 

변경 감지(Dirty Checking)

영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면

하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적

JPA -> Update메서드는 따로 없음

대표사진 삭제

사진 설명을 입력하세요.

@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("변경 감지 확인");
        //find -> set // em.persist(memo); -> 자동으로 반영됨
        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}
 

em.flush() 이후 Entity <-> LoadedState 비교하여 다르다면 DB에 update Query 요청

대표사진 삭제

사진 설명을 입력하세요.

entityInstance는 Entity 객체의 현재 상태

loadedState는 조회했을 때 Entity의 최초 상태(위 코드로 확인시는 최초 id4번 값이 나옴)

트랜잭션 commit 후 em.flush() 메서드가 호출되면 현재 상태와 최초 상태를 비교하고 변경이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장한 후 DB에 요청

find -> set을 통해 수정하면 em.persist(memo);

 

Entity의 상태

대표사진 삭제

사진 설명을 입력하세요.

비영속

new 연산자를 통해 인스턴스화 된 Entity 객체를 의미

영속성 컨텍스트에 저장되지 않았기 때문에 JPA의 관리를 받지 않음

Memo memo = new Memo(); // 비영속 상태
memo.setId(1L);
memo.setUsername("Robbie");
memo.setContents("비영속과 영속 상태");
 
대표사진 삭제

사진 설명을 입력하세요.

영속

em.persist(memo) 를 통해 비영속 -> 영속 상태로 변환

em.persist(memo);
 
대표사진 삭제

사진 설명을 입력하세요.

 

준영속

영속성 컨텍스트에 저장되어 관리되다가 분리된 상태를 의미

특정 Entity를 준영속 상태로 전환

em.detach(memo)

메서드를 호출하여 특정 Entity 객체 Memo#1를 영속성 컨텍스트에서 제거

준영속 상태로 전환되면 1차 캐시 즉, 캐시 저장소에서 제거되기 때문에 JPA의 관리를 받지 못해 영속성 컨텍스트의 어떠한 기능도 사용할 수 없음

import com.sparta.entity.Memo;
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.DisplayName;
import org.junit.jupiter.api.Test;

public class EntityStateTest {

    EntityManagerFactory emf;
    EntityManager em;

    @BeforeEach
    void setUP(){
        emf = Persistence.createEntityManagerFactory("memo");
        em = emf.createEntityManager();
    }

    @Test
    @DisplayName("준영속 상태 : detach()")
    void test2() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        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());

            // em.contains(entity) : Entity 객체가 현재 영속성 컨텍스트에 저장되어 관리되는 상태인지 확인하는 메서드
            System.out.println("em.contains(memo) = " + em.contains(memo));

            System.out.println("detach() 호출");
            em.detach(memo);
            System.out.println("em.contains(memo) = " + em.contains(memo));

            System.out.println("memo Entity 객체 수정 시도");
            memo.setUsername("Update");
            memo.setContents("memo Entity Update");

            System.out.println("트랜잭션 commit 전");
            et.commit();
            System.out.println("트랜잭션 commit 후");

        } catch (Exception ex) {
            ex.printStackTrace();
            et.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
memo.getId() = 1
memo.getUsername() = Robbie
memo.getContents() = 비영속과 영속 상태
em.contains(memo) = true
detach() 호출
em.contains(memo) = false
memo Entity 객체 수정 시도
트랜잭션 commit 전
트랜잭션 commit 후
 

em.detach(memo); 영속성 컨텍스트를 사용할 수 없음

영속성 컨텍스트로 관리되고 있는 영속 상태일때만 변경감지가 진행됨

 

em.clear()

영속성 컨텍스트를 완전히 초기화

영속성 컨텍스트의 모든 Entity를 준영속 상태로 전환

영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태

따라서 계속해서 영속성 컨텍스트를 이용할 수 있음

@Test
@DisplayName("준영속 상태 : clear()")
void test3() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo1 = em.find(Memo.class, 1);
        Memo memo2 = em.find(Memo.class, 2);

        // em.contains(entity) : Entity 객체가 현재 영속성 컨텍스트에 저장되어 관리되는 상태인지 확인하는 메서드
        System.out.println("em.contains(memo1) = " + em.contains(memo1));
        System.out.println("em.contains(memo2) = " + em.contains(memo2));

        System.out.println("clear() 호출");
        em.clear();
        System.out.println("em.contains(memo1) = " + em.contains(memo1));
        System.out.println("em.contains(memo2) = " + em.contains(memo2));

        System.out.println("memo#1 Entity 다시 조회");
        Memo memo = em.find(Memo.class, 1);
        System.out.println("em.contains(memo) = " + em.contains(memo));
        System.out.println("\n memo Entity 수정 시도");
        memo.setUsername("Update");
        memo.setContents("memo Entity Update");

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
em.contains(memo1) = true
em.contains(memo2) = true
clear() 호출
em.contains(memo1) = false
em.contains(memo2) = false
memo#1 Entity 다시 조회
Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
em.contains(memo) = true

 memo Entity 수정 시도
트랜잭션 commit 전
Hibernate: 
    /* update
        com.sparta.entity.Memo */ update memo 
    set
        contents=?,
        username=? 
    where
        id=?
트랜잭션 commit 후
 

close()

영속성 컨텍스트를 종료

해당 영속성 컨텍스트가 관리하던 영속성 상태의 Entity들은 모두 준영속 상태로 변경

영속성 컨텍스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없음

@Test
@DisplayName("준영속 상태 : close()")
void test4() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo1 = em.find(Memo.class, 1);
        Memo memo2 = em.find(Memo.class, 2);

        // em.contains(entity) : Entity 객체가 현재 영속성 컨텍스트에 저장되어 관리되는 상태인지 확인하는 메서드
        System.out.println("em.contains(memo1) = " + em.contains(memo1));
        System.out.println("em.contains(memo2) = " + em.contains(memo2));

        System.out.println("close() 호출");
        em.close();
        Memo memo = em.find(Memo.class, 2); // Session/EntityManager is closed 메시지와 함께 오류 발생
        System.out.println("memo.getId() = " + memo.getId());

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}
 

준영속 상태에서 다시 영속 상태로 바꾸는 방법

em.merge(entity);

전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환

파라미터로 전달된 Entity의 식별자 값으로 영속성 컨텍스트를 조회함

  1. 해당 Entity가 영속성 컨텍스트에 없다면?

DB에서 새롭게 조회(Select)

조회한 Entity를 영속성 컨텍스트에 저장

전달 받은 Entity의 값을 사용하여 병합

Update SQL이 수행 (수정)

  1. 만약 DB에서도 없다면 ?

새롭게 생성한 Entity를 영속성 컨텍스트에 저장

Insert SQL이 수행(저장)

즉 merge(entity) 메서드는 비영속, 준영속 모두 파라미터로 받을 수 있으며 상황에 따라 ‘저장’을 할 수도 ‘수정’을 할 수도 있음

 

merge(entity) 저장

@Test
@DisplayName("merge() : 저장")
void test5() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo = new Memo();
        memo.setId(3L);
        memo.setUsername("merge()");
        memo.setContents("merge() 저장");

        System.out.println("merge() 호출");
        Memo mergedMemo = em.merge(memo);

        System.out.println("em.contains(memo) = " + em.contains(memo));
        System.out.println("em.contains(mergedMemo) = " + em.contains(mergedMemo));

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}
 
대표사진 삭제

사진 설명을 입력하세요.

id 3을 찾기위해서 Select -> DB에 id 3 없으니 insert Query ActionQueue에 담금 -> commit -> insert

 

merge(entity)수정

@Test
@DisplayName("merge() : 수정")
void test6() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo = em.find(Memo.class, 3);
        System.out.println("memo.getId() = " + memo.getId());
        System.out.println("memo.getUsername() = " + memo.getUsername());
        System.out.println("memo.getContents() = " + memo.getContents());

        System.out.println("em.contains(memo) = " + em.contains(memo));

        System.out.println("detach() 호출");
        em.detach(memo); // 준영속 상태로 전환
        System.out.println("em.contains(memo) = " + em.contains(memo));

        System.out.println("준영속 memo 값 수정");
        memo.setContents("merge() 수정");

        System.out.println("\n merge() 호출");
        Memo mergedMemo = em.merge(memo);
        System.out.println("mergedMemo.getContents() = " + mergedMemo.getContents());

        System.out.println("em.contains(memo) = " + em.contains(memo));
        System.out.println("em.contains(mergedMemo) = " + em.contains(mergedMemo));

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
memo.getId() = 3
memo.getUsername() = merge()
memo.getContents() = merge() 저장
em.contains(memo) = true
detach() 호출
em.contains(memo) = false
준영속 memo 값 수정

 merge() 호출
Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
mergedMemo.getContents() = merge() 수정
em.contains(memo) = false
em.contains(mergedMemo) = true
트랜잭션 commit 전
Hibernate: 
    /* update
        com.sparta.entity.Memo */ update memo 
    set
        contents=?,
        username=? 
    where
        id=?
트랜잭션 commit 후
 

select -> detach 준영속상태 -> set (수정) -> merge(병합) -> commit

 

삭제

em.remove(Entity)

대표사진 삭제

사진 설명을 입력하세요.

 

SPring Data JPA

Spring Boot Data JPA

build.gradle

    // JPA 설정
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 

application.properties

Hibernamte 설정

spring.jpa.hibernate.ddl-auto=update 

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
 

ddl-auto에는 옵션이 5개 있음

create -> 기존에 있던 table을 전부 삭제한 다음에 다시 생성(drop+create)

create-drop -> create랑 같긴 같은데 종료 시점에 table을 drop

update -> 변경된 부분만 반영

validate -> Entity와 table이 정상적으로 mapping되어있는지 확인(유효성검사)

none -> 아무것도안함

 

MemoEntity(일반 POJO class -> JPA)

package com.sparta.memo.entity;

import com.sparta.memo.dto.MemoRequestDto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class Memo {
    private Long id;
    private String username;
    private String contents;

    public Memo(MemoRequestDto requestDto) {
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
    }

    public void update(MemoRequestDto memoRequestDto) {
        this.username = memoRequestDto.getUsername();
        this.contents = memoRequestDto.getContents();
    }
}
-----------------------------------------------------------
package com.sparta.memo.entity;

import com.sparta.memo.dto.MemoRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity // JPA가 관리할 수 있는 Entity 클래스 지정
@Getter
@Setter
@Table(name = "memo") // 매핑할 테이블의 이름을 지정
@NoArgsConstructor
public class Memo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "username", nullable = false)
    private String username;
    @Column(name = "contents", nullable = false, length = 500)
    private String contents;

    public Memo(MemoRequestDto requestDto) {
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
    }

    public void update(MemoRequestDto requestDto) {
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
    }
}
 

Spring Boot 환경에서는 EntityManageFactory랑 EmtityManager를 자동으로 생성해줌

application properties에 정보를 주면 들어있는 정보를 바탕으로 자동으로 만듬

 

Spring 프레임워크에서는 DB의 Transactional 개념을 app에 적용할 수 있도록 관리자를 제공

@Transactional 애너테이션을 클래스나 메서드에 추가하면 쉽게 트랜잭션 개념을 적용가능

@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
						...
			
		@Transactional
		@Override
		public <S extends T> S save(S entity) {
		
			Assert.notNull(entity, "Entity must not be null");
		
			if (entityInformation.isNew(entity)) {
				em.persist(entity);
				return entity;
			} else {
				return em.merge(entity);
			}
		}
						...
}
 

메서드가 호출되면, 해당 메서드 내에서 수행되는 모든 DB 연산 내용은 하나의 트랜잭션으로 묶임.

이때, 해당 메서드가 정상적으로 수행되면 트랜잭션을 커밋하고, 예외가 발생하면 롤백.

클래스에 선언한 @Transactional은 해당 클래스 내부의 모든 메서드에 트랜잭션 기능을 부여.

이때, save 메서드는 @Transactional 애너테이션이 추가되어있기 때문에 readOnly = true 옵션인 @Transactional을 덮어쓰게 되어 readOnly = false 옵션으로 적용.

(save메서드는 데이터를 저장, 수정하기 위해 readOnly = false로 하기 위함)

readOnly = True

트랜잭션에서 데이터를 읽기만 할 때 사용.

이 속성을 사용하면 읽기 작업에 대한 최적화를 수행할 수 있습니다.

만약, 해당 트랜잭션에서 데이터를 수정하려고 하면 예외가 발생하기 때문에 주의

 

Transactional TestCode

package com.sparta.memo;

import com.sparta.memo.entity.Memo;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
public class TransactioTest {

    @PersistenceContext
    EntityManager em;

    @Test
    @Transactional
    @Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
    @DisplayName("메모 생성 성공")
    void test1() {

        Memo memo = new Memo();
        memo.setUsername("Robbert");
        memo.setContents("@Transactional 테스트 중!");

        em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
    }
}
------------------------------------------------------------------------
    @Test
    @DisplayName("메모 생성 실패")
    void test2() {
        Memo memo = new Memo();
        memo.setUsername("Robbie");
        memo.setContents("@Transactional 테스트 중!");

        em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
    }
   
 

실패 코드 @Transactional을 적용하지 않았기 때문에 밑 오류 발생

실패 코드는 @Disabled 달아서 테스트를 안하겠다고 선언해도 됨

대표사진 삭제

사진 설명을 입력하세요.

JPA를 사용하여 DB에 데이터를 저장, 수정, 삭제 하려면 트랜잭션 적용이 반드시 필요

 

영속성 컨텍스트와 트랜잭션의 생명주기

스프링 컨테이너 환경에서는 영속성 컨텍스트와 트랜잭션의 생명주기가 일치

트랜잭션이 유지되는 동안은 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능을 사용 가능서 실패 테스트 코드 메서드에 트랜잭션이 적용되지 않았기 때문에 영속성 컨텍스트가 유지되지 못해 오류가 발생했음

 

트랜잭션 전파

대표사진 삭제

사진 설명을 입력하세요.

Memo Repository 하단에 추가

    @Transactional
    public Memo createMemo(EntityManager em) {
        Memo memo = em.find(Memo.class, 1);
        memo.setUsername("Robbie");
        memo.setContents("@Transactional 전파 테스트 중!");

        System.out.println("createMemo 메서드 종료");
        return memo;
    }
 

TestCode

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemoRepository memoRepository;

    @Test
    @Transactional
    @Rollback(value = false)
    @DisplayName("트랜잭션 전파 테스트")
    void test3() { //부모메서드
        memoRepository.createMemo(em); //자식메서드
        System.out.println("테스트 test3 메서드 종료");
    }

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
createMemo 메서드 종료
테스트 test3 메서드 종료
Hibernate: 
    /* update
        for com.sparta.memo.entity.Memo */update memo 
    set
        contents=?,
        username=? 
    where
        id=?
 

부모메서드가 끝난 후 Commit이 일어남

즉 자식메서드 트랜잭션이 부모메서드 트랜잭션과 합쳐짐

대표사진 삭제

사진 설명을 입력하세요.

트랜잭션 default 값이 REQUIRED이기 때문

REQUIRED -> 부모메서드에 트랜잭션이 존재한다면 자식메서드의 트랜잭션은 부모메서드의 트랜잭션에 합류

부모클래스의 트랜잭션을 없앤다면

    @Test
//    @Transactional
//    @Rollback(value = false)
    @DisplayName("트랜잭션 전파 테스트")
    void test3() {
        memoRepository.createMemo(em);
        System.out.println("테스트 test3 메서드 종료");
    }

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
createMemo 메서드 종료
Hibernate: 
    /* update
        for com.sparta.memo.entity.Memo */update memo 
    set
        contents=?,
        username=? 
    where
        id=?
테스트 test3 메서드 종료
 

자식메서드의 return하면서 update실행

 

Spring Data JPA란?

 
사진 삭제

사진 설명을 입력하세요.

Spring Data JPA는 JPA를 쉽게 사용할 수 있게 만들어놓은 하나의 모듈

- JPA를 추상화시킨 Repository 인터페이스를 제공

 
사진 삭제

사진 설명을 입력하세요.

Spring Data JPA에서는 JpaRepository 인터페이스를 구현하는 클래스를 자동으로 생성해줌

Spring 서버가 뜰 때 JpaRepository 인터페이스를 상속받은 인터페이스가 자동으로 스캔이 되면,

해당 인터페이스의 정보를 토대로 자동으로 SimpleJpaRepository 클래스를 생성해 주고, 이 클래스를 Spring ‘Bean’으로 등록함.

따라서 인터페이스의 구현 클래스를 직접 작성하지 않아도 JpaRepository 인터페이스를 통해 JPA의 기능을 사용할 수 있음.

Spring Data JPA 사용방법

JpaRepository 등록

 
사진 삭제

사진 설명을 입력하세요.

JpaRepository<"@Entity 클래스", "@Id 의 데이터 타입">를 상속받는 interface 로 선언.

Spring Data JPA에 의해 자동으로 Bean 등록.

제네릭스의 @Entity 클래스 위치에 Memo Entity를 추가했기 때문에 해당 MemoRepository는 DB의 memo 테이블과 연결되어 CRUD 작업을 처리하는 인터페이스가 됨

 

memo Jdbc -> Spring Data JPA

package com.sparta.memo.service;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import com.sparta.memo.repository.MemoRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class MemoService {

    private final MemoRepository memoRepository;

    public MemoService(MemoRepository memoRepository) {
        this.memoRepository = memoRepository;
    }

    public MemoResponseDto createMemo(MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        Memo saveMemo = memoRepository.save(memo);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(saveMemo);

        return memoResponseDto;
    }

    public List<MemoResponseDto> getMemos() {
        // DB 조회
        return memoRepository.findAll()
                               .stream()
                               .map(MemoResponseDto::new)
                               .toList();
//stream에서 memo가 하나씩 빠져나가고 mpa에 의해서 변환이 될건데 MemoResponseDto에 생성자 중에
// memo를 파라미터로 가지고 있는 생성자가 호출이 되고 그게 하나씩 변환이 되면서 그 값들을 List로 바꿔준다
    }

    @Transactional
    public Long updateMemo(Long id, MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findMemo(id);

        // memo 내용 수정
        memo.update(requestDto);

        return id;
    }

    public Long deleteMemo(Long id) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findMemo(id);

        // memo 삭제
        memoRepository.delete(memo);

        return id;
    }

    private Memo findMemo(Long id) {
        return memoRepository.findById(id).orElseThrow(() ->
                new IllegalArgumentException("선택한 메모는 존재하지 않습니다.")
        );
    }
}
---------------------------------------------------------------------------
package com.sparta.memo.repository;

import com.sparta.memo.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MemoRepository extends JpaRepository<Memo, Long> { //Entity -> Memo

}
 

JPA Auditing

Spring Data JPA에서는 시간에 대해서 자동으로 값을 넣어주는 기능인 JPA Auditing을 제공

Entity폴더에 Timestamped 생성 후

package com.sparta.memo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {

    @CreatedDate
    @Column(updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime modifiedAt;
}
 

@MappedSuperclass

JPA Entity 클래스들이 해당 추상 클래스를 상속할 경우 createdAt, modifiedAt 처럼 추상 클래스에 선언한 멤버변수를 컬럼으로 인식할 수 있습니다.

@EntityListeners(AuditingEntityListener.class)

해당 클래스에 Auditing 기능을 포함시켜 줍니다.

@CreatedDate

Entity 객체가 생성되어 저장될 때 시간이 자동으로 저장됩니다.

최초 생성 시간이 저장되고 그 이후에는 수정되면 안되기 때문에 updatable = false 옵션을 추가합니다.

@LastModifiedDate

조회한 Entity 객체의 값을 변경할 때 변경된 시간이 자동으로 저장됩니다.

처음 생성 시간이 저장된 이후 변경이 일어날 때마다 해당 변경시간으로 업데이트됩니다.

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용합니다.

DB에는 Date(날짜), Time(시간), Timestamp(날짜와 시간)라는 세 가지 타입이 별도로 존재합니다.

DATE : ex) 2023-01-01

TIME : ex) 20:21:14

TIMESTAMP : ex) 2023-01-01 20:22:38.771000

 

Application에 @EnableJpaAuditing 생성해야 적용 실행함

package com.sparta.memo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class MemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MemoApplication.class, args);
    }
}
 

Memo Entity

 
사진 삭제

사진 설명을 입력하세요.

상속을 받고

반환해주는 MemoResponseDto에 적용

@Getter
public class MemoResponseDto {
    private Long id;
    private String username;
    private String contents;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    public MemoResponseDto(Memo memo) {
        this.id = memo.getId();
        this.username = memo.getUsername();
        this.contents = memo.getContents();
        this.createdAt = memo.getCreatedAt();
        this.modifiedAt = memo.getModifiedAt();
    }
 
 
사진 삭제

사진 설명을 입력하세요.

 

Query Methods란?

Spring Data JPA에서는 메서드 이름으로 SQL을 생성할 수 있는 Query Methods 기능을 제공

메서드 이름을 분석해서 SimpleJpaRepository에서 자동으로 SQL 구현 함

findAllByOrderByModifiedAtDesc() -> Modified(수정)기준으로 전체 데이터를 내림차순으로 가져오는 SQL구문 메서드 생성

List<Memo> findAllByUsername(String username) -> where username = 파라미터(username)

 

package com.sparta.memo.repository;

import com.sparta.memo.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MemoRepository extends JpaRepository<Memo, Long> { //Entity -> Memo
    //findAll, ByOrderBy, ModifiedAt, Desc
    List<Memo> findAllByOrderByModifiedAtDesc();
    List<Memo> findAllByUsername(String username); //where username = 파라미터 라고 생각하면 됨
}
 

Service class

findAll() -> 생성한 메서드 호출(findAllByOrderByModifiedAtDesc() )

    public List<MemoResponseDto> getMethod() {
        return memoRepository.findAllByOrderByModifiedAtDesc()
                .stream()
                .map(MemoResponseDto::new)
                .toList();
 

 

반응형
LIST

'K-Digital Training > 내일배움캠프' 카테고리의 다른 글

숙련 2주차/Spring  (0) 2023.11.21
숙련 1주차/Spring  (2) 2023.11.21
입문 1주차/Spring  (1) 2023.11.21
자바 5주차  (1) 2023.11.01
자바 4주차  (1) 2023.11.01