@PersistenceUnit : 엔티티 메니터 팩토리( EntityManagerFactory ) 직접 주입 받을 수있음 .
그러나
@PersistenceContext : 엔티티 메니저( EntityManager ) 주입 하는 것이 있으므로 거의 쓸일 없을 것 .
++ 트랜젝션이 커밋되는 시점에 db에 반영됨 (insert쿼리 날라감)
public Member findOne(Long id) {
return em. find(Member.class, id);
}
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
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public void save(Member member) {
em.persist(member); //jpa가 저장하는 로직 된다.
}
public Member findOne(Long id) {
return em. find(Member.class, id);
}
//createQuery은 Entity 객체를 대상으로 쿼리를 줘서, 테이블 단위로 쿼리 처리하는 sql과 조금 다름
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();
}
}
jpa 데이터 변경이나 로직들은 트랜젹션 안에서 해야한다!
( 그래야 Lazy도 가능.. ) @Transactional
이거는 javax랑 스프링거 중에서
import org.springframework.transaction.annotation.Transactional; 이거 쓰기
@Transactional(readOnly = true)옵션 주면
jpa 조회하는 곳에서는 성능 최적화 시켜준다.
읽기에는 이걸 넣어주면 좋다.
@Autowired : 스프링이 스프링빈에 들어있는 bean을 Injectiongownsek.
@Autowired
private MemberRepository memberRepository;
로 쓰기도 하지만 테스트 등을 할 때 이름을 바꿀 수 없는 문제가 있으므로
settter Injection으로
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
단점은 runtime에 누군가 바꿀 수 있음.
궁극적으로 생성자 injection이 좋다.
private MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
중간에 바꿀수있지도 않고
테스트 케이스 생성할 때 직접 주입시켜서 의존을 안놓치고 잘 챙길 수있음.
요새는 생성자 하나만 있는 경우에는 스프링이 자동으로 autowired 해준다.
필드 변경 할 이유 없어서 final 권장
private final MemberRepository memberRepository;
( final로 해두면 컴파일시점에 값 세팅 안한거 체크 가능 )
@AllArgsConstructor는 생성자 만들어주고,
@RequiredArgsConstructor은 final가진 거만 생성자 만들어준다.
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.AllArgsConstructor;
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) //readOnly를 기본으로 두고
@RequiredArgsConstructor //final 필드 생성자 만들어준다.
public class MemberService {
private final MemberRepository memberRepository;
/*@Autowired
public MemberService(MemberRepository memberRepository) {
this.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);
}
}
테스트 중 밑의 에러가 뜬다면
java.lang.IllegalStateException: Failed to load ApplicationContext
h2 데이터베이스 띄우는걸 까먹지 않았는지 확인 .
쿼리를 보면
insert가 없다.
이유는
persist할 때 Transactional의 기본이 Rollback이기 때문이다. (Commit이 아니라)
만일 @Rollback(flase)로 주면 insert 가 보인다.
Junit5에서는 밑의 코드 대신에
@Test(expected = IllegalStateException.class)
public void 중복_회원_예외() throws Exception {
//Given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//When
memberService.join(member1);
memberService.join(member2); //예외가 발생해야 한다.
//Then
fail("예외가 발생해야 한다.");
}
Junit5인 jupiter.api Package의 @Test 어노테이션의 경우 expected 속성이 제거되어 더 이상 @Test 어노테이션을 통한 Exception Test가 불가능
Junit5에서는 Assertions의 assertThrows를 이용하여 Junit4와 동일하게 Exception을 테스트
그러므로 아래의 코드로 진행
@Test
public void 중복_회원_예외() throws Exception {
//Given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//When
memberService.join(member1);
//memberService.join(member2); //예외가 발생해야 한다.
//Then
IllegalStateException thrown = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));
}
최종 코드
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@WebAppConfiguration
class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Autowired EntityManager em;
@Test
//@Rollback(value = false)
public void 회원가입() throws Exception {
//given
Member member = new Member();
member.setName("Kim");
//when
Long saveId = memberService.join(member);
//then
assertEquals(member, memberRepository.findOne(saveId));
}
@Test
public void 중복_회원_예외() throws Exception {
//Given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//When
memberService.join(member1);
//memberService.join(member2); //예외가 발생해야 한다.
//Then
IllegalStateException thrown = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));
}
}