IT/development

[Spring data JPA] ์ดˆ๊ฐ„๋‹จ CRUD

์•Œ ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž 2022. 11. 21.

๋ชฉ์ฐจ

     

    Spring Boot์™€ JPA ์ดˆ๊ฐ„๋‹จ CRUD REST API ์˜ˆ์‹œ(๋‚ด๊ฐ€ ๋ณด๊ธฐ ์œ„ํ•ด ๊ธฐ๋ก)

     

    ๋ผ์ด๋ธŒ์ฝ”๋”ฉ(์‹œ๊ฐ„ ์ƒ ๋งŽ์ด ๋ณต๋ถ™ ํ•จ) ๐Ÿ˜ถ

     

    h2 database ver: H2 2.1.214 (2022-06-13)

    build.gradle ๐Ÿ˜

    plugins {
        id 'org.springframework.boot' version '2.6.11'
        id 'io.spring.dependency-management' version '1.0.13.RELEASE'
        id 'java'
    }
    
    group = 'com.devlsy'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8'
    
    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.boot:spring-boot-devtools'
        compileOnly 'org.projectlombok:lombok'
        runtimeOnly 'com.h2database:h2'
        annotationProcessor 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    
    tasks.named('test') {
        useJUnitPlatform()
    }

     

    domain ๐Ÿ˜‰

    DTO(ํ”„๋ก ํŠธ์™€ ๋ฐ์ดํ„ฐ๋งŒ ์ฃผ๊ณ  ๋ฐ›๋Š” ์šฉ๋„)

    entity๋Š” ์ ˆ๋Œ€๋กœ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต๊ณผ ํ†ต์‹ ํ•˜๋ฉด ์•ˆ๋˜๊ธฐ์— ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต๊ณผ ํ†ต์‹ ํ•˜๋Š” ์šฉ๋„์ธ DTO๋ฅผ ๋”ฐ๋กœ ์ƒ์„ฑํ•ด์„œ ์ด๋†ˆ์œผ๋กœ๋งŒ ํด๋ผ์ด์–ธํŠธ์™€ ํ†ต์‹ 

    DB์˜ ๊ฐ’์„ ์—”ํ‹ฐํ‹ฐ์— ๋‹ด๊ณ  ์—”ํ‹ฐํ‹ฐ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ DTO์— ๋‹ด์•„์„œ DTO๋ฅผ ์ตœ์ข…์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜

    package com.devlsy.devlsyjpa.domain;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.Setter;
    
    import java.time.LocalDateTime;
    
    @Getter @Setter
    @Builder
    public class MemberDTO {
    
        private Long memberId;
        private String name;
        private String email;
    
        private LocalDateTime regDate;
        private LocalDateTime updateDate;
    	
        
       	// convert entity to dto
        public static MemberDTO fromEntity(MemberEntity member) {
            return MemberDTO.builder()
                    .memberId(member.getMemberId())
                    .name(member.getName())
                    .email(member.getEmail())
                    .regDate(member.getRegDate())
                    .updateDate(member.getUpdateDate())
                    .build();
        }
    }

     

    Entity(JPA ๊ด€๋ฆฌ ์ฃผ์ฒด)

    ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๋Š” ๋ถˆ๋ณ€๊ฐ์ฒด์—ฌ์•ผ ํ•˜๋ฏ€๋กœ getter๋งŒ ์—ด์–ด๋‘๊ณ  ๊ฐ์ฒด ์ƒ์„ฑ ์‹œ์ ์— builderํŒจํ„ด์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •

    package com.devlsy.devlsyjpa.domain;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import org.hibernate.annotations.CreationTimestamp;
    import org.hibernate.annotations.UpdateTimestamp;
    
    import javax.persistence.*;
    import java.time.LocalDateTime;
    
    @Entity
    @Getter
    @NoArgsConstructor
    @Table(name = "member_test")
    public class MemberEntity {
    
        @Id @GeneratedValue
        @Column(name = "member_id")
        private Long memberId;
        private String name;
        private String email;
    
        @CreationTimestamp
        @Column(name = "reg_date", updatable = false)
        private LocalDateTime regDate;
    
        @UpdateTimestamp
        @Column(name = "update_date")
        private LocalDateTime updateDate;
    
        @Builder
        public MemberEntity(Long memberId, String name, String email, LocalDateTime regDate, LocalDateTime updateDate) {
            this.memberId = memberId;
            this.name = name;
            this.email = email;
            this.regDate = regDate;
            this.updateDate = updateDate;
        }
    }

     


     

    Controller ๐Ÿ˜

    ๊ฐ์ฒด ์ฃผ์ž…์€ ์ƒ์„ฑ์ž ํŒจํ„ด ์‚ฌ์šฉ

    // @RequiredArgsConstructor ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ๋กฌ๋ณต์„ ์ด์šฉํ•ด์„œ ์ž๋™์ƒ์„ฑํ•ด์ค€๋‹ค.(ํ•„๋“œ์— final์ด๋‚˜ @NotNull์ด ๋ถ™์€ ์ƒ์„ฑ์ž๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์คŒ)
    
    // @RequiredArgsConstructor ์ด ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ ์•ˆํ•  ๊ฒฝ์šฐ ์•„๋ž˜์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•จ, ์žฅ๋‹จ์ด ์žˆ๊ณ  ํ•„๋“œ๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ์ผ ๊ฒฝ์šฐ ์ข€ ์ง€์ €๋ถ„ํ•ด ์งˆ์ˆ˜๋„ ์žˆ์Œ
    @Autowired
        public MemberController(MemberService memberService, EtcService etcService, GoodService goodService, BadService badService) {
            this.memberService = memberService;
            this.etcService = etcService;
            this.goodService = goodService;
            this.badService = badService;
        }
    package com.devlsy.devlsyjpa.controller;
    
    import com.devlsy.devlsyjpa.domain.MemberDTO;
    import com.devlsy.devlsyjpa.service.MemberService;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @Slf4j
    @RestController
    @RequiredArgsConstructor
    public class MemberController {
    
        private final MemberService memberService;
    
        // ๋ฉค๋ฒ„ ๋“ฑ๋ก
        @PostMapping("/")
        public Long createMember(@RequestBody MemberDTO memberDTO) {
            return memberService.createMember(memberDTO);
        }
    
        // ๋ฉค๋ฒ„ ์กฐํšŒ
        @GetMapping("/{memberId}")
        public MemberDTO findOneMember(@PathVariable Long memberId) {
            return memberService.findMember(memberId);
        }
    
        // ๋ฉค๋ฒ„ ๋ชฉ๋ก ์กฐํšŒ
        @GetMapping("/")
        public List<MemberDTO> findAllMember() {
            return memberService.findAllList();
        }
    
        // ๋ฉค๋ฒ„ ์‚ญ์ œ
        @DeleteMapping("/{memberId}")
        public void deleteMember(@PathVariable Long memberId) {
            memberService.deleteMember(memberId);
        }
    
    }

     

     

    Service ๐Ÿค—

    ๊ฐ์ฒด ์ฃผ์ž…์€ ์ƒ์„ฑ์ž ํŒจํ„ด ์‚ฌ์šฉ

    package com.devlsy.devlsyjpa.service;
    
    import com.devlsy.devlsyjpa.domain.MemberDTO;
    import com.devlsy.devlsyjpa.domain.MemberEntity;
    import com.devlsy.devlsyjpa.repository.MemberRepository;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    @Slf4j
    @RequiredArgsConstructor
    public class MemberService {
        private final MemberRepository memberRepository;
    
        /**
         * ๋ฉค๋ฒ„ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ
         * @return
         */
        @Transactional(readOnly = true)
        public List<MemberDTO> findAllList() {
            List<MemberEntity> memberEntityList = memberRepository.findAll();
    // ์ผ๋ฐ˜ foreach(java 1.5 ์ด์ƒ)
    //      List<MemberDTO> memberDTOList = new ArrayList<>();
    //        for (MemberEntity member: memberEntityList) {
    //            memberDTOList.add(MemberDTO.fromEntity(member));
    //        }
    //        return memberDTOList;
    		// java 1.8 stream ์ด์šฉ
    	    List<MemberDto> result = memberEntityList.stream()
                    .map(m -> toDto(m)).collect(toList());
            return result;
        }
    
        /**
         * ๋ฉค๋ฒ„ ๋‹จ๊ฑด ์กฐํšŒ
         * @param
         * @return
         */
        @Transactional(readOnly = true)
        public MemberDTO findMember(Long memberId) {
            MemberEntity findMember = memberRepository.findByMemberId(memberId);
            return MemberDTO.fromEntity(findMember);
        }
    
        /**
         * ๋ฉค๋ฒ„ ๋‹จ๊ฑด ์‚ญ์ œ
         * @param
         * @return
         */
        @Transactional
        public void deleteMember(Long memberId) {
            memberRepository.deleteByMemberId(memberId);
        }
    
        /**
         * ๋ฉค๋ฒ„ ๋“ฑ๋ก
         * @param
         * @return
         */
    
        @Transactional
        public Long createMember(MemberDTO memberDTO) {
            MemberEntity member = MemberEntity.builder()
                    .memberId(memberDTO.getMemberId())
                    .name(memberDTO.getName())
                    .email(memberDTO.getEmail())
                    .build();
    
            MemberEntity save = memberRepository.save(member);
            return save.getMemberId();
        }
    
    }

     

    Repository ๐Ÿ˜˜

    JPA ์ž‘์—… ๊ด€๋ จ JpaRepository๋ฅผ ์ƒ์†๋ฐ›์€ ์ธํ„ฐํŽ˜์ด์Šค

    package com.devlsy.devlsyjpa.repository;
    
    import com.devlsy.devlsyjpa.domain.MemberEntity;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
    
        // ๋ฉค๋ฒ„ ๋‹จ๊ฑด ์กฐํšŒ
        public MemberEntity findByMemberId(Long memberId);
    
        // ๋ฉค๋ฒ„ ๋‹จ๊ฑด ์‚ญ์ œ
        public void deleteByMemberId(Long memberId);
    }

     


    application.yml ๐Ÿ˜†

    spring:
      datasource:
        url: jdbc:h2:tcp://localhost/~/jpashop
        username: sa
        password:
        # h2 database ์‚ฌ์šฉ
        driver-class-name: org.h2.Driver
    
      jpa:
        hibernate:
          # ๋กœ์ปฌ์—์„œ ์ •๋ง ํŽธ๋ฆฌํ•œ ์˜ต์…˜
          ddl-auto: create
        properties:
          hibernate:
            show_sql: true
            format_sql : true
        database-platform: org.hibernate.dialect.H2Dialect
    
    logging:
      level:
        org.hivernate.SQL: debug
        org.hivernate.type: trace

     

    ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐœ๋ฐœ ๋์ด๋‹ค.(SQL์„ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๊ฐ€ ์ „ํ˜€ ์—†๋‹ค.)

    ibatis/mybatis์—์„œ ์ž‘์„ฑํ•˜๋˜ SQL๋ฌธ์€ ์•ˆ๋…•์ด๋‹ค.

    ibatis/mybatis์—์„œ ","๊ณผ ๊ฐ™์€ ์˜คํƒˆ์ž ๋•Œ๋ฌธ์— ์—๋Ÿฌ ์žก๋А๋ผ ์‹œ๊ฐ„์„ ๋ณด๋ƒˆ๋˜ ๋‚ ๋“ค์ด ์ฃผ๋งˆ๋“ฑ์ฒ˜๋Ÿผ ์Šค์ณ๊ฐ„๋‹ค.

     

    JPA๋ฅผ ๊ณ„์† ๊ณต๋ถ€ํ•˜๊ณ  ์žˆ๋Š” ์ž…์žฅ์ด๊ณ  ํ•™์Šต ๋‚œ์ด๋„๊ฐ€ ์žˆ์ง€๋งŒ ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ์ƒ์‚ฐ/์œ ์ง€๋ณด์ˆ˜ ๋“ฑ ์—ฌ๋Ÿฌ๋ฐฉ๋ฉด์—์„œ ์ƒ๋‹นํ•œ ํšจ๊ณผ๊ฐ€ ๋‚  ๋“ฏ ์‹ถ๋‹ค.

    ์•„์ง์€ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋Š” ๊ฐ•์˜๋งŒ ๋“ฃ๊ณ  ์‹ค์ œ ํ˜ผ์ž ํ•ด๋ณด๊ณ  ๋‚ด๊ป„๋กœ ๋งŒ๋“ค์ง€ ์•Š์•˜๊ธฐ์— ๊ทธ๊ฒƒ์€ ๋‚˜์ค‘์— ๊ธฐ๋กํ•  ์˜ˆ์ •


    ํ…Œ์ŠคํŠธ(postman ์ด์šฉ)

    ํšŒ์› ๋“ฑ๋ก

     

    ํšŒ์› ์‚ญ์ œ

     

     

    ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ๋ช‡๊ฐœ ๋„ฃ๊ณ  ์กฐํšŒ

     

    ํšŒ์› ๋‹จ๊ฑด ์กฐํšŒ

     

    ํšŒ์› ์ˆ˜์ •์€ ์šฐ์„  ์ƒ๋žต(JPA์—์„œ๋Š” ๋”ํ‹ฐ ์ฒดํ‚น์„ ์ด์šฉํ•ด ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์  ์ด์ „์— ํ•„๋“œ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฐ”๋€ ๋‚ด์šฉ์„ DB์— commitํ•œ๋‹ค.)

    save ํ•œ๋ฒˆ์œผ๋กœ update๊นŒ์ง€ ํ•  ์ˆ˜ ์žˆ๋‹ค.

     

    ์ „์ฒด ์ฝ”๋“œ: https://github.com/devLsy/devlsyjpa

     

    ๋Œ“๊ธ€