사용자 도구

사이트 도구


java:jpa:springdatajpa

문서의 이전 판입니다!


Spring Data JPA

기본 Repository Interface

public interface MyEntityRepository extends JpaRepository<MyEntity, KeyClass>, MyEntityRepositoryCustom {
}
  • 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 상속 금지

  • 멀티 모듈 프로젝트에서 BoardRepository가 도메인 모듈에 있을 때, API 모듈에서 이를 상속해서 BoardApiRepository 처럼 만들거나 하는 경우가 발견됨. 절대 상속하지 말 것
  • 이 경우 도메인 모듈과 말단 모듈의 Repository가 둘다 Custom 구현을 필요로 할 경우 제대로 작동안하게 된다.
  • 또한, 메소드 이름 겹칠 때 뭘 선택하게 되는지 불확실한 등 문제가 많다.
  • 꼭 말단 모듈에서 Repository 메소드를 도메인모듈과 격리해서 구현하고자 한다면 상속 없이 별개의 Repository로 따로 만든다.

Pageable 사용 금지

  • Spring Data 에서 Pageable 을 인자로 받는 메소드는 기본적으로 전체 갯수를 세는 쿼리를 추가로 요청한다.
  • 이 기능은 데이터량이 적은 tutorial 프로젝트 수준에서나 쓸모 있다.
  • 대부분의 실무 서비스는 이런 방식의 페이징을 사용하면 시스템이 사용할 수 없을 정도로 큰 부하를 일으킨다.
  • 다른 문제 : page의 시작인 기본이 0인데 문제는 보편적인 서비스의 page 번호는 1로 시작한다. 이를 헷갈려 해서 호출자가 첫번째 페이지를 1로 호출하는 경우가 많다. 설정으로 해결 가능하나 이를 지키지 못하는 경우가 많이 발생한다.

Pageable 대신 Slice 사용

  • SlicePageable 과 달리 전체 갯수를 세지 않는다.
  • 페이징이 필요하다면 되도록 전체 갯수는 한 번만 세고 Slice 를 이용해 페이지 데이터를 가져오게 한다.
  • 다만, Sliceoffset/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

  • AbstractPersistable : 퍼시스턴스 클래스에 기본 ID 필드와 equals/hashcode 생성.

Audit

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 과 함께

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);

참조

java/jpa/springdatajpa.1688969122.txt.gz · 마지막으로 수정됨: 2023/07/10 15:05 저자 kwon37xi