Spring Data JPA
는 스프링 프레임워크 환경에서 JPA
를 편리하게 사용할 수 있도록 만들어주는편리하게 사용할 수 있게 만들어주는 추상화 라이브러리입니다.
Spring Data JPA
를 사용하지 않았을 때 기본적인 CRUD 수행을 위해서는 다음과 같은 Repository
를 작성해주어야했습니다.
@Repository
public class MemberRepository {
@PersistenceContext
EntityManager em;
public void save(Member m) {
em.persist(m);
}
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 void updateName(Long id, String newName) {
Member member = findOne(id);
member.setName(newName);
}
public void delete(Long id) {
em.remove(member);
}
}
위와 같이 기본적이고 공통적인 CRUD는 어떤 Repository를 다루던지 간에 필요로하는 경우가 많습니다.
당연히 이런 공통적인 부분을 매번 작성하는 것은 번거롭기 때문에 Spring Data JPA
는 이런 공통 CRUD를 공통 인터페이스
라는 개념을 통해 제공하여 구현 없이 인터페이스 implements만으로 CRUD를 처리할 수 있도록 만들어줍니다.
Spring Data JPA
를 사용하기 위해서는spring-data-jpa
라이브러리를 추가해야합니다.
그럼 지금부터 Spring Data JPA
가 제공하는 기능들에 대해 알아보도록 하겠습니다.
Spring Data JPA
는 처음 예시로 보여준 것처럼 간단한 CRUD를 공통으로 처리하게 해주는 공통 인터페이스
를 제공하고 있습니다.
공통 인터페이스는 JpaRepository<T, ID> 인터페이스
를 상속하여 사용합니다.
이때 제네릭 T
는 엔티티 클래스를 지정하고 ID
는 엔티티 클래스의 식별자 필드의 타입을 지정하면 됩니다.
@Entity
public class Member {
@Id
private Long id;
}
public interface MemberRepository extends JpaRepository<Member, Long> {}
위와 같이 인터페이스를 상속하기만 하면 공통 CRUD 기능을 사용할 준비가 완료됩니다.
공통 인터페이스에서 제공하는 주요 메소드들은 다음과 같습니다.
메소드 | 설명 |
---|---|
save() | 새로운 엔티티는 INSERT, 존재하는 엔티티는 UPDATE를 수행 |
findOne() | 엔티티 하나를 조회 |
getOne() | 엔티티 하나를 프록시로 조회 |
findAll() | 모든 엔티티를 조회 |
delete() | 엔티티 삭제 |
소개된 메소드들 외의 메소드들은 공식 문서에서 확인하실 수 있습니다.
save()
의 경우 메소드에 전달하는 엔티티에 DB 내에 존재하지 않으면 INSERT를 수행하고 존재하는 경우 UPDATE를 수행한다는 점에 유의해주세요.
당연히 공통 인터페이스에서 제공하는 메소드들만으로는 서비스의 모든 쿼리를 처리할 수 없기 때문에 Spring Data JPA
는 쿼리를 개발할 수 있는 기능을 제공하며 이를 쿼리 메소드
라고 합니다.
Spring Data JPA
의 가장 신기한 기능이라고 볼 수도 있습니다. 인터페이스 내부에 규칙에 맞는 메소드 이름으로 정의하기만 하면 적절한 JPQL 쿼리를 생성해서 실행해줍니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByMemberName(String name);
}
위와 같이 메소드를 정의하고 실행하면 다음 JPQL을 실행합니다.
SELECT m FROM Member m WHERE m.name = ?1 //1번 위치에 파라미터로 전달한 문자열 삽입
메소드 이름으로 쿼리를 생성하고자 할 때 다음과 같은 규칙을 지켜야합니다.
키워드 | 생성되는 JPQL |
---|---|
Distinct | SELECT DISTINCT (SELECT는 예시임) |
And | ... WHERE x.lastname = ?1 AND x.firstname = ?2 |
Or | ... WHERE x.lastname = ?1 OR x.firstname = ?2 |
Is / Equals | ... WHERE x.firstname = ?1 |
Between | ... WHERE x.startdate BETWEEN ?1 AND ?2 |
LessThan | ... WHERE x.age < ?1 |
LessThanEqual | ... WHERE x.age <= ?1 |
GreaterThan | ... WHERE x.age > ?1 |
GreaterThanEqual | ... WHERE x.age >= ?1 |
After | ... WHERE x.startdate > ?1 |
Before | ... WHERE x.startdate < ?1 |
IsNull | ... WHERE x.age IS NULL |
IsNotNull / NotNull | ... WHERE x.age IS NOT NULL |
Like | ... WHERE x.firstname LIKE ?1 |
NotLike | ... WHERE x.firstname NOT LIKE ?1 |
StartingWith / StartsWith | ... WHERE x.firstname LIKE ?1 (prefix% ) |
EndingWith / EndsWith | ... WHERE x.firstname LIKE ?1 (%suffix ) |
Containing / Contains | ... WHERE x.firstname LIKE ?1 (%keyword% ) |
OrderBy | … WHERE x.age = ?1 ORDER BY x.lastname DESC |
Not | ... WHERE x.lastname <> ?1 |
In | ... WHERE x.age IN ?1 |
NotIn | ... WHERE x.age NOT IN ?1 |
True | ... WHERE x.active = TRUE |
False | ... WHERE x.active = FALSE |
IgnoreCase | ... WHERE UPPER(x.firstname) = UPPER(?1) |
메소드 이름으로 지원되는 주요 접두어는 다음과 같습니다.
접두어 | 역할 |
---|---|
find...By | 엔티티(리스트)를 조회 |
get...By , read...By , query...By | 조회 (find와 동일) |
count...By | 개수 반환 (long ) |
exists...By | 존재 여부 반환 (boolean ) |
delete...By | 조건 기반 삭제 (int or void ) |
한 가지 주의할 점은 엔티티의 필드명이 바뀌면 인터페이스 메소드 이름도 함께 변경해주어야 한다는 것 입니다.
메소드 이름으로 JPA NamedQuery
를 호출할 수도 있습니다.
JPA NamedQuery
는 다음 코드와 같이 쿼리에 이름을 부여해서 사용하는 방식을 의미합니다.
@Entity
@NamedQuery(
name = "Member.findByName",
query = "SELECT m FROM Member m WHERE m.name = :name"
)
public class Member {
@Id
private Long id;
private String name;
}
위와 같이 선언된 NamedQuery를 리포지토리에서 메소드 이름을 통해 호출할 수 있습니다.
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByName(@Param("name") String name);
}
@Param
은 이름 기반 파라미터(:
)를 사용할 때 파라미터에 매핑할 인자를 지정합니다.
@Query
를 이용해서 리포지토리에 직접 쿼리를 정의할 수도 있습니다.
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("SELECT m FROM Member m WHERE m.name = :name")
Member findByName(@Param("name") String name);
}
계속해서 예제에 이름 기반 파라미터 바인딩을 사용했는데, 위치 기반 파라미터 바인딩도 사용할 수 있습니다.
다만 이전에 설명했듯이 코드 가독성에선 이름 기반 파라미터 바인딩 방식이 더 좋으므로 이름 기반 파라미터 바인딩을 우선해서 사용하는 것을 권장드립니다.
@Modifying
는 Spring Data JPA
를 사용할 때 @Query
에서 INSERT, UPDATE, DELETE
쿼리를 작성하고 실행할 때 반드시 붙여야하는 어노테이션입니다.
왜냐하면 @Query
는 기본적으로 SELECT
쿼리만 사용할 수 있기 때문입니다. 그래서 데이터를 조작하는 INSERT, UPDATE, DELETE
를 사용하게 된다면 명시적으로 알려줘야 합니다.
또한 데이터 조작 후 트랜잭션을 통해 DB 반영을 해야하므로 @Transactional
도 함께 사용해야합니다. 일반적으로 @Transactional
인터페이스 메소드보다는 서비스 레이어에 붙여서 하나의 작업 단위를 이루도록 만들어줍니다.
보통 Spring Data JPA
를 사용하면 인터페이스만 정의하게 되고 그 구현체는 따로 정의하지 않게 됩니다. 만약 인터페이스에 정의한 메소드를 구현해야하는 경우 필요한 메소드만 구현할 수 있도록하는 기능을 제공 하고 있습니다.
먼저 직접 구현하고자하는 메소드를 가진 인터페이스를 정의합니다.
public interface MyCustomRepository {
public Member findMemberCustom();
}
다음으로 위 인터페이스를 구현한 구현 클래스를 작성합니다. 이때 주의할 점은 Repository 이름 + Impl
이라는 이름으로 구현 클래스를 정의해야 JPA
가 사용자 정의 구현 클래스로 인식하고 동작하게 됩니다.
//반드시 리포지토리 이름 + Impl로 정의
public class MemberRepositoryImpl implements MyCustomRepository {
@Override
public Member findMemberCustom() {}
}
그리고 다음과 같이 리포지토리에서 사용자 정의 리포지토리를 상속받으면 커스텀 리포지토리를 JPA에서 사용할 수 있게 됩니다.
@Repository
public interface MemberRepository
extends JpaRepository<Member, Long>, MyCustomRepository {}