spring boot/JPA

[ JPA ] 회원 도메인 개발

지니엠 2023. 12. 27. 15:14

 

 

 

 

도메인 분석, 설계를 다 했다면, 본격 개발을 시작해 보자 

개발순서 :  레포지토리 계층 > 서비스  > 테스트 케이스 검증 >  계층에 적용

 

 

 

 


 

 

 

회원 레포지토리 

 

repository 패키지를 먼저 만들고, MemberRepository java파일을 생성

package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Member;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@Repository // 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
public class MemberRepository {

     @PersistenceContext	// 엔티티 매니저(EntityManager) injection
     private 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();
    // sql은 테이블을 대상으로 jpql은 엔티티를 대상으로
  }

    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
@Transactional(readOnly = true)
public class MemberService{

	@Autowired
    private 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 IllegalSateException("이미 존재하는 회원입니다.");
       }     
    }
   /* 전체 회원 조회 */
   public List<Member> findMembers(){
   	return memberRepository.findAll();
   }
    public Member findOne(Long memberId){
    	return memberRepository.findOne(memberId);
    }

@Transactional 의 default은 readonly = false

위의 서비스단은 읽는 메소드가 많기 때문에, 전체를 readonly = true로 묶고, 

쓰기가 필요한 메소드만 따로 @Transactional으로 묶어 줬다.

 

위에 작성한 validation로직은 문제가 발생할 가능성이 높다.

검증 로직이 있다고 해도 멀티 쓰레드 상황을 고려해서 DB에 유니크제약조건을 걸어주는 등 한 번 더 걸러주는 것이 좋다.

 

reposirtory injection 할때 방법이 여러 가지가 있다.

위의 방법같이 필드주입방법 

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

 

setter injection 방법

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

 

생성자 주입 방법

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

 

 

가장 좋은 방법은 생성자 injection 이다!

요즘 스프링은 생성자가 하나일 경우 @Autowired 없어도 자동 injection으로 해준다.

여기에 롬복의 @AllArgsConstructor을 적용해 주면 필드의 모든 것을 가지고 생성자를 만들어주는 방법이다.

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

즉 이부분을 생략할 수 있다.

 

여기서 더 좋은 방법이 @RequiredArgsConstructor

final이 있는 필드만 가지고 생성자를 만들어주는 방법이다. 

 

 

 

먼저 만들었던 repository에서도 생성자를 만들어 줄 수 있다.

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    private final EntityManager em;
    //스프링이 엔티티 매니저를 만들어서 여기에 주입해줌 > 이걸 생성자로 injection해줬다.
    // 원래는 persistance를 써야하는데 spring data jpa가 @Autowired로 할수있게 해줬고
    // RequiredArgsConstructor를 써서 @Autowired 안써도 사용할수있게 해줬다.

 

이렇게 일관성있게 사용하는 것이 좋다!