JPA는 기본적으로 하나의 트랜잭션 안에서 기능을 수행하기 때문에 다음과 같이 트랜잭션을 선언해줘야 한다.
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("Factory");
// db connection
EntityManager manager = factory.createEntityManager();
EntityTransaction transaction = manager.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
Member member = new Member();
member.setUsername("Steven Gerrard");
manager.persist(member); // db 저장
Member findMember = manager.find(Member.class, member.getId()); // select 쿼리
System.out.println(findTeam.getName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
manager.close();
factory.close();
}
}
위와 같은 순수한 JPA의 형태를 이용한다면 복잡한 코드를 보게 될 것이다. 이런 무서운 코드들에게 쓸 신경을 다른 곳에서 쓰기 위해 Spring Data JPA가 등장했다.
Spring에서는 Repository를 구현하여 특정 기능을 선언해주고 사용한다.
우리가 CRUD를 수행하여 실제 데이터베이스에 접근하기 위해서는 데이터 접근 계층인 Repository를 만들어야 한다.
위에서는 member의 기본 키인 id를 조회를 하므로 em.find를 사용할 수 있는데, 사용자 이름으로 조회한다고 하면 우리는 새로운 메서드를 만들어야 한다.
@Repository
public class MemberRepository {
@PersistenceContext;
EntityManager em;
public void save(Member member) {
em.persist(member);
}
public void findByName(String name) {
return em.createQuery("SELECT m FROM Member m WHERE m.name = :name", Member.class)
.setParameter("name", name)
.getResult();
}
}
그럼 중복 회원 검사가 추가되려먼 어떻게 해야할까?
@Repository
public class MemberRepository {
@PersistenceContext;
EntityManager em;
public void save(Member member) {
em.persist(member);
}
public void findByUsername(String name) {
return em.createQuery("SELECT m FROM Member m WHERE m.name = :name", Member.class)
.setParameter("name", name)
.getResult();
}
public void validateUsername(Member member) {
List<Member> findMembers = memberRepository.findByUsername(member.getUsername());
if(!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원");
}
}
}
이러한 형태가 된다.
물론 JpaRepository를 사용하더라도 특수한 기능을 위해서 추가적인 작업을 해야 하니 메서드가 더 많아질 것인데, 위의 코드는 어쩌면 가독성이 안 좋아 보이기도 한다.
이는 JPA가 Spring에 종속적이지 않는 형태를 가지고 있기 때문이다.
그래서 앞서 본 코드들에넌 Spring과 JAP 사이의 차이가 존재하는데, 이를 해결하려고 등장한 것이 Spring Data JPA
이다.
Spring Data JPA는 간단한 CRUD 기능을 공통으로 처리하는 인터페이스를 제공한다.
이를 JPA 공통 인터페이스라고 한다.
public interface UserRepository extends JpaRepository<User, Long>{
}
findByUsername, save, update 등과 같이 간단하지만 단순 반복 작업들을 Spring Data JPA 구현체인 Hibernate가 애플리케이션 실행 시점에 동적으로 자주 사용되는 쿼리 집합을 만들어 우리의 UserRepository 인터페이스를 구현해준다.
그럼 우리가 자주 사용하는 CRUD를 굳이 JPQL로 작성하지 않더라도 인터페이스 하나만 상속받으면 사용할 수 있게 된다.
JpaRepository 인터페이스를 상속 받으면 어떤 기능들을 편하게 사용할 수 있을까?
다음과 같은 기능들이 존재한다.
이외에도 더 많은 기능들이 존재하는데, JpaRepository 는 아래와 같이 다양한 기능이 정의된 인터페이스를 상속 받게 되는데, 이 인터페이스들 덕분에 우리는 단순 CRUD 외에 다양한 기능(Paging, Sorting)을 수행할 수 있다.
JpaRepository 인터페이스의 계층 구조
JpaRepository에는 제네릭으로 타입을 지정할 수 있는데, 순서는 다음과 같다.
public interface UserRepository extends JpaRepository<User, Long>{
}
UserRepository 를 우리는 User 엔티티에 실제로 접근하려고 하니 User과 User 테이블, User 엔티티의 PK 데이터 타입인 Long을 넣어준 것이다.