계층형 구조 사용
서비스, 리포지토리 계층을 개발하고,
테스트 케이스를 작성해서 검증,
마지막에 웹 계층 적용
repository 패키지 생성후 MemberRepository.java
만들기
jsql :
select m from Member m
sql과 거의 비슷한데 약간의 차이가 있다.
jpql에서는 from 의 대상이 테이블이 아니라 엔티티가 된다
단축키 사용 :
아래를 작성 후
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();
}
}
service 패키지 생성후 MemberService.java
만들기
@Transactional
꼭 걸기.
- Transactional은 두가지인데, 스프링에서 제공하는 어노테이션으로 사용.(쓸수 있는 옵션들이 많다.)
@Transactional(readOnly = true)
조회하는 곳의 트렌젝션에서 readOnly
를 걸면 성능을 최적화 할 수 있다.
영속성 컨텍스트를 플러시 안해서 dutychecking을 안하는 이점이 있다.
조회하는 곳 에선 readOnly=true를 사용.
➡️ 🔺 읽기가 아닌 쓰기에서 readOnly=true
를 넣으면 데이터 변경이 안된다.
셋팅시 public class에 @Transactional(readOnly = true)
를 걸고 쓰기를 해야하는 부분에만 @Transactional
을 걸면 된다. ➡️ @Transactional
의 기본값은 false 다.
필드 인젝션.
@Autowired
을 하면 스프링이 스프링 빈에 등록되어 있는 MemberRepository
를 인젝션 해준다.
아래처럼 작성할 경우 동시에 둘이 같은 name를 입력할 경우 두개가 동시에 들어간다. 🔺문제가 될 수 있다.
➡️ 🚨 멀티 쓰레드나 이런 상황을 고려하기 위해 작성 후 한번 더 잡는다. 데이터 베이스에서 Membername을 유니크 제약 조건으로 잡아주는 것을 권장한다. 안전하다.
1️⃣
보통 아래처럼 많이 사용한다.
그러나 그럴때 단점이 테스트 할때도 바꿀수 없다는 것이다.
@Autowired
private MemberRepository memberRepository;
2️⃣
그래서 나온 방법이 setter 인젝션
바로 주입하는게 아니라 setMemberRepository
로 들어와서 주입.
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
3️⃣
⭐️ 궁극적으로 요즘에 권장하는 방법은 생성자 인젝션이다
@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);
}
}