Technical architectural analysis, practical configuration manifests, and deployment instructions for Spring Data JPA Specification: Dynamic.
To prepare for maintenance difficulties when using JPA Creteria, frequently used syntax was prepared as a static method. (static method defined as generic grammar)
/** Replace to Global
1. $package
2. $RepositoryName
3. $EntityName
4. $columnVarName
5. $EntityName2 (for InnerJoin)
6. $entity2VarName (for InnerJoin)
*/
how to use
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
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
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
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.
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
// 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.




Leave a Reply