IT/development

[Spring data JPA] 초간단 CRUD

알 수 없는 사용자 2022. 11. 21. 06:55
반응형

목차

     

    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

     

    반응형