[Spring JPA 1]_회원 리포지토리, 서비스개발

youuu·2022년 11월 13일
0

Study

목록 보기
7/11

애플리케이션 아키텍처

계층형 구조 사용

  • controller, web: 웹 계층
  • service : 비즈니스 로직, 트랜잭션 처리
  • repository : JPA를 직접 사용하는 계층, 엔티티 매니저 사용
  • domain : 엔티티가 모여 있는 계층, 모든 계층에서 사용

💻 개발 순서:

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


1. 레포지토리 개발


repository 패키지 생성후 MemberRepository.java 만들기

jsql : select m from Member m
sql과 거의 비슷한데 약간의 차이가 있다.
jpql에서는 from 의 대상이 테이블이 아니라 엔티티가 된다

📂 repository

📋 MemberRepository.java

단축키 사용 :
아래를 작성 후

result에 커서 올린후 option + commnad + n누르면 한줄로 정리 된다.

sql은 테이블에 대상으로 쿼리작성, jsql은 엔티티 객체를 대상으로 쿼리 작성한다.

@PersistenceContext가 있으면

@PersistenceContext
    private EntityManager em;

EntityManager 를 생성자로 인젝션 하였다.
원래는 EntityManager 를 인젝션 하기 위해 @Autowired@PersistenceContext두개가 있어야 인젝션된다.
스프링 부트 JPA에서는 지원해줘서 가능하다.

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.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    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();
    }
}


2. 회원 서비스 개발


service 패키지 생성후 MemberService.java 만들기

📂 service

📋 MemberService.java

@Transactional 꼭 걸기.

  • Transactional은 두가지인데, 스프링에서 제공하는 어노테이션으로 사용.(쓸수 있는 옵션들이 많다.)

1. readOnly

  • @Transactional(readOnly = true)
    조회하는 곳의 트렌젝션에서 readOnly를 걸면 성능을 최적화 할 수 있다.
    영속성 컨텍스트를 플러시 안해서 dutychecking을 안하는 이점이 있다.
    조회하는 곳 에선 readOnly=true를 사용.
    ➡️ 🔺 읽기가 아닌 쓰기에서 readOnly=true를 넣으면 데이터 변경이 안된다.

  • 셋팅시 public class에 @Transactional(readOnly = true)를 걸고 쓰기를 해야하는 부분에만 @Transactional을 걸면 된다. ➡️ @Transactional의 기본값은 false 다.

2. @Autowired

필드 인젝션.
@Autowired을 하면 스프링이 스프링 빈에 등록되어 있는 MemberRepository를 인젝션 해준다.

3. validateDuplicateMember

아래처럼 작성할 경우 동시에 둘이 같은 name를 입력할 경우 두개가 동시에 들어간다. 🔺문제가 될 수 있다.
➡️ 🚨 멀티 쓰레드나 이런 상황을 고려하기 위해 작성 후 한번 더 잡는다. 데이터 베이스에서 Membername을 유니크 제약 조건으로 잡아주는 것을 권장한다. 안전하다.

4. @Autowired 를 할 경우 단점 ➡️ @RequiredArgsConstructore 를 사용한다

1️⃣
보통 아래처럼 많이 사용한다.
그러나 그럴때 단점이 테스트 할때도 바꿀수 없다는 것이다.

@Autowired
private MemberRepository memberRepository;

2️⃣
그래서 나온 방법이 setter 인젝션

바로 주입하는게 아니라 setMemberRepository로 들어와서 주입.

  • 장점 : 테스트 코드를 작성할때 직접 Mock을 주입 해줄 수 있다.
    필드 주입은 그냥 주입하기가 까다롭다.
  • 그러나 치명적인 단점이 있다. 런타임에 누군가가 바꿀수 있다. 사실 안좋은 방법이다.
  @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

3️⃣
⭐️ 궁극적으로 요즘에 권장하는 방법은 생성자 인젝션이다

  • 생성자 인젝션은 생성과 동시에 완성되어지기 때문에 set해서 바꿀 수는 없다.
  • 생성자가 하나일 경우엔 @Autowired를 빼도 스프링이 알아서 인젝션 시켜준다.
  • 그 다음 더 수정할 일이 없기 때문에 private MemberRepository memberRepository;final을 붙여준다. (final 넣는걸 추천.)
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

4️⃣ ⭐️⭐️⭐️
@RequiredArgsConstructore를 사용하면 final 있는 필드만 가지고 생성자를 인젝션 해준다.

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@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) {
        //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 memberId) {
        return memberRepository.findOne(memberId);
    }

}
profile
공부중인 주니어 개발자

0개의 댓글