JPA Web Application

์˜ํ•œ๋‹˜์˜ ์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ1 - ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ๊ฐ•์˜๋ฅผ ์š”์•ฝํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Project

Spring Boot Project

Spring Boot Starter

  • web, thymeleaf, jpa, h2, lombok, validation..

  • set Lombok

    • Prefrences - plugin - lombok

    • Prefrences - Annotation Processors - Enable annotation processing

  • set Build Tools Gradle

    • Preferences - Build, Execution, Deployment - Build Tools - Gradle

      • Build and run using: Gradle IntelliJ IDEA

      • Run tests using: Gradle IntelliJ IDEA

Thymeleaf

H2 Database

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ ์ƒ์„ฑ

    • jdbc:h2:~/databaseName (jsessionid ํฌํ•จ - ํŒŒ์ผ ๋ชจ๋“œ)

    • ~/databaseName.mv.db ํŒŒ์ผ ์ƒ์„ฑ ํ™•์ธ

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘์†

    • jdbc:h2:tcp://localhost/~/databaseName (๋„คํŠธ์›Œํฌ ๋ชจ๋“œ)

JPA & DB ์„ค์ •

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/jpashop
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ์ ์— ํ…Œ์ด๋ธ”์„ drop ํ•˜๊ณ , ๋‹ค์‹œ ์ƒ์„ฑ
      ddl-auto: create
  properties:
    hibernate:
      # System.out ์— ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์‹คํ–‰ SQL์„ ๋‚จ๊ธด๋‹ค.
      show_sql: true
      format_sql: true

logging:
  level:
    # Logger๋ฅผ ํ†ตํ•ด ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์‹คํ–‰ SQL์„ ๋‚จ๊ธด๋‹ค.
    org.hibernate.SQL: debug
    # ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋กœ๊ทธ
    org.hibernate.type: trace

@Transactional

  • @Transactional์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ์ ์šฉ๋  ๊ฒฝ์šฐ, ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„ ๋ฐ”๋กœ ๋กค๋ฐฑ ์‹คํ–‰

  • ๋กค๋ฐฑ์„ ์›ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ @Rollback(false) ์‚ฌ์šฉ

    @Test
    @Transactional
    @Rollback(false)
    public void testMember() {
        // given
        Member member = new Member();
        member.setUsername("memberA");
    
        // when
        Long savedId = memberRepository.save(member);
        Member findMember = memberRepository.find(savedId);
    
        // then
        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(findMember).isEqualTo(member); // JPA ์—”ํ‹ฐํ‹ฐ ๋™์ผ์„ฑ ๋ณด์žฅ
    }

Build

./gradlew clean build

cd build/libs/

java -jar XXX.jar

Query Parameter Log

spring-boot-data-source-decorator Public

implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.6'
  • ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์‹œ์Šคํ…œ ์ž์›์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์šด์˜ ์ ์šฉ ์‹œ ์„ฑ๋Šฅํ…Œ์ŠคํŠธ ํ•„์š”

๋„๋ฉ”์ธ ๋ถ„์„ ์„ค๊ณ„

๋„๋ฉ”์ธ ๋ชจ๋ธ๊ณผ ํ…Œ์ด๋ธ” ์„ค๊ณ„

  • ํšŒ์›์ด ์ฃผ๋ฌธ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํšŒ์›์ด ์ฃผ๋ฌธ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒƒ์ด ์ž˜ ์„ค๊ณ„ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ๊ฐ์ฒด ์„ธ์ƒ์€ ์‹ค์ œ ์„ธ๊ณ„์™€๋Š” ๋‹ค๋ฅด๋‹ค

    • ํšŒ์›์ด ์ฃผ๋ฌธ์„ ์ฐธ์กฐํ•˜์ง€ ์•Š๊ณ , ์ฃผ๋ฌธ์ด ํšŒ์›์„ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค.

  • ์™ธ๋ž˜ํ‚ค๊ฐ€ ์žˆ๋Š” ๊ณณ์„ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์œผ๋กœ ์ •ํ•˜์ž.

์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค ๊ฐœ๋ฐœ

  • ์ด๋ก ์ ์œผ๋กœ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค ์„ค๊ณ„ ์‹œ Getter/Setter๋ฅผ ๋ชจ๋‘ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ , ๊ผญ ํ•„์š”ํ•  ๊ฒฝ์šฐ ๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ด์ƒ์ ์ด๋‹ค.

    • ์‹ค๋ฌด์—์„œ๋Š” ์—”ํ‹ฐํ‹ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ์ผ์ด ๋งŽ์œผ๋ฏ€๋กœ, Getter ์ •๋„๋Š” ์—ด์–ด๋‘๋Š” ๊ฒƒ์ด ํŽธ๋ฆฌ

    • ๋‹จ, ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ๋Š” Setter ๋Œ€์‹  ๋ณ€๊ฒฝ ์ง€์ ์ด ๋ช…ํ™•ํ•˜๋„๋ก ๋ณ„๋„ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜์ž.

  • ํ…Œ์ด๋ธ” ID๋Š” ๊ด€๋ก€์ƒ ํ…Œ์ด๋ธ”๋ช… + id๋ฅผ ๋งŽ์ด ์‚ฌ์šฉ

  • ์‹ค๋ฌด์—์„œ๋Š” @ManyToMany ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง์ž

    • ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์— ์ปฌ๋Ÿผ ์ถ”๊ฐ€๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ์ฟผ๋ฆฌ๋ฅผ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์‹คํ–‰ํ•˜๊ธฐ ์–ด๋ ค์šฐ๋ฏ€๋กœ ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ์—๋Š” ํ•œ๊ณ„ ์กด์žฌ

    • ๋Œ€์‹ , ์ค‘๊ฐ„ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งŒ๋“ค๊ณ  ๋Œ€๋‹ค๋Œ€ ๋งคํ•‘์„ ์ผ๋Œ€๋‹ค, ๋‹ค๋Œ€์ผ ๋งคํ•‘์œผ๋กœ ํ’€์–ด๋‚ด์„œ ์‚ฌ์šฉํ•˜์ž

  • ๊ฐ’ ํƒ€์ž…์€ ์ƒ์„ฑ์ž์—์„œ ๊ฐ’์„ ๋ชจ๋‘ ์ดˆ๊ธฐํ™”ํ•ด์„œ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅํ•œ ํด๋ž˜์Šค๋กœ ์„ค๊ณ„ํ•˜์ž.

    • JPA ์ŠคํŽ™์ƒ ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์€ ์ž๋ฐ” ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ public ๋˜๋Š” (๊ฐ€๊ธ‰์ ) protected ๋กœ ์„ค์ •ํ•ด์ฃผ์ž. @NoArgsConstructor(access = AccessLevel.PROTECTED)

    • JPA ๊ตฌํ˜„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๋ฆฌํ”Œ๋ž™์…˜, ํ”„๋ก์‹œ ๊ฐ™์€ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ

์—”ํ‹ฐํ‹ฐ ์„ค๊ณ„ ์ฃผ์˜์‚ฌํ•ญ

์—”ํ‹ฐํ‹ฐ์—๋Š” ๊ฐ€๊ธ‰์  Setter๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ

  • Setter๊ฐ€ ๋ชจ๋‘ ์—ด๋ ค์žˆ๋‹ค๋ฉด, ๋ณ€๊ฒฝ ํฌ์ธํŠธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์„œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค.

๋ชจ๋“  ์—ฐ๊ด€๊ด€๊ณ„๋Š” ์ง€์—ฐ๋กœ๋”ฉ(LAZY)์œผ๋กœ ์„ค์ •ํ•˜๊ธฐ

  • ์ฆ‰์‹œ๋กœ๋”ฉ(EAGER)์€ ์˜ˆ์ธก์ด ์–ด๋ ต๊ณ , ์–ด๋–ค SQL์ด ์‹คํ–‰๋ ์ง€ ์ถ”์ ์ด ์–ด๋ ค์›€

  • ํŠนํžˆ๋‚˜ JPQL์„ ์‹คํ–‰ํ•  ๋•Œ N+1 ๋ฌธ์ œ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒ

  • ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ DB์—์„œ ์กฐํšŒํ•ด์•ผ ํ•œ๋‹ค๋ฉด, fetch join ๋˜๋Š” ์—”ํ‹ฐํ‹ฐ ๊ทธ๋ž˜ํ”„ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ž.

  • @XToOne(OneToOne, ManyToOne) ๊ด€๊ณ„๋Š” ๊ธฐ๋ณธ์ด ์ฆ‰์‹œ๋กœ๋”ฉ(EAGER)์ด๋ฏ€๋กœ ์ง์ ‘ ์ง€์—ฐ๋กœ๋”ฉ(LAZY)์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

์ปฌ๋ ‰์…˜์€ ํ•„๋“œ์—์„œ ์ดˆ๊ธฐํ™” ํ•˜๊ธฐ

  • ์ปฌ๋ ‰์…˜์€ ํ•„๋“œ์—์„œ ๋ฐ”๋กœ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ๊ฒƒ์ด null ๋ฌธ์ œ์—์„œ ์•ˆ์ „

  • ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์†ํ™” ํ•  ๋•Œ, ์ปฌ๋ž™์…˜์„ ๊ฐ์‹ธ์„œ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋‚ด์žฅ ์ปฌ๋ ‰์…˜์œผ๋กœ ๋ณ€๊ฒฝ org.hibernate.collection.internal.PersistentBag

  • ๋งŒ์•ฝ getOrders() ์ฒ˜๋Ÿผ ์ž„์˜์˜ ๋ฉ”์„œ๋“œ์—์„œ ์ปฌ๋ ฅ์…˜์„ ์ž˜๋ชป ์ƒ์„ฑํ•˜๋ฉด ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ๋‚ด๋ถ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•„๋“œ๋ ˆ๋ฒจ์—์„œ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์•ˆ์ „ํ•˜๊ณ , ์ฝ”๋“œ๋„ ๊ฐ„๊ฒฐํ•˜๋‹ค.

private List<OrderItem> orderItems = new ArrayList<>();

์˜์†์„ฑ ์ „์ด

  • ํŠน์ • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์† ์ƒํƒœ๋กœ ๋งŒ๋“ค ๋•Œ, ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋„ ํ•จ๊ป˜ ์˜์† ์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์‚ฌ์šฉ

  • ๋‹จ, ์—”ํ‹ฐํ‹ฐ์˜ ์†Œ์œ ์ž๊ฐ€ ํ•˜๋‚˜์ผ ๋•Œ๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)

์—ฐ๊ด€๊ด€๊ณ„ ํŽธ์˜ ๋ฉ”์„œ๋“œ

  • ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„ ์‹œ ๊ฐ์ฒด๊ฐ„ ๊ฐ’ ์„ธํŒ…์— ํ•„์š”

  public void setMember(Member member) {
      this.member = member;
      member.getOrders().add(this);
  }

  public void addOrderItem(OrderItem orderItem) {
      orderItems.add(orderItem);
      orderItem.setOrder(this);
  }

  public void setDelivery(Delivery delivery) {
      this.delivery = delivery;
      delivery.setOrder(this);
  }

ํ…Œ์ด๋ธ”, ์ปฌ๋Ÿผ๋ช… ์ƒ์„ฑ ์ „๋žต

  • SpringPhysicalNamingStrategy

    • ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์˜ ๊ธฐ์กด ๊ตฌํ˜„์€ ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ๋ช…์„ ๊ทธ๋Œ€๋กœ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋ช…์œผ๋กœ ์‚ฌ์šฉ

    • ์Šคํ”„๋ง ๋ถ€ํŠธ ๊ธฐ๋ณธ ์„ค์ •์€ (์—”ํ‹ฐํ‹ฐ/ํ•„๋“œ > ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ)

      • CamelCase -> _(underscore)

      • .(dot) -> _(underscore)

      • ๋Œ€๋ฌธ์ž -> ์†Œ๋ฌธ์ž

  • ๋…ผ๋ฆฌ๋ช… ์ ์šฉ

    • ๋ช…์‹œ์ ์œผ๋กœ ์ปฌ๋Ÿผ/ํ…Œ์ด๋ธ”๋ช…์„ ์ง์ ‘ ์ ์ง€ ์•Š์œผ๋ฉด ImplicitNamingStrategy ์‚ฌ์šฉ

    spring.jpa.hibernate.naming.implicit-strategy : 
    org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
  • ๋ฌผ๋ฆฌ๋ช… ์ ์šฉ

    • ๋ชจ๋“  ๋…ผ๋ฆฌ๋ช…, ์‹ค์ œ ํ…Œ์ด๋ธ”์— ์ ์šฉ

    • SpringPhysicalNamingStrategy ๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๋ฃฐ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ(username -> usernm)

    spring.jpa.hibernate.naming.physical-strategy: 
    org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

๋„๋ฉ”์ธ ๊ฐœ๋ฐœ

Repository

  • @Repository

    • ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก, JPA ์˜ˆ์™ธ๋ฅผ ์Šคํ”„๋ง ๊ธฐ๋ฐ˜ ์˜ˆ์™ธ๋กœ ์˜ˆ์™ธ ๋ณ€ํ™˜

  • @PersistenceContext

    • ์—”ํ‹ฐํ‹ฐ ๋ฉ”๋‹ˆ์ €( EntityManager) ์ฃผ์ž…

  • @PersistenceUnit

    • ์—”ํ‹ฐํ‹ฐ ๋ฉ”๋‹ˆํ„ฐ ํŒฉํ† ๋ฆฌ( EntityManagerFactory) ์ฃผ์ž…

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    /**
     * SpringBoot(SpringDataJPA) ๊ฐ€
     * @PersistenceContext ๋Œ€์‹  final, RequiredArgsConstructor (@Autowired)๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅํ•˜๋„๋ก ์ง€์›
     */
    private final EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }

    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }

    public List<Member> findByName(String name) {
        return em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
    }
}

Service

  • @Service

  • @Transactional

    • ํŠธ๋žœ์žญ์…˜, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ

    • readOnly=true

      • ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ์ด ์—†๋Š” ์ฝ๊ธฐ ์ „์šฉ ๋ฉ”์„œ๋“œ์— ์‚ฌ์šฉ

      • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ flush ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์•ฝ๊ฐ„์˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ(์ฝ๊ธฐ ์ „์šฉ์—๋Š” ๋‹ค ์ ์šฉ)

    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ง€์›ํ•˜๋ฉด DB์—์„œ ์„ฑ๋Šฅ ํ–ฅ์ƒ

  • @Autowired

    • ์ƒ์„ฑ์ž Injection์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉ, ์ƒ์„ฑ์ž๊ฐ€ ํ•˜๋‚˜๋ฉด ์ƒ๋žต ๊ฐ€๋Šฅ

DI ์ฃผ์ž…

๊ณตํ†ต์ ์œผ๋กœ๋Š” Spring ๊ฐ€๋™ ์‹œ ์˜์กด์„ฑ ์ฃผ์ž… ๋ฐœ์ƒ

Field Injection

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ ์˜์กด์„ฑ ํ•„๋“œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์–ด mock ๊ฐ์ฒด ์ฃผ์ž…์ด ์–ด๋ ค์šด ๋‹จ์ 

@Service
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;
}

Setter injection

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ mock ๊ฐ์ฒด ์ฃผ์ž… ๊ฐ€๋Šฅ

  • ํ•˜์ง€๋งŒ, setter ๋ฉ”์„œ๋“œ๊ฐ€ ๋…ธ์ถœ๋˜์–ด ์ค‘๊ฐ„์— ์ƒ์„ฑ์ž ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๊ณ , ์˜์กด์„ฑ ํ•„๋“œ ์ถ”๊ฐ€ ์‹œ ๋ฒˆ๊ฑฐ๋กœ์šด ์ฝ”๋“œ ์ถ”๊ฐ€๊ฐ€ ํ•„์š”

@Service
public class MemberService {
    private MemberRepository memberRepository;

    @Autowired
    public voidMemberRepository(MemberRepository memberRepository) {
      this.memberRepository = memberRepository;
    }
}

Construct injection

  • ์ƒ์„ฑ์ž ์ฃผ์ž… ๋ฐฉ์‹์„ ๊ถŒ์žฅ

  • ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅํ•œ ์•ˆ์ „ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ

    • ์ƒ์„ฑ์ž์—์„œ injection ๋˜๋ฏ€๋กœ ์ค‘๊ฐ„์— ์ƒ์„ฑ์ž ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅ

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ ์ƒ์„ฑ์ž ์ฃผ์ž… ๊ด€๋ จํ•˜์—ฌ ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ์ธ์ง€ ๊ฐ€๋Šฅ

  • ์ƒ์„ฑ์ž๊ฐ€ ํ•˜๋‚˜๋ฉด, @Autowired ์ƒ๋žต ๊ฐ€๋Šฅ

@Service
public class MemberService {
    private MemberRepository memberRepository;

    @Autowired
    public MemberRepository(MemberRepository memberRepository) {
      this.memberRepository = memberRepository;
    }
}

Construct injection using lombok

  • final ํ•„๋“œ๋งŒ ๋Œ€์ƒ์œผ๋กœ ์ƒ์„ฑ์ž ์ƒ์„ฑ

    • final ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ปดํŒŒ์ผ ์‹œ์ ์— memberRepository๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š๋Š” ์˜ค๋ฅ˜ ์ฒดํฌ ๊ฐ€๋Šฅ

    • ๋ณดํ†ต ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๋ฐœ๊ฒฌ

  • injection์— ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ

@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;
}

https://data-make.tistory.com/657

Test

  • @RunWith(SpringRunner.class)

    • ์Šคํ”„๋ง๊ณผ ํ…Œ์ŠคํŠธ ํ†ตํ•ฉ

  • @SpringBootTest

    • ์Šคํ”„๋ง ๋ถ€ํŠธ ๋„์šฐ๊ณ  ํ…Œ์ŠคํŠธ(์ด๊ฒŒ ์—†์œผ๋ฉด @Autowired ๋‹ค ์‹คํŒจ)

  • @Transactional

    • ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ ์ง€์›

    • ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ  ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚˜๋ฉด ํŠธ๋žœ์žญ์…˜์„ ๊ฐ•์ œ๋กœ ๋กค๋ฐฑ

    • ์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์—์„œ ์‚ฌ์šฉ๋  ๋•Œ๋งŒ ๋กค๋ฐฑ

GivenWhenThen

In-Memory DB

  • ํ…Œ์ŠคํŠธ๋Š” ์ผ€์ด์Šค ๊ฒฉ๋ฆฌ๋œ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜๊ณ , ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜ ํ•˜์ž.

    • In-Memory DB ์‚ฌ์šฉ์ด ๊ฐ€์žฅ ์ด์ƒ์ !

  • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์œ„ํ•œ ์Šคํ”„๋ง ํ™˜๊ฒฝ(src/test/resources/application.yml)๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” ํ™˜๊ฒฝ(src/main/resources/application.yml) ์„ค์ • ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•˜์ž.

  • ์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” datasource ์„ค์ •์ด ์—†์œผ๋ฉด, ๊ธฐ๋ณธ์ ์„ In-Memory DB ์‚ฌ์šฉ

    • driver-class : ํ˜„์žฌ ๋“ฑ๋ก๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณด๊ณ  ๊ฒฐ์ •

    • ddl-auto : create-drop ๋ชจ๋“œ๋กœ ๋™์ž‘

    • datasource, JPA ๊ด€๋ จ๋œ ๋ณ„๋„์˜ ์ถ”๊ฐ€ ์„ค์ •์„ ํ•˜์ง€ ์•Š์•„๋„ ๊ฐ€๋Šฅ (์ž๋™์œผ๋กœ ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ชจ๋“œ ์ „ํ™˜)

H2 Database Engine Cheat Sheet

๋„๋ฉ”์ธ ๋ชจ๋ธ ํŒจํ„ด

๋„๋ฉ”์ธ ๋ชจ๋ธ ํŒจํ„ด

  • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ฐ€์ง€๊ณ  ๊ฐ์ฒด ์ง€ํ–ฅ์˜ ํŠน์„ฑ์„ ์ ๊ทน ํ™œ์šฉํ•˜๋Š” ํŒจํ„ด

  • ์„œ๋น„์Šค ๊ณ„์ธต์€ ๋‹จ์ˆœํžˆ ์—”ํ‹ฐํ‹ฐ์— ํ•„์š”ํ•œ ์š”์ฒญ์„ ์œ„์ž„ํ•˜๋Š” ์—ญํ• 

  • JPA, ORM...

ํŠธ๋žœ์žญ์…˜ ์Šคํฌ๋ฆฝํŠธ ํŒจํ„ด

  • ์—”ํ‹ฐํ‹ฐ์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๊ฑฐ์˜ ์—†๊ณ  ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ๋Œ€๋ถ€๋ถ„์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํŒจํ„ด

  • Mybatis

๋ณ€๊ฒฝ ๊ฐ์ง€์™€ ๋ณ‘ํ•ฉ

์ค€์˜์† ์—”ํ‹ฐํ‹ฐ

  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋”์ด์ƒ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๋Š” ์—”ํ‹ฐํ‹ฐ

  • ์‹๋ณ„์ž๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” new Object ์•คํ‹ฐํ‹ฐ๋ฅผ ์ค€์˜์† ์—”ํ‹ฐํ‹ฐ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Œ

Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());

์ค€์˜์† ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•

๋ณ€๊ฒฝ ๊ฐ์ง€ ๊ธฐ๋Šฅ ์‚ฌ์šฉ

  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‹ค์‹œ ์กฐํšŒํ•œ ํ›„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‹ค์‹œ ์กฐํšŒ/๋ณ€๊ฒฝํ•  ๊ฒฝ์šฐ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ๋ณ€๊ฒฝ ๊ฐ์ง€(Dirty Checking)๊ฐ€ ๋™์ž‘ํ•ด์„œ UPDATE ์ฟผ๋ฆฌ ์‹คํ–‰

  • ๊ท€์ฐฎ์„ ์ˆ˜ ์žˆ์ง€๋งŒ ๋ณ‘ํ•ฉ์€ ์œ„ํ—˜์„ฑ์ด ์กด์žฌํ•˜๋ฏ€๋กœ Dirty Checking ์„ ์ž˜ ํ™œ์šฉํ•˜์ž.

/**
  * @param itemId
  * @param param : ํŒŒ๋ฆฌ๋ฏธํ„ฐ๋กœ ๋„˜์–ด์˜จ ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ
  */
  @Transactional
  public void updateItem(Long itemId, Item param) {
    Item findItem = itemRepository.findOne(itemId); //๊ฐ™์€ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ(์˜์† ์ƒํƒœ)
    findItem.setPrice(param.getPrice()); //๋ฐ์ดํ„ฐ ์ˆ˜์ •
    findItem.setName(param.getName());
    findItem.setStockQuantity(param.getStockQuantity());
    // Transactional commit -> flush
  }

๋ณ‘ํ•ฉ(merge) ์‚ฌ์šฉ

  • ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์† ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ

  • ๋ณ€๊ฒฝ ๊ฐ์ง€ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์›ํ•˜๋Š” ์†์„ฑ๋งŒ ์„ ํƒํ•ด์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ณ‘ํ•ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฏ€๋กœ ๋ณ‘ํ•ฉ ์‹œ ๊ฐ’์ด ์—†์œผ๋ฉด null ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š” ์œ„ํ—˜์„ฑ ์กด์žฌ

\1. merge() ์‹คํ–‰

\2. ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜์–ด์˜จ ์ค€์˜์† ์—”ํ‹ฐํ‹ฐ์˜ ์‹๋ณ„์ž ๊ฐ’์œผ๋กœ 1์ฐจ ์บ์‹œ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒ

\2-1. ๋งŒ์•ฝ 1์ฐจ ์บ์‹œ์— ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—†์œผ๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ , 1์ฐจ ์บ์‹œ์— ์ €์žฅ

\3. ์กฐํšŒํ•œ ์˜์† ์—”ํ‹ฐํ‹ฐ์— ์ค€์˜์† ์—”ํ‹ฐํ‹ฐ์˜ ๋ชจ๋“  ๊ฐ’์„ ์ฑ„์›Œ ๋„ฃ๋Š”๋‹ค.

\4. ์˜์† ์ƒํƒœ์ธ ์—”ํ‹ฐํ‹ฐ ๋ฐ˜ํ™˜

\5. ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ๋ณ€๊ฒฝ ๊ฐ์ง€ ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค UPDATE ์ฟผ๋ฆฌ ์‹คํ–‰

@Transactional
void update(Item itemParam) { //itemParam: ํŒŒ๋ฆฌ๋ฏธํ„ฐ๋กœ ๋„˜์–ด์˜จ ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ
    Item mergeItem = em.merge(item);
}

๊ฒฐ๋ก 

  • ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ํ•ญ์ƒ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ž.

  • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์–ด์„คํ”„๊ฒŒ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ง์ž.

  • ํŠธ๋žœ์žญ์…˜์ด ์žˆ๋Š” ์„œ๋น„์Šค ๊ณ„์ธต์— ์‹๋ณ„์ž์™€ ๋ณ€๊ฒฝํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜์ž.

    • parameter, dto ํ™œ์šฉ

  • ํŠธ๋žœ์žญ์…˜์ด ์žˆ๋Š” ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ , ์—”ํ‹ฐํ‹ฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝํ•˜ํ•˜์ž.

    • ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‹คํ–‰

    • Setter ์—†์ด ์—”ํ‹ฐํ‹ฐ์—์„œ ๋ฐ”๋กœ ์ถ”์  ๊ฐ€๋Šฅํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์ž.

/**
 * Controller
 */ 
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") BookForm form) {

    itemService.updateItem(itemId, form.getName(), form.getPrice(), form.getStockQuantity());

    return "redirect:/items";
}

/**
 * Service
 */
@Transactional
public void updateItem(Long itemId, String name, int price, int stockQuantity) {
    Item findItem = itemRepository.findOne(itemId); //๊ฐ™์€ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ(์˜์† ์ƒํƒœ)
    findItem.change(name, price, stockQuantity);
}

Last updated