Spring Data JPA
기본 Repository Interface
public interface MyEntityRepository extends JpaRepository<MyEntity, KeyClass>, MyEntityRepositoryCustom {
}
Repository 구현
@Transactional(value = "transactionManager", readOnly = false)
public abstract class MyDomainRepositorySupport extends QueryDslRepositorySupport {
// JdbcTemplate, jooq 등을 injection할 수도 있다.
public MyDomainRepositorySupport(Class<?> domainClass) {
super(domainClass);
}
@PersistenceContext(unitName = "persistenceUnitName")
@Override
public void setEntityManager(EntityManager entityManager) {
super.setEntityManager(entityManager);
}
}
모든 Repository 에는 @Transactional 붙이기
Spring Data JPA 뿐만 아니라 모든 Repository 에는 명시적으로 @Transactional
을 붙여야 한다.
그렇지 않으면 Repository 에 추가된 메소드에는 트랜잭션이 걸리지 않는다.
트랜잭션 없이 DB 작업이 수행되면 다양한 문제를 겪을 수 있다.
특히 JDBC DataSource 에서 autocommit=false
가 기본으로 하고 작업시마다 autocommit=false|true
토글을 하지 않고 hibernate.connection.provider_disables_autocommit: true
인 경우에 문제가 된다.
Spring 은 JPA EntityManager 의 경우 DB 관련 작업이 시작되면 EM 을 생성하고 커밋/롤백시에 정리작업을 하는데, EM을 생성하는 것은 트랜잭션이 없어도 생성한다. 따라서 커밋/롤백이 없으면 정리가 안되어 EntityManager 가 계속 남아서 존재하는 현상이 발생하고 이는 일종의 leak 이며, DB쪽에서 커넥션을 끊을 경우 EM에서 오류가 발생하는 현상이 생긴다.
모든 Repository 에는 @Transactional
을 붙여야한다.
Repository 상속 금지
멀티 모듈 프로젝트에서
BoardRepository
가 도메인 모듈에 있을 때,
API 모듈에서 이를 상속해서
BoardApiRepository
처럼 만들거나 하는 경우가 발견됨.
절대 상속하지 말 것
이 경우 도메인 모듈과 말단 모듈의 Repository가 둘다 Custom 구현을 필요로 할 경우 제대로 작동안하게 된다.
또한, 메소드 이름 겹칠 때 뭘 선택하게 되는지 불확실한 등 문제가 많다.
꼭 말단 모듈에서 Repository 메소드를 도메인모듈과 격리해서 구현하고자 한다면 상속 없이 별개의 Repository로 따로 만든다.
Pageable 사용 금지
-
-
-
Spring Data 에서 Pageable
을 인자로 받는 메소드는 기본적으로 전체 갯수를 세는 쿼리를 추가로 요청한다.
이 기능은 데이터량이 적은 tutorial 프로젝트 수준에서나 쓸모 있다.
대부분의 실무 서비스는 이런 방식의 페이징을 사용하면 시스템이 사용할 수 없을 정도로 큰 부하를 일으킨다.
다른 문제 : page의 시작인 기본이 0인데 문제는 보편적인 서비스의 page 번호는 1로 시작한다. 이를 헷갈려 해서 호출자가 첫번째 페이지를 1로 호출하는 경우가 많다. 설정으로 해결 가능하나 이를 지키지 못하는 경우가 많이 발생한다.
-
Pageable 대신 Slice 사용
Slice 는
Pageable
과 달리 전체 갯수를 세지 않는다.
페이징이 필요하다면 되도록 전체 갯수는 한 번만 세고 Slice
를 이용해 페이지 데이터를 가져오게 한다.
다만, Slice
도 offset/limit
방식으로 성능이 떨어지는 것은 동일하다.
Native Query
@Query(value = "select user_id, user_name from users ", nativeQuery = true)
List<Object[]> findUSBCandidates();
Native Query는 Repository에서 @Query(value=“쿼리문”, nativeQuery = true)
로 수행한다.
컬럼이 하나 혹은 엔티티 객체일 경우에는 리턴값을 해당 타입 혹은 해당 타입의 리스트로 지정한다.
다중 컬럼일 경우에는 Object []
로 받아서 컬럼 지정 순서대로 값을 뽑아서 사용한다.
Projection
Domain Support
Audit
QueryDslRepositorySupport
Spock QueryDslRepositorySupport Mock Test
IntelliJ Spring Data Jpa Repository 자동생성
Spring Data JPA Repository
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
#parse("File Header.java")
@Repository
public interface ${EntityClass}Repository extends JpaRepository<${EntityClass}, ${EntityKey}>, QuerydslPredicateExecutor<${EntityClass}>, ${EntityClass}RepositoryCustom {
}
Spring Data JPA Repository Custom Impl
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import org.springframework.data.jpa.repository.support.QueryDslRepositorySupport;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class ${EntityClass}RepositoryImpl extends QuerydslRepositorySupport implements ${EntityClass}RepositoryCustom {
public ${EntityClass}RepositoryImpl() {
super(${EntityClass}.class);
}
@PersistenceContext
@Override
public void setEntityManager(EntityManager entityManager) {
super.setEntityManager(entityManager);
}
}
Java 8 과 함께
-
-
Optional
, CompletableFuture
, Stream
등 지원
CompletableFuture
, Stream
사용시에 Transaction Boundary 조심할 것.
interface CustomerRepository extends Repository<Customer, Long> {
@Async
CompletableFuture<List<Customer>> readAllBy();
@Query("select c from Customer c")
Stream<Customer> streamAllCustomers();
// CRUD method using Optional
Optional<Customer> findOne(Long id);
// Query method using Optional
Optional<Customer> findByLastname(String lastname);
}
Exists
Query 인자로 들어온 null 무시하기
(? IS NULL OR COLUMN = ?)
Projection
@Query에 SpEL 지원
@Query("select u from User u where u.age = ?#{[0]}")
List<User> findUsersByAge(int age);
@Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(@Param("customer") Customer customer);
참조