부트캠프 50일차
Spring Boot
Spring Data JPA
@Query
Spring Data JPA에서 기본으로 제공해주는 메서드나 메서드 이름으로 만드는 쿼리로 모든 쿼리를 다 커버할 수는 없다.
복잡하고 정교한 쿼리를 짜기 위해서는 직접 쿼리를 작성할 필요가 있는데 그럴 때 사용하는 어노테이션이 @Query
어노테이션이다.
@Query
어노테이션의 값으로는 JPQL이라는 문법이 들어가는데, @Query
어노테이션의 속성들을 보니 Native Query로도 작성할 수 있는 것 같다.
JPQL
JPQL은 JPA에서 사용하는 쿼리 문법이다. JPQL은 엔티티를 기준으로 탐색한다.
- 파라미터 바인딩
- ?1, ?2, … : 매개변수 위치를 기반으로 지정
- @Param : 매개변수 이름을 기반으로 지정
Example
@Query("SELECT p FROM Product p Where p.name = ?1") List<Product> findByNameParameterBinding(String name);
@Query("SELECT p FROM Product p Where p.name = :name") List<Product> findByNameParameterBindingWithParam(@Param("name") String name);
Querydsl
Querydsl은 문자열로 쿼리를 작성하는 대신 코드로 작성할 수 있게 도와주는 프레임워크이다.
장점
- IDE가 제공하는 코드 자동 완성 기능을 사용할 수 있다.
- 문법적으로 잘못된 쿼리를 허용하지 않는다. 즉, 정상적으로 활용된 Querydsl은 문법 오류를 발생시키지 않는다.
- 동적인 쿼리를 쉽게 짤 수 있다.
- 코드로 작성하므로 가독성 및 생산성이 향상된다.
- 도메인 타입과 프로퍼티를 안전하게 참조할 수 있다.
Querydsl은 target 폴더에 도메인과 동일한 Q 타입의 클래스를 생성해서 사용한다.
사용 예시
List<Product> productList = jpaQueryFactory
.selectFrom(qProduct) // select() 메서드와 from() 메서드로 분리할 수 있다.
.where(qProduct.name.eq("pen"))
.orderBy(qProduct.price.desc())
.fetch();
반환 메서드
List<T> fetch()
: 조회 결과를 리스트로 반환T fetchOne()
: 단 건의 조회 결과를 반환, 조회 결과가 없으면 null을 반환하며 여러 개 조회될 경우 NonUniqueResultException 발생T fetchFirst()
: 첫번째 조회 결과를 반환, 조회 결과가 없으면 null을 반환Long fetchCount()
: 조회 결과의 개수를 반환QueryResults<T> fetchResults()
: 조회 결과 리스트와 개수를 포함한 QueryResults 객체를 반환
Spring Data JPA
Spring Data JPA에서는 Querydsl을 편리하게 사용할 수 있도록 지원한다.
-
QuerydslPredicateExecutor
public interface QProductRepository extends JpaRepository<Product, Long>, QuerydslPredicateExecutor<Product> { }
repository 인터페이스가 QuerydslPredicateExecutor 인터페이스를 상속 받아 사용 가능 : Predicate 표현식을 매개변수로 사용할 수 있다.
-
QuerydslRepositorySupport
- QuerydslRepositorySupport 클래스를 상속 받음
- 생성자에 있는 매개변수를 지우고 super()에 엔티티 클래스를 넘겨야함
- 스프링 빈으로 등록해서 사용
@Component public class ProductRepositoryCustomImpl extends QuerydslRepositorySupport implements ProductRepositoryCustom { public ProductRepositoryCustomImpl() { super(Product.class); } @Override public List<Product> findByName(String name) { QProduct qProduct = QProduct.product; return from(qProduct) .where(qProduct.name.eq(name)) .fetch(); } }
JPA에서 제공하는 쿼리 대신 내가 직접 구현해서 사용하고 싶다면?!
- 내가 구현하고 싶은 메서드가 정의된 인터페이스 생성
- 해당 인터페이스를 구현하는 클래스 생성 (이름은 인터페이스명 + Impl)
- 사용 중인 Repository가 내가 정의한 인터페이스를 상속 받도록 하면 끝!
JPA Auditing
엔티티 클래스에 공통적으로 들어가는 필드가 있는 경우가 있다. 가령, ‘생성 일자’, ‘생성한 사람’, ‘변경 일자’, ‘변경한 사람’ 등이 있을 수 있는데, 이런 필드들은 엔티티가 여러 개일 경우 중복될 뿐 아니라 객체를 생성하거나 변경할 때마다 계속 값을 입력해야 하는 번거로움이 있다.
이 문제를 해결하기 위해 Spring Data JPA는 JPA Auditing이라는 기능을 제공한다.
기능 활성화
- Application 클래스에
@EnableJpaAuditing
어노테이션을 추가 - Configuration 클래스를 별도로 분리해서
@EnableJpaAuditing
어노테이션을 추가
부모 클래스 만들기
필드의 중복을 제거하기 위해서 자바에서는 보통 상속의 개념을 사용한다. JPA Auditing 기능에서도 똑같이 상속을 사용해서 중복을 제거할 수 있다.
- 부모 클래스에는
@MappedSuperclass
어노테이션을 붙여줘야함 - @EntityListeners 어노테이션을 이용해서 AuditingEntityListener 클래스가 제공하는 시간 세팅 로직을 적용
- @CreatedDate, @LastModifiedDate 어노테이션으로 생성, 수정 시간 필드로 설정
BaseEntity
@Getter
@Setter
@ToString
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
Product
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@Entity
public class Product extends BaseEntity {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private int price;
@Column(nullable = false)
private int stock;
}
@ToString
어노테이션의 callSuper 속성true로 설정하면 부모 클래스의 필드를 포함한다.
[참고]
장정우, 스프링 부트 핵심 가이드(스프링 부트를 활용한 애플리케이션 개발 실무)
깃허브 주소 : https://github.com/chocolaggibbiddori/springboot-wikibooks