Spring Data JPA Specification: Dynamic

💡 Spring Data JPA Specification: Dynamic — Executive Summary

Technical architectural analysis, practical configuration manifests, and deployment instructions for Spring Data JPA Specification: Dynamic.

References and Guidelines (NOTE)

To prepare for maintenance difficulties when using JPA Creteria, frequently used syntax was prepared as a static method. (static method defined as generic grammar)

java
/** Replace to Global
1. $package
2. $RepositoryName
3. $EntityName
4. $columnVarName
5. $EntityName2 (for InnerJoin)
6. $entity2VarName (for InnerJoin)
*/
how to use
java
import $package.SpecificationUtil;
import static $package.SampleEnum.SAMPLE;

private final $RepositoryName repo;
/* in Method */

// 초기화: 이렇게 초기화 하면 바로 .and()로 조건 추가할 수 있음.
Specification<$EntityName> spec = (root, query, criteriaBuilder) -> null;

// or조건 사용, NOT IN 사용
List<String> list;
spec = spec.or(SpecificationUtil.notIn("$columnVarName", list));

// and조건 사용, = 사용
String str;
spec = spec.and(SpecificationUtil.equal("$columnVarName", str));

// enum 사용 (repository columln 타입이 enum인 경우)
spec = spec.and(SpecificationUtil.equal("$columnVarName", SAMPLE));

// innerjoin하여 확장된 컬럼에 대한 IN 사용 (repository에 조인되어 있어야 함)
List<String> list;
spec = spec.and(SpecificationUtil.inInnerJoin(
                    $EntityName2.class,
                    "$entity2VarName",
                    "$repoColumnVarName",
                    list
            ));

// Specification을 조건으로 사용하여 검색
List<$EntityName> entities = repo.findAll(spec);
repository Class
java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface $RepositoryName extends JpaRepository<$EntityName, Long>, JpaSpecificationExecutor<$EntityName> {
		// jpa repository
		@Column(name = "VALUE")
    private String $columnVarName;

		@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "JOIN_KEY")
    private $EntityName2 $entity2VarName;
}
static Method (util) Class
java
package cloudPlatform.cmm.jpa;

import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import java.util.Arrays;
import java.util.List;

public static <T> Specification<T> isNull(String colName) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.isNull(root.get(colName));
    }

    public static <T> Specification<T> isNotNull(String colName) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.isNotNull(root.get(colName));
    }

    public static <T> Specification<T> lessThanOrEqualTo(String colName, String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get(colName), value);
    }

    public static <T> Specification<T> greaterThanOrEqualTo(String colName, String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get(colName), value);
    }

    public static <T> Specification<T> lessThan(String colName, String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.lessThan(root.get(colName), value);
    }

    public static <T> Specification<T> greaterThan(String colName, String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get(colName), value);
    }

    public static <T, V> Specification<T> notIn(String colName, List<V> valueList) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.not(root.get(colName).in(valueList));
    }

    public static <T, V> Specification<T> in(String colName, List<V> valueList) {
        return (root, query, criteriaBuilder) -> root.get(colName).in(valueList);
    }

    public static <T, V> Specification<T> like(String colName, V value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get(colName), "%" + value + "%");
    }

    public static <T, V> Specification<T> equal(String colName, V value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(colName), value);
    }

    public static <T, V> Specification<T> notEqual(String colName, V value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.notEqual(root.get(colName), value);
    }

    public static <T1, T2, V> Specification<T1> inInnerJoin(Class<T2> joinEntityClass, String colName, List<V> valueList) {
        return (root, query, criteriaBuilder) -> {
            String joinEntityName = Arrays.stream(root.getJavaType().getDeclaredFields())
                    .filter(field -> field.getType() == joinEntityClass)
                    .map(Field::getName)
                    .findFirst()
                    .orElse(null);
            Join<T1, T2> joinEntity = root.join(joinEntityName, JoinType.INNER);
            return joinEntity.get(colName).in(valueList);
        };
    }

    public static <T1, T2, V> Specification<T1> equalsInnerJoin(Class<T2> joinEntityClass, String colName, V value) {
        return (root, query, criteriaBuilder) -> {
            String joinEntityName = Arrays.stream(root.getJavaType().getDeclaredFields())
                    .filter(field -> field.getType() == joinEntityClass)
                    .map(Field::getName)
                    .findFirst()
                    .orElse(null);
            Join<T1, T2> joinEntity = root.join(joinEntityName, JoinType.INNER);
            return criteriaBuilder.equal(joinEntity.get(colName), value);
        };
    }
}
old

#### How to use Specification

##### Overview

> 💡

Function analyzed to dynamically control the WHERE clause when adding the Dream Mark One audit log multi-search function

JPA additional argument value.

1. Pageable: Paging processing information objects such as Order By, Limit, Offset, etc.
2. Specification: WHERE condition configuration information object added with and, or

##### Repository extends

java
public interface AuditLogRepository extends JpaRepository<AuditLog, Long>, JpaSpecificationExecutor<AuditLog> {}
// public interface jpaRepository enxtends JpaRepository<{ Entity }, Long>, JpaSpecificationExecutor<{{ Entity }}>

When declaring a repository as above, not only JpaRepository but also JpaSpecificationExecutor is inherited.

→ As a result, Specification\<\{ Entity \}> can be used as the first argument to findAll.

→ ex) findAll(Specification); findAll(Specification, Pageable);

##### Condition Definition

– Predefine conditions for inclusion in Specification.

java
package cloudPlatform.cmm.audit.repo.spec;

import cloudPlatform.cmm.audit.model.AuditLog;
import org.springframework.data.jpa.domain.Specification;

import java.util.List;

public class AuditSpecification {
    public static Specification<AuditLog> notIn(String colName, List<String> valueList) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.not(root.get(colName).in(valueList));
    }
    public static Specification<AuditLog> in(String colName, List<String> valueList) {
        return (root, query, criteriaBuilder) -> root.get(colName).in(valueList);
    }
    public static Specification<AuditLog> like(String colName, String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get(colName), "%" + value + "%");
    }
    public static Specification<AuditLog> lessThanOrEqualToCreatDt(String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get("creatDt"), value);
    }
    public static Specification<AuditLog> greaterThanOrEqualToCreatDt(String value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get("creatDt"), value);
    }
    public static Specification<AuditLog> isNotConnSection() {
        return (root, query, criteriaBuilder) -> criteriaBuilder.notLike(root.get("se"), "로그%");
    }
    public static Specification<AuditLog> isConnSection() {
        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("se"), "로그%");
    }
    public static Specification<AuditLog> betweenAndEqualTo(String colName, String from, String to) {
        Specification<AuditLog> rtnSpec = (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get(colName), from);
        rtnSpec.and(((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get(colName), to)));
        return rtnSpec;
    }
}

1. The value returned by Specification<{ Entity }> can always use return (root, query, criteriaBuilder) -> { in / like / equal / less / greater / ... }
1. If the value to compare is a List root.get(”colName”).method(valueList)
2. If the value to compare is a String criteriaBuilder.like(root.get(”colName”), value)
2. The basic logic is as follows

java
// Ex: "TableVo" Entity, "column" variable에 대하여
   // List를 포함하는레코드만 조회한 후 val을 포함하는 문자열만 조회

   Specification<TableVo> mainSpec = (root, query, criteriaBuilder) -> null;
   Specification<TableVo> inSpec = (root, query, criteriaBuilder) -> root.get("column").in(List);
   Specification<TableVo> likeSpec = (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("column"), "%" + val + "%");

   mainSpec.and(inSpec);
   mainSpec.and(likeSpec);

   // Specification만 사용시
   List<TableVo> resultList = repository.findAll(mainSpec);

   // Sort와 함께 사용시 : "column" 기준 오름차순 정렬
   Sort sort = Sort.by("column").ascending(); //.descending();
   List<TableVo> sortingList = repository.findAll(mainSpec, sort);

   // Pageable과 함께 사용시
   Sort sort = Sort.by("column").ascending(); //.descending();
   Pageable pageable = PageRequest.of( { pageNum }, { pageSize }, sort );
   Page<TableVo> pagingList = repository.findAll(mainSpec, pageable);

3. The String value in place of the column name “colName” used above must match the variable name of the comparison target within the Entity.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *