ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • #Spring boot-7 애플리케이션 구현 준비
    SPRING-BOOT 2021. 3. 3. 14:21

    [출처] 인프런 김영한 강사님 -실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

     

    <구현 요구사항>

     

    실제 동작하는 화면을 먼저 확인한다.

     

    기능 목록

     

    · 회원 기능

       ·  회원 등록

       ·  회원 조회

    · 상품 기능

       ·  상품 등록

       ·  상품 수정

       ·  상품 조회

    · 주문 기능

       ·  상품 주문

       ·  주문 내역 조회

       ·  주문 취소

     

    예제를 단순화 하기 위해 다음 기능은 구현X

      · 로그인과 권한 관리X

      · 파라미터 검증과 예오 처리 단순화

      · 상품은 도서만 사용  

      · 카테고리는 사용X

      · 배송 정보는 사용X

     

     

    <애플리케이션 아키텍처>

    계층형 구조 사용

      · controller, web: 웹 계층 (여기서는 contoller가 바로 Repository에 접근하여 유연하게 사용 할수 있도록 할것임.)

      · service: 비즈니스 로직, 트랜잭션 처리

      · repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용

      · domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용

     

     

    패키지 구조

    · jpabook.jpashop

       · domain

       · exception(공통예외)

       · repository

       · service

       · web

     

    개발 순서: 서비스, 리포지토리 계층을 개발하고, 테스트 케이스를 작성해서 검증, 마지막에 웹 계층 적용

     

     

    회원 도메인 개발

    구현 기능

     · 회원 등록

     · 회원 목록 조회

     

    순서

     · 회원 엔티티 코드 다시 보기

     · 회원 리포지토리 개발

     · 회원 서비스 개발

     · 회원 기능 테스트

     

    회원 리포지토리 개발

    회원 리포지토리 코드

    package jpabook.jpashop.Repository;
    
    import jpabook.jpashop.domain.Member;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Repository;
    
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    import java.util.List;
    
    @Repository
    @RequiredArgsConstructor//Autowired가 사용가는하다는 것은
    public class MemberRepository {
        //@PersistenceContext
        // 1. spring이 em 을만들어서 여기다가 주입해주게 된다.
        //@Autowired
        // 2.스프링 부트라서 @PersistenceContext를 안쓰고  @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(); //ctrl_alt_ n 합쳐서 인라인 만들기
        }
    
        public List<Member> findByName(String name){
            return em.createQuery("select m from Member m where m.name =:name",Member.class)
                    .setParameter("name",name)
                    .getResultList();
        }
     }
    

    기술 설명

     · @Repository : 스프링 빈으로 등록(안에 @Component 어노테이션이 있음)

         compoent scan의 대상이되서 scan이 된다.

        , JPA 예외를 스프링 기반 예외로 예외 변환

     · @PersistenceContext : 엔티티 메니저( EntityManager ) 주입

     · @PersistenceUnit : 엔티티 메니터 팩토리( EntityManagerFactory ) 주입

     

    기능 설명

     · save()

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

     · findOne()

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

     · findAll()

        public List<Member> findAll(){
            return em.createQuery("select m from Member m", Member.class) //jpql, 반환객체
                    .getResultList(); //ctrl_alt_ n 합쳐서 인라인 만들기
        }

     · findByName()

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

     

     

    회원 서비스 개발

     

    회원 서비스 코드

    package jpabook.jpashop.service;
    
    import jpabook.jpashop.Repository.MemberRepository;
    import jpabook.jpashop.domain.Member;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    @Transactional(readOnly = true) //jpa의 모든 데이터 변경이나 로직은 트랜잭션 안에서 다일어나야한다.
    //영속섣컨텍스트를 flush안하고 더티체킹을 안해서 성능상 이점이있음.
    //읽기 전용이기 때문에 디비한태도 부화를 덜어줄 수 있음.
    @RequiredArgsConstructor //4.lombok 주입
    public class MemberService {
    
        //1. 필드주입
        //주입하기 까다로움
        //@Autowired
        //private MemberRepository memberRepository;
    
        //2. setter 주입
        //메서드로 주입하기 때문에 가짜 memberRepository를 주입할수 있음
        //단점 -> runtime(실제 application 돌아가는 시점에)에 누군가 이걸 변경 할 수 있음.
        //@Autowired
        //public void setMemberRepository(MemberRepository memberRepository){
        //    this.memberRepository = memberRepository;
        //}
    
        //3. 생성자 주입
        //private final MemberRepository memberRepository;
        //
        //public MemberService(MemberRepository memberRepository) {
        //     this.memberRepository = memberRepository;
        //}
    
        //lombok
    
        private final MemberRepository memberRepository;
    
    
        //회원 가입
        @Transactional //jpa의 모든 데이터 변경이나 로직은 트랜잭션 안에서 다일어나야한다.
        public Long join(Member member){
            validateDuplicateMember(member);//중복 회원 검증
            memberRepository.save(member);
            return member.getId();
        }
    
        private void validateDuplicateMember(Member member) {
            //EXCEPTION
            List<Member> findMembers = memberRepository.findByName(member.getName());
            if(!findMembers.isEmpty()){
                throw new IllegalStateException("이미 존재하는 회원입니다.");
            }
        }
    
        //회원 전체 조회
        public List<Member> findMembers(){
            return memberRepository.findAll();
        }
    
        //회원 단건 조회
        public Member findOne(Long id){
            return memberRepository.findOne(id);
        }
    
    
    }
    

    기술 설명

     · @Service

     · @Transactional : 트랜잭션, 영속성 컨텍스트

         · readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 영속성 컨텍스트를 플러시 하지 않으므로

                                 약간의 성능 향상(읽기 전용에는 다 적용)

         · 데이터베이스 드라이버가 지원하면 DB에서 성능 향상

     · @Autowired

          · 생성자 Injection 많이 사용, 생성자가 하나면 생략 가능

     

    기능 설명

     · join()

     · findMembers()

     · findOne()

     

    참고: 실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회원명 컬럼에 유니크 제 약 조건을 추가하는 것이 안전하다.

    참고: 스프링 필드 주입 대신에 생성자 주입을 사용하자

     

    필드 주입(발전 1단계)

    public class MemberService {
         @Autowired
         MemberRepository memberRepository;
         ...
    }

    주입하기 까다로움

     

    setter 주입(발전 2단계)

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

    메서드로 주입하기 때문에 가짜 memberRepository를 주입할수 있음
    단점 -> runtime(실제 application 돌아가는 시점에)에 누군가 이걸 변경 할 수 있음.

     

    생성자 주입(발전 3단계)

    public class MemberService {
       private final MemberRepository memberRepository;
       
       public MemberService(MemberRepository memberRepository) {
         this.memberRepository = memberRepository;
       }
       ...
    }

     · 생성자 주입 방식을 권장

       (한번 생성할 때 다 완성이 되버리기 때문에 중간에 set을통해 memberRepository를 바꿀 수 없다.)

     · test case를 작성할 때 직접 주입을 해줘야하는데 이런 잘 놓치기 쉬운부분에 에러 표시가 뜨기때문에 

       얘는 이게 필요해, 이걸 의존하고 있어를 알 수 있다. 

     · 변경 불가능한 안전한 객체 생성 가능

     · 생성자가 하나면, @Autowired 를 생략할 수 있다.

     · final 키워드를 추가하면 컴파일 시점에 memberRepository 를 설정하지 않는 오류를 체크할 수 있다.

      (보통 기본 생성자를 추가할 때 발견)

     

    lombok(발전 4단계)

    @RequiredArgsConstructor
    public class MemberService {
       private final MemberRepository memberRepository;
       ...
    }

    참고: 스프링 데이터 JPA를 사용하면 EntityManager 도 주입 가능

    @RequiredArgsConstructor 는 final이 있는 필드만 가지고 생성자를 만들어준다.

     

    * MemberService 최종 코드*

    @Service
    @Transactional(readOnly = true)
    @RequiredArgsConstructor
    public class MemberService {
       private final MemberRepository memberRepository;
       
       /**
       * 회원가입
       */
       @Transactional //변경
       public Long join(Member member) {
         validateDuplicateMember(member); //중복 회원 검증
         memberRepository.save(member);
         return member.getId();
       }
       
       private void validateDuplicateMember(Member member) {
         List<Member> findMembers =
        memberRepository.findByName(member.getName());
         if (!findMembers.isEmpty()) {
        	 throw new IllegalStateException("이미 존재하는 회원입니다.");
         }
       }
       
       /**
       * 전체 회원 조회
       */
       public List<Member> findMembers() {
       		return memberRepository.findAll();
       }
       
       public Member findOne(Long memberId) {
       		return memberRepository.findOne(memberId);
       }
    }

     

     

     

     

    댓글

Designed by Tistory.