참고 : 인프런 [ 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 김영한 ]
~/repository/MemberRepository.java
@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public void save(Member member){
em.persist(member);
}
public Member findOne(Long id){ //회원 아이디로 조회
return em.find(Member.class, id);
}
//jpql로 리스트 조회 대상 : 엔티티
public List<Member> findAll(){
return em.createQuery("select m from Member m",Member.class)
.getResultList();
}
public List<Member> findName(String name){
return em.createQuery("select m from Member m where m.name = :name",Member.class)
.setParameter("name",name)
.getResultList();
}
}
@PersistenceContext
JPA에서 엔티티 매니저를 주입받기 위해 사용되는 어노테이션
- 스프링 프레임워크의 경우 @Autowired 어노테이션과 함께 사용되어 엔티티 매니저를 주입받을 수 있음
즉 스프링에서는
@PersistenceContext private EntityManager em;과
@Autowired private EntityManager em;이 같음
JPQL
- SQL 쿼리 대상 : 테이블
- JPQL 쿼리 대상 : 엔티티 객체
- JPQL에서는 엔티티 클래스와 필드를 직접 사용하여 쿼리를 작성
- 파라미터 바인딩 : setParameter(name,value)
String jpql = "SELECT p FROM Product p WHERE p.price > :price"; List<Product> products = entityManager.createQuery(jpql, Product.class) .setParameter("price", 100) .getResultList();- 리스트형태로 반환 : getResultList()
~/service/MemberService.java
@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("이미 존재하는 회원입니다.");
}
}
//회원 전체 조회
...
//단건 조회
...
}
엔티티 매니저는 member 객체를 영속성 컨텍스트에 추가하고, 이때 member 객체의 @Id 필드를 기준으로 관리@Id 필드는 엔티티의 기본 키로 사용되며, 영속성 컨텍스트에서 엔티티를 식별하는 데 사용됩니다.@Id 값은 엔티티가 영속성 컨텍스트에 추가될 때 이미 설정되어 있거나, @GeneratedValue를 통해 자동 생성됩니다. 즉, @Id 값은 항상 생성 되어있는 것이 보장됨.@Transactional
트랜잭션 관리가 필요한 코드의 시작과 끝을 명시.
- @Transactional(readOnly = true) : 조회만 가능
- @Transactional(readOnly = false) (기본값)
위 코드에서는 클래스 내 모든 메서드들을 @Transactional(readOnly = true)로 설정하고, 회원가입 로직인 join 메서드만 @Transactional(readOnly = false)로 설정해주었다.
IllegalStateException 런타임 예외
객체의 상태가 호출된 메서드를 실행하기에 적절하지 않을 때 사용
참고
실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회원명 컬럼에 유니크 제약 조 건을 추가하는 것이 안전하다
@Autowired //필드 인젝션
private MyRepository myRepository;@Autowired 생략 가능private final MyRepository myRepository;
//@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}@RequiredArgsConstructor :final로 선언된 모든 필드와 @NonNull로 마크된 필드에 대해 생성자를 자동으로 생성 @RequiredArgsConstructor
public class MyService {
private final MyRepository myRepository;
}
- 생성자 주입 방식을 권장
- 변경 불가능한 안전한 객체 생성 가능
- 생성자가 하나면,
@Autowired를 생략할 수 있다.final키워드를 추가하면 컴파일 시점에memberRepository를 설정하지 않는 오류를 체크할 수 있다.(보통 기본 생성자를 추가할 때 발견)
🌟 회원 리파지토리를 공부할 때 스프링 부트에서는 엔티티 매니저를 주입받을 때 @PersistenceContext 와 @Autowired 모두 사용가능하다고 하였다.
즉, 엔티티 매니저 또한 생성자 주입을 통한 인젝션이 가능하다.
~/repository/MemberRepository.java
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em; //생성자 주입 (롬복)
...
로 코드 변경이 가능하다!
@RunWith(SpringRunner.class) //JUnit에게 스프링과 관련된 걸로 테스트 한다는 것을 알림
@SpringBootTest //스프링부트로 테스트 돌림
@Transactional //테스트 후 디비 롤백
public class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
//given
Member member = new Member();
member.setName("kim");
//when
Long saveId = memberService.join(member);
//then
assertEquals(member, memberRepository.findOne(saveId));
}
...

@Transactional 어노테이션은 트랜잭션 커밋을 하지 않고 롤백을 하기때문에 테스트가 실행되더라도 DB에 insert 되지 않는다.@Rollback(false) 를 삽입한다.테스트 오류
테스트 코드 실행 중 위와 같은 오류가 발생하였다.해결 방법 : 그레이들 프로젝트 테스트 실행을 IntelliJ로 변경한다.
참고 : https://stackoverflow.com/questions/55405441/intelij-2019-1-update-breaks-junit-tests
@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("예외가 발생해야 한다."); //Assert 메서드
}
중복회원예외 테스트의 경우 중복 이름을 가진 회원을 회원가입시켰을때,
예외가 발생하는 것을 확인해야 한다.
개선 방법 : @Test(expected = IllegalStateException.class)
Assert의 fail() 메서드
- 테스트가 특정 코드 경로를 실행해서는 안 되는 경우 사용
- 코드가 특정 지점에 도달했을 때, 테스트를 실패시키기 위해 명시적으로 fail() 메서드를 호출

테스트 성공!