이번 포스팅은 김영한 이사님의 스프링 부트 JPA 활용 1편을 듣고 제작되었습니다.
이번엔 회원Repository를 먼저 개발해보고자 한다. 구현 기능은 앞서 얘기했듯 회원 등록 및
회원 목록 조회 2가지이다.
참고
앞으로
id는 대부분 엔티티의id를 의미하게 되며 이것은 즉, 테이블의pk를 의미하므로 같은 의미로 사용을 할 것이다.
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name="member_id")
private Long id;
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
앞서 구현한 회원 도메인은 위와 같다.
@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);
}
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();
}
}
@Repository 어노테이션을 사용EntityManager를 그대로 주입받기 위해 @PersistenceContext 어노테이션을 사용save에서 em.persist를 하면 영속성 컨텍스트에 엔티티 넣고 Transaction이 Commit되는 시점에 DB에 반영됨.findOne에서 em.find에 타입과 id 를 넘겨주면 단건 조회가 가능함.findAll에서 em.createQuery를 통해 JPQL을 사용, FROM의 대상이 테이블이 아닌 엔티티가 됨.findByName도 JPQL을 사용하여 구현, 이때 WHERE에 파라미터를 인자로 받아 지정해줌.JPQL은 순수 쿼리와 거의 같지만 테이블에 대한 쿼리가 아닌 엔티티 객체에 대한 쿼리를 만듦. 자세한 정보는 이 글을 참고하거나 JPA 기본편 강의를 참고하자.
@Service
@Transactional(readOnly = true)
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) {
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)
}
}
@Service 어노테이션을 사용하여 컴포넌트 스캔의 대상이 되도록 한다.JPA를 사용한 데이터의 변경이나 조회같은 로직은 트랜잭션 안에서 실행되어야 한다. 따라서 @Transaction어노테이션을 붙어주었다.@Transaction의 옵션 중 readOnly = true 옵션을 사용하면 읽기 전용이 된다. 이 옵션을 사용하면 데이터를 조회하는데 최적화된 동작이 이루어진다.readOnly = true옵션을 사용하면 동작을 안하기 때문에 메서드 레벨에서 다르게 지정해주어야한다. 디폴트는 readOnly = false이다.중복 이름 검사의 경우 실제로 동시요청이 이루어지는 상황에선 동작하지 않을 수 있으므로 실무에선 중복 되면 안되는 값에 대해선 DB에서 UNIQUE 제약조건을 사용해주어야 한다.@AutoWired 어노테이션이 없어도 자동으로 스프링 컨테이너에서 가져와준다.id값을 return 해주는 이유??
em.persist를 하는 순간 영속성 컨텍스트에 해당 객체를 올린다. 이때 영속성 컨텍스트는 식별자로@Id어노테이션이 붙은id값을 사용함.
그러므로id값은 테이블에 해당 데이터가 있는 한 항상 보장하기 때문에id값을 돌려주어 값이 있는지를 확인하게 된다.
Lombok을 사용하면 생성자 주입 코드를 없애고 어노테이션만으로 해결 가능하다.
@Service
@Transactional(readOnly = true)
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
.
.
.
}
위의 코드가 흔히 우리가 생성자 주입을 통한 DI방식의 코드이다. 이 코드에 Lombok라이브러리를 적용시키면 생성가 필요 없이 어노테이션만 붙여주면 된다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
.
.
.
}
Lombok의 @RequiredArgsConstructor를 사용하면 final 키워드가 붙은 필드에 대해서 생성자를 만들어준다. 이렇게 되면 별도의 생성자를 클래스 안에 구현할 필요가 없어진다.
마찬가지로 MemberRepository도 변경을 해주자.
@Repository
public class MemberRepository {
//@PersistenceContext
private final EntityManager em;
public MemberRepository(EntityManager em) {
this.em = em;
}
.
.
.
}
EntityManager의 경우 @PersistenceContext를 사용해도 되지만 스프링부트에서 @AutoWired에 대한 생성자 주입도 지원을 한다.
이렇게 변형이 가능하다는 것은 역시 @RequiredArgsConstructors 어노테이션을 통해 간소화시킬 수 있다.
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
.
.
.
}
물론 사람마다 프로그래밍 스타일이 다르긴 하지만 최근엔 실무에서도 이렇게 많이 쓴다고도 하고 실제로 이렇게 쓰면 일관성이 생기기 때문에 아무래도 프로그램 구현시, 혹은 유지 보수시에 편해지게 된다.
이렇게 해서 MemberRepository와 MemberService를 구현을 해보았다. 전에 스프링부트 입문 강의에서 했던 것과 비슷해서 쉽게 이해할 수 있었는데 여기에 코드에 대한 상세한 설명이 있어서 훨씬 이해도 잘되고 알고 가는 것도 많은 것 같다.
서비스의 로직에서 id를 반환해주는 이유라던가 생성자 주입을 Lombok을 통해서 간단하게 줄이는 방법에 대해서는 새로 알게된 사실들이었는데 실제 프로젝트를 진행할 때 많은 도움이 될 수 있을 거라 생각한다.
다음에는 MemberService에 대한 테스트를 진행해볼 예정이다. 비슷한 예제를 진행해서 그런건진 몰라도 확실히 스프링 입문을 처음 정리하면서 들을 때완 달리 습득력도 빨라지고 이해도 잘 되는 것 같다. 정리하면서 듣는 것이 시간은 좀 오래 걸리지만 남는 것이 많아 좋은 것 같다고 생각들었다.