๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐฑ์—”๋“œ/spring&springboot

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ž…๋ฌธ์ฃผ์ฐจ] Spring Data JPA

by alswlfl 2026. 6. 26.

Spring Boot ํ™˜๊ฒฝ์—์„œ์˜ JPA

Spring Boot ํ™˜๊ฒฝ์—์„œ๋Š” EntityManagerFactory๋ž‘ EntityManager๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์คŒ

  • application.properties์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฃผ๋ฉด, ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ž๋™์œผ๋กœ EntityManagerFactory ๋งŒ๋“ค์–ด์คŒ
  • Spring Boot์—์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š” EntityManager๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์˜ค๊ณ  ์‹ถ์„ ๋•Œ๋Š” @PersistenceContext ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ

๋ฉ”๋ชจ์žฅ ํ”„๋กœ์ ํŠธ์— JPA ์ ์šฉํ•˜๊ธฐ

1. build.gradle ํŒŒ์ผ ๋‚ด JPA ์„ค์ • ์ถ”๊ฐ€

// JPA ์„ค์ •
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  • spring-book-starter-data-jpa ์„ค์ •ํ•˜๋ฉด ์ž๋™์œผ๋กœ Hibernate Core๋ฅผ ๋‹น๊ฒจ์˜ด
dependencies {
    // JPA ์„ค์ •
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    // MySQL
    implementation 'mysql:mysql-connector-java:8.0.28'
    // jdbc ์‚ญ์ œ

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

 

2. application.properties ๋‚ด Hibernate ์„ค์ • ์ถ”๊ฐ€

# ddl-auto ์˜ต์…˜
# 1. create: ๊ธฐ์กด ํ…Œ์ด๋ธ” ์‚ญ์ œ ํ›„ ๋‹ค์‹œ ์ƒ์„ฑ(drop+create)
# 2. create-drop: create์™€ ๋น„์Šทํ•˜์ง€๋งŒ, ์ข…๋ฃŒ ์‹œ์ ์— table drop
# 3. update: ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ๋ฐ˜์˜(ํ…Œ์ด๋ธ”์ด ์žˆ์œผ๋ฉด ์ƒ์„ฑ X, ์—†์œผ๋ฉด ์ƒ์„ฑ)
# 4. validate: ์—”ํ‹ฐํ‹ฐ์™€ ํ…Œ์ด๋ธ”์ด ์ •์ƒ์ ์œผ๋กœ ๋งคํ•‘๋˜์–ด์žˆ๋Š”์ง€๋งŒ ํ™•์ธ
# 5. none: ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ
spring.jpa.hibernate.ddl-auto=update

# show_sql, format_sql, use_sql_comments๋Š”
# Hibernate๊ฐ€ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” SQL์„ ๋ณด๊ธฐ ์ข‹๊ฒŒ ๋งŒ๋“ค์–ด์คŒ
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

 

3. Memo Entity ๋งŒ๋“ค๊ธฐ

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 ํŠธ๋žœ์žญ์…˜

  • Java ํ™˜๊ฒฝ์—์„œ JPA๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” EntityManager์—์„œ Entity ํŠธ๋žœ์žญ์…˜์„ ๊ฐ€์ ธ์™€์„œ ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์—ˆ์Œ
  • Spring Boot ํ™˜๊ฒฝ์—์„œ๋Š” ์†์‰ฝ๊ฒŒ ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ์ œ๊ณตํ•จ → @Transactional ์–ด๋…ธํ…Œ์ด์…˜ ์ด์šฉ
  • ํด๋ž˜์Šค ์ƒ๋‹จ์— @Transactional์„ ๊ฑธ๋˜๊ฐ€, ์•„๋‹ˆ๋ฉด method ์ž์ฒด์— ๊ฑธ์–ด์„œ ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ์ด ์„ค์ •๋จ
  • method ์ž์ฒด์— ํŠธ๋žœ์žญ์…˜์„ ๊ฑธ๋ฉด, ํ•ด๋‹น method ๋‚ด์—์„œ ์ˆ˜ํ–‰๋˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ์‚ฐ ๋‚ด์šฉ๋“ค์„ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์Œ
  • ์ด๋•Œ, ํ•ด๋‹น method์—์„œ ์ฝ”๋“œ๊ฐ€ ์ „๋ถ€ ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰์ด ๋˜๋ฉด ๋งˆ์ง€๋ง‰์— ๋๋‚  ๋•Œ Commitํ•˜๊ณ , ์•„๋‹ˆ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•จ
  • ์™ผ์ชฝ ์ด๋ฏธ์ง€์—์„œ @Transactional(readOnly=true)๋Š” ํด๋ž˜์Šค ๋‚ด๋ถ€์— ์žˆ๋Š” ๋ชจ๋“  method์— ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์˜ readOnly์ด ๋ถ€์—ฌ๋จ
    • readOnly ์˜ต์…˜: ํŠธ๋žœ์žญ์…˜์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ๋งŒ ํ•  ๋•Œ ์‚ฌ์šฉ๋จ
    • ์ฝ๊ธฐ ์ž‘์—…์— ๋Œ€ํ•œ ์ตœ์ ํ™” ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ
    • ๋งŒ์•ฝ readOnly = true ๊ฑธ์–ด๋†“์€ ์ƒํƒœ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•จ
  • save ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ @Transactional์ด ๋‹ค์‹œ ๊ฑธ๋ ค์žˆ๋Š” ๊ฒฝ์šฐ, readOnly = false ์˜ต์…˜์œผ๋กœ ์ ์šฉ๋จ

@Transactional ์–ด๋…ธํ…Œ์ด์…˜ ์ ์šฉ

ํŠธ๋žœ์žญ์…˜ ํ…Œ์ŠคํŠธ

1) ๋ฉ”๋ชจ ์ƒ์„ฑ ์„ฑ๊ณต ํ…Œ์ŠคํŠธ

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

    @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 ๊ฐ์ฒด ์ €์žฅ
    }
}

ํŠธ๋žœ์žญ์…˜ ์‹คํ–‰ ๊ฒฐ๊ณผ
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด ๋ฐ˜์˜

2) ๋ฉ”๋ชจ ์ƒ์„ฑ ์‹คํŒจ ํ…Œ์ŠคํŠธ

@Test
@DisplayName("๋ฉ”๋ชจ ์ƒ์„ฑ ์‹คํŒจ")
void test2() {
    Memo memo = new Memo();
    memo.setUsername("Robbie");
    memo.setContents("@Transactional ํ…Œ์ŠคํŠธ ์ค‘!");

    em.persist(memo);  // ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋ฉ”๋ชจ Entity ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
}

ํŠธ๋žœ์žญ์…˜ ์‹คํŒจ ๊ฒฐ๊ณผ

  • Transaction์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— EntityManager ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ ๋ฐœ์ƒ → Transaction์ด ์ ์šฉ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋Š” ์—๋Ÿฌ
  • Insert, Update, Delete์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ๋Š” ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•จ!
  • @Disabled ์ถ”๊ฐ€ํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋Š” ํ…Œ์ŠคํŠธ๋ผ๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ์•„์˜ˆ ์ˆ˜ํ–‰์ด ๋˜์ง€ ์•Š์Œ
@Test
@Disabled // ๋” ์ด์ƒ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์•ˆํ•˜๊ฒ ๋‹ค๋Š” ๋œป
@DisplayName("๋ฉ”๋ชจ ์ƒ์„ฑ ์‹คํŒจ")
void test2() {
...

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ํŠธ๋žœ์žญ์…˜์˜ ์ƒ๋ช…์ฃผ๊ธฐ

Spring ๋‚ด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ํŠธ๋žœ์žญ์…˜์˜ ์ƒ๋ช…์ฃผ๊ธฐ

Spring ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ํŠธ๋žœ์žญ์…˜์˜ ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ์ผ์น˜ํ•จ

  • ํŠธ๋žœ์žญ์…˜์ด ์œ ์ง€๋˜๋Š” ๋™์•ˆ์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋„ ๊ณ„์† ์œ ์ง€๋˜๊ธฐ ๋•Œ๋ฌธ์— ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • Spring์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— Service๋ถ€ํ„ฐ Repository๊นŒ์ง€ Transaction์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ

ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ

// MemoRepository ๋‚ด์— ์ฝ”๋“œ ์ถ”๊ฐ€
@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;
}
package com.sparta.memo;

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

@SpringBootTest
public class TransactionTest {

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemoRepository memoRepository;

    @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
    @Disabled
    @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);
        System.out.println("ํ…Œ์ŠคํŠธ test3 ๋ฉ”์„œ๋“œ ์ข…๋ฃŒ");
    }
}
  • test3() ๋ฉ”์„œ๋“œ๊ฐ€ ๋ถ€๋ชจ ๋ฉ”์„œ๋“œ์ด๊ณ , createMemo()๊ฐ€ ์ž์‹ ๋ฉ”์„œ๋“œ์ž„

๊ฒฐ๊ณผ

  • ์ฆ‰, ์ž์‹ ๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜์ด ๋ถ€๋ชจ ๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜์— ํ•ฉ์ณ์ง
  • @Transactional์—๋Š” propagation์ด๋ผ๋Š” ์˜ต์…˜์ด ์กด์žฌํ•จ

@Transactional์˜ propagation ์˜ต์…˜

REQUIRED: ๋ถ€๋ชจ ๋ฉ”์„œ๋“œ์— Transactional์ด ์กด์žฌํ•œ๋‹ค๋ฉด, ์ž์‹ ๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜์€ ๋ถ€๋ชจ ๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜์— ํ•ฉ๋ฅ˜ํ•˜๊ฒŒ ๋จ(default ์˜ต์…˜)

@Transactional(propagation = Propagation.REQUIRED)

 

๋งŒ์•ฝ, ๋ถ€๋ชจ ๋ฉ”์„œ๋“œ์— Transactional์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์ž์‹ ๋ฉ”์„œ๋“œ ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ํ›„ ๋๋‚จ

@Test
// @Transactional()
// @Rollback(value = false)
@DisplayName("ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ ํ…Œ์ŠคํŠธ")
void test3() {
    memoRepository.createMemo(em);
    System.out.println("ํ…Œ์ŠคํŠธ test3 ๋ฉ”์„œ๋“œ ์ข…๋ฃŒ");
}

๋ถ€๋ชจ ๋ฉ”์„œ๋“œ์— Transactional์ด ์—†๋Š” ๊ฒฝ์šฐ


Spring Data JPA

Spring Data JPA๋Š” JPA๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด๋†“์€ ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ

Spring Data JPA

  • JPA๋ฅผ ์ถ”์ƒํ™”์‹œํ‚จ Repository ์ธํ„ฐํŽ˜์ด์Šค ์ œ๊ณตํ•จ
  • Repository ์ธํ„ฐํŽ˜์ด์Šค๋Š” Hibernate์™€ ๊ฐ™์€ JPA ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ๋จ
    • ๊ฐœ๋ฐœ์ž๋“ค์€ Repository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด JPA๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋จ

Spring Data JPA์˜ SimpleJpaRepository

  • Spring Data JPA์—์„œ๋Š” JpaRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์คŒ
    • Spring ์„œ๋ฒ„๊ฐ€ ๋œฐ ๋•Œ JpaRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์€ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ž๋™์œผ๋กœ ์Šค์บ”์ด ๋˜๋ฉด, ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ์ž๋™์œผ๋กœ SimpleJpaRepository ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ , ์ด ํด๋ž˜์Šค๋ฅผ Spring Bean์œผ๋กœ ๋“ฑ๋กํ•จ
  • ๋”ฐ๋ผ์„œ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ JpaRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด JPA์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
public interface MemoRepository extends JpaRepository<Memo, Long> {

}
  • JpaRepository<"@Entity ํด๋ž˜์Šค", "@Id์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…">๋ฅผ ์ƒ์†๋ฐ›๋Š” interface๋กœ ์„ ์–ธํ•จ
    • Spring Data JPA์— ์˜ํ•ด ์ž๋™์œผ๋กœ Bean ๋“ฑ๋ก ๋จ
    • ์ œ๋„ค๋ฆญ์Šค์˜ @Entity ํด๋ž˜์Šค ์œ„์น˜์— Memo Entity๋ฅผ ์ถ”๊ฐ€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น MemoRepository๋Š” DB์˜ memo ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐ๋˜์–ด CRUD ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋จ

๋ฉ”๋ชจ์žฅ ํ”„๋กœ์ ํŠธ์— Spring Data JPA ์ ์šฉํ•˜๊ธฐ

package com.sparta.memo.repository;

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

// SimpleJpaRepository์— @Repository ์–ด๋…ธํ…Œ์ด์…˜์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ
// interface์— ๋”ฐ๋กœ @Repository ๋‹ฌ์•„์ฃผ์ง€ ์•Š์•„๋„ ๋จ!
// @Repository
public interface MemoRepository extends JpaRepository<Memo, Long> {

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

    // MemoService๊ฐ€ ์ƒ์„ฑ์ด ๋  ๋•Œ, ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ JdbcTemplate ๋ฐ›์•„์˜ค๊ณ 
    // ๋”ฑ ํ•œ ๋ฒˆ๋งŒ 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 ์กฐํšŒ
        return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
    }

    @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) {
        // ํ•ด๋‹น ๋ฉ”๋ชจ๊ฐ€ DB์— ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
        return memoRepository.findById(id).orElseThrow(() ->
                new IllegalArgumentException("์„ ํƒํ•œ ๋ฉ”๋ชจ๋Š” ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
        );
    }
}

JPA Auditing ์ ์šฉํ•˜๊ธฐ

  • ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋  ๋•Œ๋งˆ๋‹ค ์ €์žฅ๋œ ์‹œ๊ฐ„ ํ˜น์€ ์ˆ˜์ •๋œ ์‹œ๊ฐ„์„ ์ €์žฅํ•˜๊ณ  ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ, ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ์‹œ๊ฐ„๋„ ๊ฑธ๋ฆฌ๊ณ  ๋กœ์ง๋„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ
  • ๋˜ํ•œ, ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์— ์ƒ์„ฑ ์‹œ๊ฐ„๊ณผ ์ˆ˜์ • ์‹œ๊ฐ„์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ, ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ ์ง์ ‘ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•จ

Spring Data JPA๋Š” ์‹œ๊ฐ„์— ๋Œ€ํ•ด์„œ ์ž๋™์œผ๋กœ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ธ JPA Auditing ์ œ๊ณตํ•˜๊ณ  ์žˆ์Œ

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 ์–ด๋…ธํ…Œ์ด์…˜
// JPA Entity Class๋“ค์ด ํ•ด๋‹น ์ถœ์ƒ Class๋ฅผ ์ƒ์†ํ•  ๊ฒฝ์šฐ,
// ์ถ”์ƒ ํด๋ž˜์Šค(abstract class)์—์„œ ์„ ์–ธํ•œ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜(์ฆ‰, ์—ฌ๊ธฐ์„œ๋Š” createdAt, modifiedAt)๋ฅผ ์ปฌ๋Ÿผ์œผ๋กœ ์ธ์‹ํ•ด์คŒ
@MappedSuperclass
// @EntityListeners(AuditingEntityListener.class) ์–ด๋…ธํ…Œ์ด์…˜
// ํ•ด๋‹น ํด๋ž˜์Šค ์ฆ‰ Timestamped ํด๋ž˜์Šค์˜ Auditing ๊ธฐ๋Šฅ ํฌํ•จ์‹œ์ผœ์คŒ
// ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ์•„์ค˜์•ผ์ง€ ์ž๋™์œผ๋กœ ์‹œ๊ฐ„์„ ๋„ฃ์–ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์ˆ˜ํ–‰๋จ
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {

    @CreatedDate // Entity ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์ €์žฅ๋  ๋•Œ ์‹œ๊ฐ„ ๊ฐ’์ด ์ž๋™์œผ๋กœ ์ €์žฅ๋จ
    @Column(updatable = false) // ์ตœ์ดˆ ์‹œ๊ฐ„๋งŒ ์ €์žฅ๋˜๊ณ , ์ˆ˜์ • ์‹œ๊ฐ„์€ ์ €์žฅ๋˜๊ฒŒ ํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด updatable = false ์˜ต์…˜ ์ถ”๊ฐ€
    @Temporal(TemporalType.TIMESTAMP) // Java์˜ Date Type ํ˜น์€ Calender ๊ฐ™์€ ๋‚ ์งœ ๋ฐ์ดํ„ฐ๋ฅผ ๋งคํ•‘ํ•  ๋•Œ ์‚ฌ์šฉ
    // DATE: 20xx-01-01
    // TIME: 20:21:13
    // TIMESTAMP: 20xx-01-01 20:21:13.33333
    private LocalDateTime createdAt;

    @LastModifiedDate // ์กฐํšŒํ•œ Entity ๊ฐ์ฒด ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ, ๋ณ€๊ฒฝ๋œ ์‹œ๊ฐ„์ด ์ž๋™์œผ๋กœ ์ €์žฅ๋จ
    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime modifiedAt;
}
@EnableJpaAuditing // JPA Auditing ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•Œ๋ ค์ค˜์•ผํ•จ
@SpringBootApplication
public class MemoApplication {

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

}
@Entity // JPA๊ฐ€ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” Entity ํด๋ž˜์Šค ์ง€์ •
@Getter
@Setter
@Table(name = "memo") // ๋งคํ•‘ํ•  ํ…Œ์ด๋ธ”์˜ ์ด๋ฆ„์„ ์ง€์ •
@NoArgsConstructor
public class Memo extends Timestamped{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ...
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 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();
    }
}

JPA Auditing ์ถ”๊ฐ€


Query Methods๋ž€?

Spring Data JPA์—์„œ๋Š” method ์ด๋ฆ„์œผ๋กœ SQL์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” Query Methods ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Œ

public interface MemoRepository extends JpaRepository<Memo, Long> {
    // ModifiedAt ํ•„๋“œ ๋ฐ์ดํ„ฐ ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ด์„œ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ณด๋ƒ„
    List<Memo> findAllByOrderByModifiedAtDesc();
}
  • ์œ„์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์ด๋ฏธ ์ •์˜๋˜์–ด ์žˆ๋Š” ๊ทœ์น™์— ๋งž๊ฒŒ๋” method๋ฅผ ์„ ์–ธํ•˜๋ฉด, ํ•ด๋‹น method ์ด๋ฆ„์„ ๋ถ„์„ํ•œ ํ›„ SimpleJpaRepository์—์„œ ๊ตฌํ˜„๋จ

๊ฒฐ๊ณผ

public interface MemoRepository extends JpaRepository<Memo, Long> {
    ...

    // ํŠน์ • username์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ์˜ Memo ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ด
    List<Memo> findAllByUsername(String username);
}