Spring Data JPA

๋ฐ•์œคํƒยท2022๋…„ 7์›” 26์ผ
1

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
10/18

๐Ÿค” JPA๋ž€?

JPA(Java Persistence API)๋Š” Java ์ง„์˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ORM ๊ธฐ์ˆ ์˜ ํ‘œ์ค€ ์‚ฌ์–‘์ด๋‹ค. JPA ํ‘œ์ค€ ์‚ฌ์–‘์„ ๊ตฌํ˜„ํ•œ ๊ตฌํ˜„์ฒด๋กœ๋Š” ๋Œ€ํ‘œ์ ์œผ๋กœ Hibernate ORM์ด ์žˆ๋‹ค.


๋ฐ์ดํ„ฐ ์—‘์„ธ์Šค ๊ณ„์ธต์—์„œ์˜ JPA ์œ„์น˜

๋ฐ์ดํ„ฐ ์—‘์„ธ์Šค ๊ณ„์ธต์—์„œ JPA๋Š” ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๊ณ  ์žˆ๊ณ  JPA์˜ ๊ตฌํ˜„์ฒด์ธ Hibernate ORM์„ ํ†ตํ•ด์„œ ๋ฐ์ดํ„ฐ์˜ ์ €์žฅ, ์กฐํšŒ ๋“ฑ์˜ ์ž‘์—…์ด ์ง„ํ–‰๋œ๋‹ค. ์ด๋•Œ Hibernate ORM์€ ๋‚ด๋ถ€์ ์œผ๋กœ JDBC API๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜๊ฒŒ ๋œ๋‹ค.


์˜์†์„ฑ ์ปจํ…์ŠคํŠธ(Persistence Context)

JPA์—์„œ P๋Š” Persistence๋กœ "์˜ค๋ž˜ ์ง€์†๋˜๊ฒŒ ํ•œ๋‹ค"๋ผ๋Š” ๋ชฉ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ORM์€ ๊ฐ์ฒด์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์˜ ๋งคํ•‘์„ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค ๊ฐ์ฒด ์•ˆ์— ํฌํ•จ๋œ ์ •๋ณด๋ฅผ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜๋Š” ๊ธฐ์ˆ ์ด๋‹ค.JPA์—์„œ๋Š” ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด ์ •๋ณด๋ฅผ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ(Persistence Context)์— ๋ณด๊ด€ํ•ด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ ์˜ค๋ž˜ ์ง€์†๋˜๋„๋ก ํ•œ๋‹ค.

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” 1์ฐจ ์บ์‹œ์™€ ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ์†Œ๋ผ๋Š” ์˜์—ญ์ด ์žˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” JPA API๋กœ ํ™•์ธํ•ด๋ณด๋ ค ํ•œ๋‹ค.


์—”ํ‹ฐํ‹ฐ์˜ ์ƒ๋ช… ์ฃผ๊ธฐ

  • ์˜์† : Manged ์ƒํƒœ, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๊ด€๋ฆฌ
  • ๋น„์˜์†: New ์ƒํƒœ
  • ์ค€์˜์† : Detached ์ƒํƒœ, ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๋ถ„๋ฆฌ
  • ์‚ญ์ œ : Removed ์ƒํƒœ

JPA API๋กœ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ์ดํ•ดํ•˜๊ธฐ

  • build.gradle ์„ค์ •
dependencies {
	...
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 
	...
}
  • JPA ์„ค์ •(application.yml)
spring:
  h2:
    console:
      enabled: true
      path: /h2     
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create  
    show-sql: true      

ddl-auto๋ฅผ create๋กœ ์„ค์ •ํ•˜์—ฌ ๊ธฐ์กด ํ…Œ์ด๋ธ” ์‚ญ์ œ ํ›„ ๋‹ค์‹œ ์ƒ์„ฑํ•˜๋„๋ก ์„ค์ •ํ•˜๊ณ  show-sql์„ true๋กœ ์„ค์ •ํ•˜์—ฌ DB์— ์ฟผ๋ฆฌ๋ฌธ์„ ๋‚ ๋ ธ์„ ๋•Œ ๋ณด์ด๋„๋ก ์„ค์ •ํ•œ๋‹ค.

  • ddl-auto ์˜ต์…˜ ์ข…๋ฅ˜

    create : ๊ธฐ์กดํ…Œ์ด๋ธ” ์‚ญ์ œ ํ›„ ๋‹ค์‹œ ์ƒ์„ฑ (DROP + CREATE)
    create-drop : create์™€ ๊ฐ™์œผ๋‚˜ ์ข…๋ฃŒ์‹œ์ ์— ํ…Œ์ด๋ธ” DROP
    update : ๋ณ€๊ฒฝ๋ถ„๋งŒ ๋ฐ˜์˜(์šด์˜DB์—์„œ๋Š” ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋จ)
    validate : ์—”ํ‹ฐํ‹ฐ์™€ ํ…Œ์ด๋ธ”์ด ์ •์ƒ ๋งคํ•‘๋˜์—ˆ๋Š”์ง€๋งŒ ํ™•์ธ
    none : ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ(์‚ฌ์‹ค์ƒ ์—†๋Š” ๊ฐ’์ด์ง€๋งŒ ๊ด€๋ก€์ƒ none์ด๋ผ๊ณ  ํ•œ๋‹ค.)

  • ์ฃผ์˜ํ•  ์ 

    ์šด์˜ ์žฅ๋น„์—์„œ๋Š” ์ ˆ๋Œ€ create, create-drop, update ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋œ๋‹ค.
    ๊ฐœ๋ฐœ ์ดˆ๊ธฐ ๋‹จ๊ณ„๋Š” create ๋˜๋Š” update
    ํ…Œ์ŠคํŠธ ์„œ๋ฒ„๋Š” update ๋˜๋Š” validate
    ์Šคํ…Œ์ด์ง•๊ณผ ์šด์˜ ์„œ๋ฒ„๋Š” validate ๋˜๋Š” none
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;

@Configuration
public class JpaBasicConfig {
    private EntityManager em;
    private EntityTransaction tx;

    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();

        return args -> {
            // ์—ฌ๊ธฐ์„œ ํ…Œ์ŠคํŠธ ์‹คํ–‰
        };
    }
}

JPA์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” EntityManger์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋Š”๋ฐ EntityMangerFactory๋ฅผ DI ๋ฐ›์•„ EntityManger๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.


์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ์ดํ•ด ์ฝ”๋“œ

  • Member Entity ์ƒ์„ฑ
@Getter
@Setter
@NoArgsConstructor
@Entity 
public class Member {
  @Id  
  @GeneratedValue  
  private Long memberId;

  private String email;

  public Member(String email) {
    this.email = email;
  }
}
  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ํ…Œ์ŠคํŠธ - 1์ฐจ ์บ์‹œ, ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ
@Configuration
public class JpaBasicConfig {
    private EntityManager em;
    private EntityTransaction tx;


    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();

        return args -> {
            Member member = new Member("hgd@gmail.com");

            em.persist(member);

            Member resultMember = em.find(Member.class, 1L);
            System.out.println("Id: " + resultMember.getMemberId() + ", email: " +
              resultMember.getEmail());

        };
    }
}


persist() ๋ฉ”์„œ๋“œ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œ์ผฐ์„๋•Œ ๋กœ๊ทธ์—๋Š” insert ์ฟผ๋ฆฌ๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค. ์ด๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— 1์ฐจ ์บ์‹œ์— member ๊ฐ์ฒด๊ฐ€ ์ €์žฅ๋˜๊ณ  ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ์†Œ์— INSERT ์ฟผ๋ฆฌ๋ฌธ์ด ๋“ค์–ด๊ฐ”์ง€๋งŒ ์•„์ง DB์— ์ฟผ๋ฆฌ๋ฌธ์„ ๋‚ ๋ฆฌ๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ํ…Œ์ŠคํŠธ - 1์ฐจ ์บ์‹œ, ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ, DB ์ฟผ๋ฆฌ๋ฌธ ์‹คํ–‰
@Configuration
public class JpaBasicConfig {
    private EntityManager em;
    private EntityTransaction tx;


    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();

        return args -> {
        	tx.begin(); // ์ถ”๊ฐ€
            Member member = new Member("hgd@gmail.com");

            em.persist(member); // insert ์ฟผ๋ฆฌ๋ฌธ ์‹คํ–‰

            Member resultMember = em.find(Member.class, 1L); // 1์ฐจ ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ด
            System.out.println("Id: " + resultMember.getMemberId() + ", email: " +
              resultMember.getEmail());
            tx.commit(); // ์ถ”๊ฐ€

        };
    }
}


์•ž์˜ ์ฝ”๋“œ์™€ ๋น„๊ตํ•œ๋‹ค๋ฉด tx.begin(), tx.commit()์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ  ์ปค๋ฐ‹ํ•˜๋Š” ์‹œ์ ์— ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ SQL ์“ฐ๊ธฐ ์ง€์—ฐ ์ €์žฅ์†Œ์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ฟผ๋ฆฌ๋ฌธ์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ ๋‚˜์„œ SQL ์“ฐ๊ธฐ ์ง€์—ฐ ์ €์žฅ์†Œ๋Š” ๋น„์›Œ์ง€๊ฒŒ ๋œ๋‹ค. commit() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด DB์— ๋ฐ˜์˜์ด ๋˜์—ˆ์ง€๋งŒ ์‚ฌ์‹ค ๋‚ด๋ถ€์ ์œผ๋กœ flush() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์–ด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด DB์— ๋ฐ˜์˜๋˜๊ฒŒ ๋œ๋‹ค. ํŠธ๋žœ์žญ์…˜์ด ์‹คํ–‰๋˜๋Š” ์ค‘๊ฐ„์— em.find() ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด DB์—์„œ ์กฐํšŒํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ ๊ฐ™์ง€๋งŒ 1์ฐจ ์บ์‹œ์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๊ฒŒ ๋œ๋‹ค.



  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ํ…Œ์ŠคํŠธ - 1์ฐจ ์บ์‹œ, ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ, ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ์„๋•Œ ํ™•์ธ
@Configuration
public class JpaBasicConfig {
    private EntityManager em;
    private EntityTransaction tx;


    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();

        return args -> {
            tx.begin();
            Member member = new Member("hgd@gmail.com");

            em.persist(member);
            member.setEmail("iss@gmail.com"); // ์ด๋ฉ”์ผ ๋ณ€๊ฒฝ
            Member resultMember = em.find(Member.class, 1L);
            System.out.println("Id: " + resultMember.getMemberId() + ", email: " +
              resultMember.getEmail());
            tx.commit();
        };
    }
}


persist()๋ฉ”์„œ๋“œ๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•˜๊ณ  member์— ์ด๋ฉ”์ผ์„ ๋ณ€๊ฒฝํ•œ๋‹ค๋ฉด 1์ฐจ ์บ์‹œ์— ๋“ค์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ์™€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— insert ์ฟผ๋ฆฌ๋ฌธ์ด ์‹คํ–‰๋˜๊ณ  ๋‚˜์„œ update ์ฟผ๋ฆฌ๋ฌธ์ด ์‹คํ–‰๋œ๋‹ค.


๐ŸŽ‹ ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘

์—”ํ‹ฐํ‹ฐ ์„ค์ •

๊ธฐ๋ณธ์ ์œผ๋กœ @Entity, @Id ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๊ณ  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

@Entity
public class Member {
	@Id
    private long memberId;
    
    private String email;
}

@Entity, @Id ์–ด๋…ธํ…Œ์ด์…˜์€ ํ•„์ˆ˜์ด๋‹ค. ์ถ”๊ฐ€์ ์ธ JPA ์–ด๋…ธํ…Œ์ด์…˜ ๋ฆฌ์ŠคํŠธ๋Š” ํ•„์š”ํ• ๋•Œ ์ฐพ์•„์„œ ์‚ฌ์šฉํ•˜์ž.


๊ธฐ๋ณธํ‚ค ๋งคํ•‘

@Id ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ธ ํ•„๋“œ๊ฐ€ ํ…Œ์ด๋ธ”์˜ ๊ธฐ๋ณธํ‚ค๊ฐ€ ๋˜๋Š”๋ฐ ๊ธฐ๋ณธํ‚ค์— ์ƒ์„ฑ์ „๋žต ๋“ค์ด ์žˆ๋‹ค.

  • ๊ธฐ๋ณธํ‚ค ์ง์ ‘ ํ• ๋‹น
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ์ƒ์—์„œ ๊ธฐ๋ณธํ‚ค ์ง์ ‘ ํ• ๋‹น(@Id ์–ด๋…ธํ…Œ์ด์…˜๋งŒ ์‚ฌ์šฉ ํ›„ DB์— ๋„ฃ์„ ๋•Œ ์ง์ ‘ ๋„ฃ๊ธฐ)
  • ๊ธฐ๋ณธํ‚ค ์ž๋™ ์ƒ์„ฑ(@GeneratedValue ์–ด๋…ธํ…Œ์ด์…˜ ์ด์šฉ)
    • IDENTITY ์ „๋žต
    • SEQUENCE ์ „๋žต : @SequenceGenerator ํ•„์š”
    • AUTO ์ „๋žต
    • TABLE ์ „๋žต : @TableGenerator ํ•„์š”


์ถœ์ฒ˜ : ๊ธฐ๋ณธ ํ‚ค ์ƒ์„ฑ ์ „๋žต


IDENTITY VS SEQUENCE

  • IDENTITY ์ „๋žต ์‚ฌ์šฉ

    member ํ…Œ์ด๋ธ”์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ฉด์„œ ์ž๋™์œผ๋กœ ๊ธฐ๋ณธํ‚ค ๊ฐ’์ด ์„ค์ •๋œ๋‹ค.


  • SEQUENCE ์ „๋žต ์‚ฌ์šฉ

DB์— SEQUENCE๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์ „์— SEQUENCE๋ฅผ ์กฐํšŒํ•ด ๊ธฐ๋ณธํ‚ค ๊ฐ’์„ ๊ฐ€์ ธ์™€ ์ €์žฅํ•œ๋‹ค.


์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘


๋ณดํ†ต ์™ธ๋ž˜ํ‚ค๊ฐ€ ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ์—์„œ @ManyToOne ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ํ•„์š”ํ•˜๋‹ค๋ฉด ์–‘๋ฐฉํ–ฅ์œผ๋กœ ๊ตฌํ˜„์„ ํ•œ๋‹ค.
์™ธ๋ž˜ํ‚ค๊ฐ€ ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ์— @JoinColumn ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•˜์—ฌ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งคํ•‘ํ•œ๋‹ค. ๋งŒ์•ฝ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์— @OneToMany ์–ด๋…ธํ…Œ์ด์…˜์— mappedBy๋ฅผ ์ด์šฉํ•˜์—ฌ ์™ธ๋ž˜ํ‚ค๊ฐ€ ์žˆ๋Š” ํ…Œ์ด๋ธ”์— ์„ ์–ธํ•œ ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ์˜ ๋ณ€์ˆ˜๋ช…์„ ์ ์–ด์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„์ผ ๊ฒฝ์šฐ 1:N, N:1๋กœ ๋ถ„ํ• ํ•˜์—ฌ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งคํ•‘ํ•œ๋‹ค.


1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2022๋…„ 7์›” 27์ผ

์ข‹์€ ๋‚ด์šฉ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ