====== Spring Data JPA ====== * [[http://www.springsource.org/spring-data/jpa|Spring Data JPA]] * [[http://docs.spring.io/spring-data/jpa/docs/1.4.2.RELEASE/reference/pdf/spring-data-jpa-reference.pdf|Spring Data JPA Reference PDF 1.4.2.RELEASE]] : 주소에서 버전 부분만 바꿔볼 것 * [[http://java.dzone.com/articles/adding-hibernate-native-sql|Adding Hibernate Native SQL Features into Your Spring Data Repository]] * [[java:jpa:springdatajpa:custom_repository|Spring Data JPA - Custom Repository]] ===== 기본 Repository Interface ===== public interface MyEntityRepository extends JpaRepository, MyEntityRepositoryCustom { } * [[java:querydsl|QueryDSL]]을 사용한다고 해서 굳이 ''QueryDslPredicateExecutor''를 추가하지 않아도 된다. Custom 에서 ''QueryDslJpaRepository''를 구현해서 직접 사용해도 된다. * ''JpaRepository'', ''QueryDslJpaRepository'', ''JpaSpecificationExecutor'' 등을 조합하고자 할 경우에 이것들을 통합 상속하는 인터페이스를 만들고 ''JpaRepository'' 대신 이를 extends 해도 된다. ===== Repository 구현 ===== * Repository는 그냥 구현하지 말고 각 Persistence Unit 단위로 abstract 클래스를 만들고 상속해서 사용하길 권한다. @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 사용 금지 ===== * [[https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Pageable.html|Pageable]] * [[https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/PageableDefault.html|@PageableDefault]] * [[https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/config/PageableHandlerMethodArgumentResolverCustomizer.html|PageableHandlerMethodArgumentResolverCustomizer]] : 기본 설정 override * Spring Data 에서 ''Pageable'' 을 인자로 받는 메소드는 기본적으로 **전체 갯수를 세는 쿼리를 추가로 요청**한다. * 이 기능은 데이터량이 적은 tutorial 프로젝트 수준에서나 쓸모 있다. * 대부분의 실무 서비스는 이런 방식의 페이징을 사용하면 시스템이 사용할 수 없을 정도로 큰 부하를 일으킨다. * 다른 문제 : page의 시작인 기본이 0인데 문제는 보편적인 서비스의 page 번호는 1로 시작한다. 이를 헷갈려 해서 호출자가 첫번째 페이지를 1로 호출하는 경우가 많다. 설정으로 해결 가능하나 이를 지키지 못하는 경우가 많이 발생한다. * [[database:seek_method|seek method]] 참조. ===== Pageable 대신 Slice 사용 ===== * [[https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Slice.html|Slice]] 는 ''Pageable'' 과 달리 전체 갯수를 세지 않는다. * 페이징이 필요하다면 되도록 전체 갯수는 한 번만 세고 ''Slice'' 를 이용해 페이지 데이터를 가져오게 한다. * 다만, ''Slice'' 도 ''offset/limit'' 방식으로 성능이 떨어지는 것은 동일하다. ===== Native Query ===== @Query(value = "select user_id, user_name from users ", nativeQuery = true) List findUSBCandidates(); * Native Query는 Repository에서 ''@Query(value="쿼리문", nativeQuery = true)''로 수행한다. * 컬럼이 하나 혹은 엔티티 객체일 경우에는 리턴값을 해당 타입 혹은 해당 타입의 리스트로 지정한다. * 다중 컬럼일 경우에는 ''Object []''로 받아서 컬럼 지정 순서대로 값을 뽑아서 사용한다. ===== Projection ===== * 기본적으로 ''@Query'' 결과를 Projection 하려면 Value Object의 필드 이름과 동일하게 alias 하여 컬럼을 select 한다. * [[https://grokonez.com/spring-framework/spring-data/query-alter-domain-model-spring-jpa-projection-springboot-mysql-database|How to query alter domain models by Spring JPA Projection - SpringBoot + MySQL database - grokonez]] ===== Domain Support ===== * [[http://docs.spring.io/spring-data/jpa/docs/1.4.2.RELEASE/api/org/springframework/data/jpa/domain/AbstractPersistable.html|AbstractPersistable]] : 퍼시스턴스 클래스에 기본 ID 필드와 equals/hashcode 생성. ===== Audit ===== * [[http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-auditing-part-one/|Spring Data JPA Tutorial: Auditing, Part One]] * [[http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-auditing-part-two/|Spring Data JPA Tutorial: Auditing, Part Two]] ===== QueryDslRepositorySupport ===== * RepositoryCustomImpl을 만들 때 ''QueryDslRepositorySupport''을 상속하면 QueryDSL 사용이 가능해진다. * Spring 5/SpringBoot 2 에서 ''QuerydslRepositorySupport'' 로 이름 변경 ==== Spock QueryDslRepositorySupport Mock Test ==== * ''QueryDslRepositorySupport'' 상속 객체를 ''Spy()'' 로 만들고 ''repositoryImpl.from(_) >> jpqlQuery'' 로 한 뒤에 ''jpqlQuery''에 대해 행위를 정의하면 된다. ==== IntelliJ Spring Data Jpa Repository 자동생성 ==== * **File and Code Templates**에서 다음 두가지를 ''java'' extension으로 추가한다. * **Reformat according to style**을 지정한다. === 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 과 함께 ===== * [[https://github.com/spring-projects/spring-data-examples/tree/master/jpa/java8|spring-data-examples/jpa/java8 at master · spring-projects/spring-data-examples]] * [[https://www.baeldung.com/spring-data-java-8|Spring Data Java 8]] * ''Optional'', ''CompletableFuture'', ''Stream'' 등 지원 * ''CompletableFuture'', ''Stream'' 사용시에 **Transaction Boundary** 조심할 것. interface CustomerRepository extends Repository { @Async CompletableFuture> readAllBy(); @Query("select c from Customer c") Stream streamAllCustomers(); // CRUD method using Optional Optional findOne(Long id); // Query method using Optional Optional findByLastname(String lastname); } ===== Exists ===== * [[https://www.baeldung.com/spring-data-exists-query|The Exists Query in Spring Data]] * ''existsByXXX'' 형태의 메소드로 가능. * ''Repository.exists(example)'' 가능 ===== Query 인자로 들어온 null 무시하기 ===== * [[https://www.baeldung.com/spring-data-jpa-null-parameters|Spring Data JPA and Null Parameters]] * ''? is null or 비교'' 를 붙이면 된다. (? is null or column = ?) ===== Projection ===== * [[https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections|Spring Data JPA - Reference Documentation]] * [[https://www.baeldung.com/spring-data-jpa-projections|Spring Data JPA Projection]] * interface 기반으로 projection 가능. ===== @Query에 SpEL 지원 ===== * [[https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions|SpEL support in Spring Data JPA @Query definitions]] @Query("select u from User u where u.age = ?#{[0]}") List findUsersByAge(int age); @Query("select u from User u where u.firstname = :#{#customer.firstname}") List findUsersByCustomersFirstname(@Param("customer") Customer customer); ===== 참조 ===== * [[http://www.petrikainulainen.net/spring-data-jpa-tutorial/|Spring DATA JPA Tutorial]] * [[http://www.baeldung.com/spring-data-jpa-query|Spring Data JPA @Query | Baeldung]] * [[http://www.baeldung.com/spring-data-annotations|Spring Data Annotations | Baeldung]] * [[https://www.baeldung.com/spring-data-query-by-example|Spring Data JPA Query by Example | Baeldung]] * [[https://www.baeldung.com/spring-data-jpa-repository-populators|Spring Data JPA Repository Populators | Baeldung]] : 실행시점 데이터 생성기 * https://github.com/spring-projects/spring-data-examples * [[https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/|Pageable을 이용한 Pagination을 처리하는 다양한 방법]] * [[https://www.baeldung.com/spring-rest-api-query-search-language-tutorial|Building a REST Query Language | Baeldung]]