웹공부/SPRING

스파르타 코딩 클럽 - Spring 4주차

Rabet 2024. 7. 26. 21:51

 

 

마지막 4주차 시작!

 

[개발자 입문]웹개발의 봄, Spring - 4주차 | Notion

PDF 파일

teamsparta.notion.site

 

Ctrl + B : 코드에서 들어가기

Shift + Shift : 자바클래스 검색

 

4-1 JPA란 무엇일까?

ORM 이란 ?

객체와 데이터베이스를 매핑해주는 도구

JPA : 자바 ORM기술의 표준 명세 ≒ Hibernate (사실상 표준)

 

4-2 Entity 이해하기

 

1. jpa-core 이름의 새 프로젝트 생성

Spring Intalizr환경이 아닌 그냥 자바환경 프로젝트 설정

 

2. META-INF (디레토리) > persistence.xml (파일) 생성

 

<class>class의 경로</class>

<?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">
        <!--Entity 클래스로 관리할 클래스의 경로-->
        <class>com.sparta.entity.Memo</class>
       
        <properties>
            <!--DB 정보 불러오기-->
            <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="happy60997"/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/memo"/>
            
            <!--테이블을 create해줌-->
            <property name="hibernate.hbm2ddl.auto" value="create" />
           
            <!--hibernate에서 sql만드는 실행 보여주기-->
            <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>

{비밀번호} 바꿔주기

 

 

3. build.gradle 파일에 코드 추가

dependencies {
    // JPA 구현체인 hibernate
    implementation 'org.hibernate:hibernate-core:6.1.7.Final'
    // MySQL
    implementation 'mysql:mysql-connector-java:8.0.28'
    
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}

 

 

4. memo 데이터베이스 연동 후 memo테이블 삭제

memo 테이블 삭제하기

- com.sparta의 main클래스 삭제

 

5. test > java > EntityTest.java 생성

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() {
        //Persistence 이름 넣어서 Factory 만들기
        emf = Persistence.createEntityManagerFactory("memo");
        //Factory에서 Entity매니저 만들기
        em = emf.createEntityManager();
    }

    @Test
    void test1() {

    }
}

 

 

6. entity 패키지 생성 > Memo 자바 클래스 생성

 

@Entity(name = "~~~~")으로 클래스 이름 지정 가능

@Table(nam = "~~~~") 매핑할 테이블의 이름 지정

@Id : 기본키 지정

@Column : 컬럼 지정

package com.sparta.entity;

import jakarta.persistence.*;

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

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

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

 

7. EntityTest 실행

memo 테이블 생성 완료

 

auto_ncrement 추가

 

 

4-3 지속성(영속성) 컨텍스트 

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

EntityManagerFactory 과정

 

 

 

- 트랜잭션이란? 

START TRANSACTION; 을 시작하고 들어간 쿼리들은

COMMIT;을 하기 전까지 실제로 적용되지 않는다.

콘솔창 열기

START TRANSACTION; # 트랜잭션을 시작합니다.

INSERT INTO memo (id, username, contents) VALUES (1, 'Rabat', 'Rabat Memo');
INSERT INTO memo (id, username, contents) VALUES (2, 'Rabbit', 'Rabbit Memo');
SELECT * FROM memo;

COMMIT; # 트랜잭션을 커밋합니다.

SELECT * FROM memo;

 

 

- JPA의 트랜잭션

앞의 트랜잭션처럼 JPA도

영속성 컨텍스트에 있는 Entity를 지연 저장소에서 관리하고 있다가 COMMIT하면 DB에 저장한다.

 

src > test > java > EntityTest의 @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("Rabat");
            memo.setContents("영속성 컨텍스트와 트랜잭션 이해하기");

            em.persist(memo); // EntityManager 사용하여 memo 객체를 영속성 컨텍스트에 저장합니다.

            et.commit(); // 오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출합니다.
            // commit 이 호출되면서 DB 에 수행한 DB 작업들이 반영됩니다.
        } catch (Exception ex) {
            System.out.println("auto가 있거나 식별자 값을 넣어주지 않아 오류가 발생했습니다.");
            ex.printStackTrace();
            et.rollback(); // DB 작업 중 오류 발생 시 rollback 을 호출합니다.
        } finally {
            em.close(); // 사용한 EntityManager 를 종료합니다.
        }

        emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.
    }
}

 

 

 

- EntityTest 디버그해보기

 

1. Memo.java

마지막에 Alt + Ins > Getter & Setter > 모두 선택 > 확인

package com.sparta.entity;

import jakarta.persistence.*;

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

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

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

    public Long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getContents() {
        return contents;
    }

    public void setContents(String contents) {
        this.contents = contents;
    }
}

 

2. Memo.java에서 다음 코드 지우기 

@GeneratedValue(strategy = GenerationType.IDENTITY) //자동증가 값

 

3. persistance.xml 바꾸기

            <!--테이블을 update로 해줌-->
            <property name="hibernate.hbm2ddl.auto" value="update" />

 

4. DB의 memo 테이블 삭제하기

memo 테이블 삭제하기

 

- 디버그 실행

마지막에 파란버튼 눌러 디버그 하기
하나씩 실행 , 모두 실행시키고 싶으면 파란버튼
Persist 명령문이 지난 후, EntityManager로 저장된 모습
원래 있던 memo을 삭제 후 만들어진 모습

 

4-4 연속성 컨텍스트의 기능

 

1. 1차 캐시 저장소 알아보기

src > test > java > PersistenceTest.java 생성

 

Ⅰ. Entity 저장

DB의 memo 테이블 삭제 후 실행

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 PersistentenceTest {

    EntityManagerFactory emf;
    EntityManager em;

    @BeforeEach
    void setUp() {
        //Persistence 이름 넣어서 Factory 만들기
        emf = Persistence.createEntityManagerFactory("memo");
        //Factory에서 Entity매니저 만들기
        em = emf.createEntityManager();
    }

    //Entity 저장
    @Test
    @DisplayName("1차 캐시 : Entity 저장")
    void test1() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = new Memo();
            memo.setId(1L);
            memo.setUsername("Rabat");
            memo.setContents("1차 캐시 Entity 저장");

            em.persist(memo);

            et.commit();

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

        emf.close();
    }


}

 

 

Ⅱ. Entity 조회

- 예제 1. Memo memo = em.find(Memo.class, 1); 호출 시 캐시 저장소에 해당 값이 존재하지 않기 때문에 DB에 SELECT 조회하여 캐시 저장소에 저장한 후 반환합니다.

//조회 예제1
@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();
}

 

 

 

 

예제1 실행 결과

 

- 예제 2.

SELECT를 한번만 합니다.

Memo memo2 = em.find(Memo.class, 1);  를 호출했을 때는 이미 캐시 저장소에 해당 값이 존재하기 때문에 DB에 조회하지 않고 캐시 저장소에서 해당 값을 반환합니다.

 

    //조회 예제2
    @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();
    }

예제2 실행 결과

 

- 객체 동일성 보장

    //객체 동일성 보장
    @Test
    @DisplayName("객체 동일성 보장")
    void test4() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {
            Memo memo3 = new Memo();
            memo3.setId(2L);
            memo3.setUsername("Robbert");
            memo3.setContents("객체 동일성 보장");
            em.persist(memo3);

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

            System.out.println(memo1 == memo2);
            System.out.println(memo1 == memo);

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

        emf.close();
    }

실행결과

 

Ⅲ. Entity 삭제

- em.remove(memo);

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

 

MANAGED : Entity상태 → JPA가 관리중이다.

em.remove(memo); 실행하면

DELETED : Entity상태 변경

et.commit; 하면 완료

 

 

 

2. 쓰기 지연 저장소

 

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

 

actionQueue : 쓰기지연저장소

 

- flush()

COMMIT 할때 작동하는 명령어로, 쓰기 지연 저장소의 SQL을 DB에 요청하는 역할을 수행한다.

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

 

 

3. 변경 감지

JPA는 em.update(entity);를 지원하지 않는다.

@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"); //"Flush" 값 변경
            memo.setContents("변경 감지 확인"); //"Flush() 메서드 호출" 값 변경

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

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

        emf.close();
    }

 

LoadedState 는 Entity의 최초상태를 저장한다.

 

entityInstance(현재상태)와 loadedState(최초상태)를 비교해서 

Update SQL을 생성한 후, 쓰기 지연 저장소에 저장한다.

쓰기 지연 저장소에 저장된 SQL을 요청한다.

이후, COMMIT되어 반영한다.

변경 전 → 변경 후

 

- persistanceTest 자바 클래스 전체코드

더보기
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 PersistentenceTest {

    EntityManagerFactory emf;
    EntityManager em;

    @BeforeEach
    void setUp() {
        //Persistence 이름 넣어서 Factory 만들기
        emf = Persistence.createEntityManagerFactory("memo");
        //Factory에서 Entity매니저 만들기
        em = emf.createEntityManager();
    }

    //Entity 저장
    @Test
    @DisplayName("1차 캐시 : Entity 저장")
    void test1() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = new Memo();
            memo.setId(1L);
            memo.setUsername("Rabat");
            memo.setContents("1차 캐시 Entity 저장");

            em.persist(memo);

            et.commit();

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

        emf.close();
    }


    //조회 예제 1
    @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();
    }

    //조회 예제2
    @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();
    }

    //객체 동일성 보장
    @Test
    @DisplayName("객체 동일성 보장")
    void test4() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {
            Memo memo3 = new Memo();
            memo3.setId(2L);
            memo3.setUsername("rabbit");
            memo3.setContents("객체 동일성 보장");
            em.persist(memo3);

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

            System.out.println(memo1 == memo2);
            System.out.println(memo1 == memo);

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

        emf.close();
    }

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

    //쓰기 지연 저장소
    @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();
    }


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

    //변경 감지
    @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"); //"Flush" 값 변경
            memo.setContents("변경 감지 확인"); //"Flush() 메서드 호출" 값 변경

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

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

        emf.close();
    }


}

4-5 Entity의 상태

 

- src > test > java > EntityStateTest 자바클래스 생성

DB의 memo 테이블 제거 후 실행! 

더보기
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() {
        //Persistence 이름 넣어서 Factory 만들기
        emf = Persistence.createEntityManagerFactory("memo");
        //Factory에서 Entity매니저 만들기
        em = emf.createEntityManager();
    }

    //비영속, 영속
    @Test
    @DisplayName("비영속과 영속 상태")
    void test1() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = new Memo(); // 비영속 상태
            memo.setId(2L);
            memo.setUsername("Rabbit");
            memo.setContents("비영속과 영속 상태");

            em.persist(memo);

            et.commit();

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

        emf.close();
    }

    //준영속

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

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

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

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

        et.begin();

        try {

            Memo memo = new Memo(); //비영속(false)
            memo.setId(3L);
            memo.setUsername("merge()");
            memo.setContents("merge() 저장");

            System.out.println("merge() 호출");
            Memo mergedMemo = em.merge(memo); //반환이 되어서 영속(true)

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

    //merge 수정
    @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();
    }
}

 

밑에 코드로 객체 2개 만들어 주기!

1. 비영속(Transient), 영속(Managed) 상태

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() {
        //Persistence 이름 넣어서 Factory 만들기
        emf = Persistence.createEntityManagerFactory("memo");
        //Factory에서 Entity매니저 만들기
        em = emf.createEntityManager();
    }

    @Test
    @DisplayName("비영속과 영속 상태")
    void test1() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = new Memo(); // 비영속 상태
            memo.setId(1L);
            memo.setUsername("Rabat");
            memo.setContents("비영속과 영속 상태");

            em.persist(memo);

            et.commit();

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

        emf.close();
    }

}

 

 

2. 준영속(Detached)

 

Ⅰ. 영속 상태에서 준영속 상태로 바꾸는 방법

- detach(entity)

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

Update가 이루어지지 않음

 

- clear()

영속성 컨텍스트를 초기화시킴

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

 

nonEnhancedEntityXref(영속성 컨텍스트)에 있던 것이 사라진 것을 볼 수 있음

 

- close()

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

닫혀서 사용할 수 없음

 

 

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

- merge(entity) - insert 저장편

3L이 없는걸 확인하고 넣어줌

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

        et.begin();

        try {

            Memo memo = new Memo(); //비영속(false)
            memo.setId(3L);
            memo.setUsername("merge()");
            memo.setContents("merge() 저장");

            System.out.println("merge() 호출");
            Memo mergedMemo = em.merge(memo); //반환이 되어서 영속(true)

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

 

 

 

- merge(entity) - insert 수정편 

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

detach때문에 영속성 컨텍스트에 entity가 없어서 select실행

 

 

3. 삭제(Removed)

em.remove(memo);

DELETED : Entity상태 변경

 

 

4-6 SPRING BOOT의 JPA

build.gradle

dependencies {
    // JPA 설정
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    // MySQL
    implementation 'mysql:mysql-connector-java:8.0.28'

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

 

application.properties에 추가

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의 옵션

더보기

1. create : 기존 테이블을 전부 삭제한 후 다시 생성 (DROP + CREATE)

2. create-drop : 종료 시점에서 테이블을 삭제함

3. update : 변경된 부분만 반영 (테이블이 없으면 만들고 있으면 만들지 않음)

4. validate : Entity와 테이블이 정상적으로 매핑되었는지 확인

5. non : 아무것도 하지 않음

 

1. Memo Entity 생성

scr > main > java > com.sparta.memo > entity > Memo 자바클래스

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, @GenerateValue와 @Column들 추가
    @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에서는 DB의 트랜잭션을 애플리케이션에 적용할 수 있도록 트랜잭션 관리자를 제공한다.

Shift + Shitf > SimpleJpaRepository > save검색

 

 

- @Transactional 테스트

memo 테이블 먼저 삭제하기

src > test > java > com.sparta.memo > TransactionTest 자바 클래스 생성

package com.sparta.memo;

import com.sparta.memo.entity.Memo;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
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;

@SpringBootTest
public class TransactionTest {

    @PersistenceContext //Entity매니저를 스프링에서 주입받고 싶으면 사용
    EntityManager em;

    @Autowired //test3 레포지토리 추가
    MemoRepository memoRepository;
    
    @Test
    @Transactional
    @Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
    @DisplayName("메모 생성 성공")
    void test1() {
        Memo memo = new Memo();
        memo.setUsername("Rabat");
        memo.setContents("@Transactional 테스트 중!");

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

    //다음 코드는 Transactional이 없어서 실행이 안됌
    @Disabled //실행하지 않게 하기
    @Test
    @DisplayName("메모 생성 실패")
    void test2() {
        Memo memo = new Memo();
        memo.setUsername("Robbie");
        memo.setContents("@Transactional 테스트 중!");

        em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
    }
    
    @Test
    @Transactional
    @Rollback(value = false)
    @DisplayName("트랜잭션 전파 테스트")
    void test3() { //부모 매소드
        memoRepository.createMemo(em); //불려진 createMemo가 자식 매소드
        System.out.println("테스트 test3 메서드 종료");
    }
}

 

- test2가 실행되지 않음

Spring에서는 Transaction이 영속성 컨텍스트임을 확인 가능하다.

 

Spring에서는 Transaction이 영속성 컨텍스트임을 확인 가능

 

 

- test3 (트랜젝션 전파 테스트)

MemoRepository 마지막에 추가

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

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

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

 

 

 

부모 매소드에 트랜잭션이 합류되면서 부모 메서드가 종료된 후 트랜잭션이 커밋 될 때 update가 실행

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

 

 

- 주석 처리 후 실행

자식 매소드가 종료된 후 트랜잭션이 커밋 되면서 update 실행

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

4-7 Spring Data JPA

 

src > java > com.sparta.memo > repository > MemoRepository를 뒤에 코드로 변경

SimpleJpaRepository에 @Repository가 있기 때문에 지워도됌

package com.sparta.memo.repository;

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


public interface MemoRepository extends JpaRepository<Memo, Long> {

}

 

entity Memo사용

 

혹시 Interface와 Class의 차이점이 궁금하다면 링크 참고!

 

클래스(Class) 와 인터페이스(Interface)

Java에서 Class와 Interface는 객체지향 프로그래밍의 핵심 구성 요소두 개념 모두 추상화를 제공하지만 목적과 사용방법에서 차이가 있음정의:클래스는 객체의 설계도 또는 틀로 간주됨. 클래스는

velog.io

 

혹시 제네릭이 무엇인지 모르는 사람은 링크 참고!

 

제네릭 - Java

제네릭이란? 제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다. 말이 어렵다. 아래 그림을 보자. 위의 그림은 아래의 코드를 간략화한 것이다. package or

opentutorials.org

 

SimpleJpaRepository에 저장되므로 MemoService는 다음과 같이 변경

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 jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;

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

        return memoResponseDto;
    }

    public List<MemoResponseDto> getMemos() {
        // DB 조회 - MemoResponseDto에서 Memo를 파라미터로 가지고 있는 생성자를 가져와 하나씩 리스트로 변경
        return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
    }

    @Transactional
    public Long updateMemo(Long id, MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인 - 아무것도 없으면 예외처리
        Memo memo = findMemo(id);

        // memo 내용 수정 - 이미 memo에 update함수가 있음(변경 감지)
        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("선택한 메모는 존재하지 않습니다.")
        );
    }
}

 

 

이곳에 save도 있고

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

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

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

        return memoResponseDto;
    }

 

 

findAll(select)도 있고

    public List<MemoResponseDto> getMemos() {
        // DB 조회 - MemoResponseDto에서 Memo를 파라미터로 가지고 있는 생성자를 가져와 하나씩 리스트로 변경
        return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
    }

 

 

delete도 있고

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

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

        return id;
    }

 

 

이곳에 findById도 있다.

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

 

 

근데 update는 Memo클래스에 있다. 하지만 @Transactional이 없으므로 넣어줘야 한다. (변경감지)

Memo.java에 있음

    @Transactional
    public Long updateMemo(Long id, MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인 - 아무것도 없으면 예외처리
        Memo memo = findMemo(id);

        // memo 내용 수정 - 이미 memo에 update함수가 있음(변경 감지)
        memo.update(requestDto);

        return id;
    }

테스트 완료!

 

4-8 JPA Auditing 적용

잘못 적었을 때, Git에서 롤백

수정 된 시간 보이게 하는법!

JPA Auditing : 시간에 대해서 자동으로 값을 넣어주는 기능

 

1. MemoApplication에 꼭 달아주기

@EnableJpaAuditing

 

 

2. src > main > java > com.sparta.memo > 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 // Timestamped를 상속하는 Entity는 createdAt와 modifiedAt 컬럼을 가진다.
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped { // 추상클래스

    @CreatedDate
    @Column(updatable = false) //최초 생성시간만
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime createdAt;

    @LastModifiedDate //조회한 Entity 객체의 값을 변경할 때 변경된 시간이 자동으로 저장
    @Column
    @Temporal(TemporalType.TIMESTAMP) //날짜(java.util.Date, java.util.Calendar)을 매핑할 때 사용 (DATE, TIME, TIMESTAMP)
    private LocalDateTime modifiedAt;
}

 

 

3. Memo 클래스 변경

public class Memo extends Timestamped{

 

4. MemoResponseDto 클래스 변경

package com.sparta.memo.dto;

import com.sparta.memo.entity.Memo;
import lombok.Getter;

import java.time.LocalDateTime;

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


    public MemoResponseDto(Memo memo) {
        this.id = memo.getId();
        this.username = memo.getUsername();
        this.contents = memo.getContents();
        this.createAt = memo.getCreatedAt();
        this.modifiedAt = memo.getModifiedAt();
    }
}

 

시간 추가 테스트 완료

잠깐 자아가 두개 있었던 것 같습니다.

 

4-9 Query Methods

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

 

MemoRepository

package com.sparta.memo.repository;

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

import java.util.List;


public interface MemoRepository extends JpaRepository<Memo, Long> {

    //ModifiedAt 데이터 기준으로 내림차순으로 정렬된 전체 데이터 찾기
    List<Memo> findAllByOrderByModifiedAtDesc();
    // username을 where문에 넣어서 찾기
//    List<Memo> findAllByUsername(String username);
}

 

MemoService 의 조회 코드 바꾸기

    public List<MemoResponseDto> getMemos() {
        // DB 조회 - MemoResponseDto에서 Memo를 파라미터로 가지고 있는 생성자를 가져와 하나씩 리스트로 변경
        return memoRepository.findAllByOrderByModifiedAtDesc().stream().map(MemoResponseDto::new).toList();
    }

 

실행 전 → 실행 후