본 가이드는 fw spring JPA Specification 핵심 가이드 및 실전 아키텍처의 핵심 개념, 실전 구성 명령어 및 아키텍처 베스트 프랙티스를 다룹니다. 클라우드 네이티브 및 인프라 엔지니어링 실무에 즉시 활용 가능하도록 체계적으로 정리했습니다.
JPA Creteria 사용시 유지보수가 힘들어질 것을 대비하여 자주사용될 구문을 static method로 준비함. (static method 제네릭 문법으로 정의)
/** 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
#### Specification 사용법
##### 개요
> 💡
드림마크원 감사로그 다중검색기능 추가시 WHERE절을 동적으로 컨트롤 하기 위하여 분석된 기능
JPA 추가 인자값.
1. Pageable : Order By, Limit Offset 등의 페이징처리 정보 객체
2. Specification : and, or로 추가된 WHERE 조건구성 정보 객체
##### Repository extends
public interface AuditLogRepository extends JpaRepository<AuditLog, Long>, JpaSpecificationExecutor<AuditLog> {}
// public interface jpaRepository enxtends JpaRepository<{ Entity }, Long>, JpaSpecificationExecutor<{{ Entity }}>
위와같이 repository를 선언할 때 JpaRepository 뿐만이 아닌 JpaSpecificationExecutor도 상속한다.
→ 결과적으로 findAll에 첫번째 인자값으로 Specification\<\{ Entity \}>를 사용할 수 있게 된다.
→ ex) findAll(Specification); findAll(Specification, Pageable);
##### 조건 정의
– 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. Specification<{ Entity }>로 리턴하는 값은 항상 return (root, query, criteriaBuilder) -> { in / like / equal / less / greater / ... }를 이용할 수 있음
1. 비교할 value가 List인 경우 root.get(”colName”).method(valueList)
2. 비교할 value가 String인 경우 criteriaBuilder.like(root.get(”colName”), value)
2. 기본 로직은 다음과 같음
// 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. 위에서 사용한 컬럼명 “colName”자리의 String value는 Entity 내 비교대상의 변수명과 일치해야 한다.
Leave a Reply