1 Spring Data JPA
- Spring Data JPA는 데이터 액세스 계층 구현에 필요한 반복적인 코드를 크게 줄여주는 프레임워크입니다.
- JPA 기반의 Repository를 편리하게 생성할 수 있도록 지원합니다.
- 기본적인 CRUD와 페이징 기능을 인터페이스만으로도 구현할 수 있습니다.
- 개발자는 JpaRepository를 상속받는 인터페이스만 작성하면 됩니다.
- 구현체는 Spring Data JPA가 자동으로 생성해서 주입해줍니다.
스프링 데이터 JPA가 구현 클래스 대신 생성
- JpaRepository를 상속받는 인터페이스 ItemRepository의 구현 클래스를 Spring Data JPA가 생성해줍니다.
2 인터페이스 구성
- Spring Data JPA는 다양한 리포지토리 인터페이스를 제공합니다.
- 각 인터페이스는 특정 기능에 중점을 둔 메서드들을 제공합니다.
2.1 JpaRepository 인터페이스
- 가장 일반적으로 사용되는 기본 리포지토리 인터페이스입니다.
- 엔티티 클래스와 ID 타입을 타입 파라미터로 지정합니다.
- 예)
<Member, Long>
- 예)
public interface MemberRepository extends JpaRepository<Member, Long> {
}
사용 예시
@SpringBootTest
@Transactional
public class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Test
public void testMember() {
Member member = new Member("memberA");
Member savedMember = memberRepository.save(member);
Member findMember = memberRepository.findById(savedMember.getId()).get();
Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
Assertions.assertThat(findMember).isEqualTo(member);
}
}
- MemberRepository는 인터페이스입니다.
memberRepository.save()
라는 메소드를 구현한적이 없지만 사용이 가능합니다.- 이는 Spring Data JPA가 MemberRepository를 구현한 구현체를 주입받기 때문입니다.
- 이처럼 기본적인 CRUD는 직접 구현하지 않아도 됩니다.
2.2 CrudRepository 인터페이스
- 기본적인 CRUD 기능을 제공하는 인터페이스입니다.
- 주요 메서드는 다음과 같습니다:
- save(): 엔티티 저장 및 수정
- findById(): ID로 엔티티 조회
- findAll(): 모든 엔티티 조회
- delete(): 엔티티 삭제
- count(): 엔티티 개수 조회
- existsById(): ID로 엔티티 존재 여부 확인
public interface CrudRepository<T, ID> extends Repository<T, ID> {
// 주어진 엔티티 저장하기
<S extends T> S save(S entity);
// 주어진 ID로 엔티티를 찾아 반환
Optional<T> findById(ID primaryKey);
// 모든 엔티티를 반환
Iterable<T> findAll();
// 엔티티의 수를 반환
long count();
// 주어진 엔티티를 삭제
void delete(T entity);
// 주어진 ID를 가지는 엔티티의 존재 유무 반환
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
2.3 PagingAndSortingRepository 인터페이스
- 페이징과 정렬 기능을 제공하는 인터페이스입니다.
- 다음과 같은 메서드를 제공합니다:
- findAll(Pageable): 페이징 처리된 데이터 조회
- findAll(Sort): 정렬된 데이터 조회
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
사용 예시
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
3 Query Methods
- Spring Data JPA는 메서드 이름만으로 쿼리를 생성하는 강력한 기능을 제공합니다.
- 복잡한 쿼리가 필요한 경우 JPA Named Query나 @Query 어노테이션을 사용할 수 있습니다.
쿼리 메소드의 3가지 기능
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA Named Query 호출
- @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의
3.1 메소드 이름으로 쿼리 생성
public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
- 메소드 이름으로
select u from User u where u.emailAddress = ?1 and u.lastname = ?2
라고 query를 생성해줍니다다. - 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 합니다.
- 그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생합니다.
Keyword | Sample | JPQL snippet |
---|---|---|
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is , Equals | findByFirstname ,findByFirstnameIs ,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull , Null | findByAge(Is)Null | … where x.age is null |
IsNotNull , NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended % ) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended % ) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in % ) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
3.2 메소드 이름으로 JPA Named Query 호출
- 실무에서 거의 사용하는 일이 없습니다.
- 애플리케이션 실행 시점에 문법 오류를 발견할 수 있습니다.
Annotation 베이스로 Named Query 설정하기
@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
Interface 선언하기
- 메소드 이름으로 쿼리를 생성하는 것이 아니라 위에서 설정된 Named Query가 실행됩니다.
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmailAddress(String emailAddress);
}
3.3 @Query를 사용해 직접 쿼리 생성
@org.springframework.data.jpa.repository.Query
어노테이션을 사용합니다.- 실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있습니다.
- 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음
- 실무에서는 메소드 이름으로 쿼리 생성 기능은 파라미터가 증가하면 메서드 이름이 매우 지저분해집니다.
- 간단한 경우 메소드 이름으로 쿼리를 생성하고 복잡해 지면 @Query 기능을 사용하는 것이 좋습니다.
엔티티 조회
@Query("select m from Member m where m.username= :username and m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);
값 조회
@Query("select m.username from Member m")
List<String> findUsernameList();
DTO 직접 조회
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) " +
"from Member m join m.team t")
List<MemberDto> findMemberDto();
파라미터 바인딩
@Query("select m from Member m where m.username = :name")
Member findMembers(@Param("name") String username);
컬렌션 파라미터 바인딩
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);