文章目录
使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据
需求概要
看标题可能有一点懵,但这篇文章来源于一个需求,这个需求是这样的:我们有一个表的数据需要根据不同的条件进行抽取,诶呀可能有的人说直接配个SQL就完事了,使用SQL去查就好了,确实以前的做法就是直接配置SQL,但是直接配SQL存在一些SQL注入的风险,而且以后考虑做成UI可配置的话,配置SQL确实太难看了。所以我就想能不能通过配置一个JSON数据,通过这个JSON数据我们就可以查到想要的数据。以后我们还可以通过前台的一些配置,筛选字段和条件来构造出这个JSON数据,做到可视化的配置。然后构造出来的这个JSON就可以获取到对应表的数据了。这个获取数据的方式就很像mongo这种NoSQL的方式,通过json数据来查询数据了。而且注意一点是,我们只是支持单表查询,没有多表查询的需求了。
JSON 结构的设计
那我们首先就要设计一个JSON的结构能够支持我们一些普通的查询工作了。
{
"conditions": [{
"conditions": [],
"operation": null,
"conditionExpression": {
"type": "STRING", //支持不同的类型
"column": "status", //对应实体的字段名
"operateExpression": "=",
"not": false, //如果not为true,则表示不等于
"operateValue": ["success"],
"dateformat": null,
"dateFormatFunction": null
}
}, {
"conditions": [],
"operation": null,
"conditionExpression": {
"type": "NUMBER",
"column": "size",
"operateExpression": "=",
"not": false,
"operateValue": ["40"],
"dateformat": null,
"dateFormatFunction": null
}
}, {
"conditions": [],
"operation": null,
"conditionExpression": {
"type": "STRING",
"column": "time",
"operateExpression": "=",
"not": false,
"operateValue": ["2021"],
"dateformat": "yyyy-MM-DD HH:mm:ss",
"dateFormatFunction": {
"dateFormat": "%Y"
}
}
}],
"operation": "AND", // conditions中的条件都是用AND来拼接
"conditionExpression": null
}
上面这个json出来的查询条件其实就是(status = 'success' and size = '40' and date_format(time,'%Y') = '2021')
可以看到还能够支持日期的一些date_format
方法
json结构出来了,我们就可以开始设计出来DTO对象
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class ConditionDTO implements Serializable {
private static final long serialVersionUID = -5051343103773843259L;
@Builder.Default
private List<ConditionDTO> conditions = new ArrayList<>();
private OperationEnum operation;
private ConditionExpressionDTO conditionExpression;
}
public enum OperationEnum {
AND, OR
}
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class ConditionExpressionDTO implements Serializable {
private static final long serialVersionUID = 7848696546935190452L;
private ColumnType type;
private String column;
@Convert(converter = OperateExpressionEnum.Converter.class)
private OperateExpressionEnum operateExpression;
private boolean not;
@Builder.Default
private List<String> operateValue = new ArrayList<>();
private String dateformat;
private InternalDateFormatFunction dateFormatFunction;
}
@Getter
public enum ColumnType implements ConvertType {
STRING(String.class) {
@Override
public String convert(String value) {
return value;
}
},
BOOLEAN(Boolean.class) {
@Override
public Boolean convert(String value) { //为了把字符串转换成具体的类型
return Boolean.valueOf(value);
}
},
NUMBER(Number.class) {
@Override
public Number convert(String value) {
return new BigDecimal(value);
}
},
DATE(LocalDate.class) {
@Override
public LocalDate convert(String value) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DateTimeUtils.DATE_FORMAT);
return LocalDate.parse(value, formatter);
}
@Override
public LocalDate convert(String value, String format) {
if (StringUtils.isBlank(format)) {
return this.convert(value);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
return LocalDate.parse(value, formatter);
}
},
DATETIME(LocalDateTime.class) {
@Override
public LocalDateTime convert(String value) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DateTimeUtils.DATE_TIME_FORMAT);
return LocalDateTime.parse(value, formatter);
}
@Override
public LocalDateTime convert(String value, String format) {
if (StringUtils.isBlank(format)) {
return this.convert(value);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
return LocalDateTime.parse(value, formatter);
}
};
private final Class<?> type;
ColumnType(Class<?> clazz) {
this.type = clazz;
}
}
public interface ConvertType {
Object convert(String value);
default Object convert(String value, String format) {
return this.convert(value);
}
}
@Getter
public enum OperateExpressionEnum implements BaseEnum<String> {
GT(">", List.of(ColumnType.values())),
GE(">=", List.of(ColumnType.values())),
LT("<", List.of(ColumnType.values())),
LE("<=", List.of(ColumnType.values())),
EQUALS("=", List.of(ColumnType.values())),
EMPTY("empty", List.of(ColumnType.values())),
LIKE("like", List.of(ColumnType.STRING)),
BETWEEN("between", List.of(ColumnType.DATE, ColumnType.DATETIME)),
IN("in", List.of(ColumnType.values()));
@JsonValue
private final String value;
private final List<ColumnType> supportTypes; //每个操作,支持的数据类型
OperateExpressionEnum(String value, List<ColumnType> supportTypes) {
this.value = value;
this.supportTypes = supportTypes;
}
public static class Converter extends BaseEnumConverter<OperateExpressionEnum, String> {
}
public static OperateExpressionEnum getOperateExpressionEnumByValue(String value) {
for (OperateExpressionEnum operateExpressionEnum : OperateExpressionEnum.values()) {
if (StringUtils.equalsIgnoreCase(value, operateExpressionEnum.getValue())) {
return operateExpressionEnum;
}
}
return null;
}
}
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class InternalDateFormatFunction implements Serializable {
private static final long serialVersionUID = 6895278799616731945L;
public static final String FUNCTION_NAME = "date_format";
public static final Class<?> RETURN_TYPE = ColumnType.STRING.getType();
private String dateFormat;
}
使用策略模式执行不同的查询条件
在OperateExpressionEnum
中我们定义了很多不同的查询条件,等于,大于,小于,in, like等等的操作。 所以我们需要根据不同的查询条件分配不同的策略,然后构造出不同的JPA查询条件。
所以我们需要先定义一个策略工厂,然后我们从工厂中获取到具体的策略
@UtilityClass
public class PredicateStrategyFactory {
private static final Map<String, PredicateStrategy> strategies = new ConcurrentHashMap<>();
public static PredicateStrategy getByType(String type) {
return strategies.get(type);
}
public static void register(OperateExpressionEnum operateExpression, ColumnType columnType, PredicateStrategy predicateStrategy) {
Assert.notNull(operateExpression, "Operate expression can't be null");
Assert.notNull(columnType, "Column type can't be null");
strategies.put(String.format("%s-%s", operateExpression.name(), columnType.name()), predicateStrategy);
}
}
然后我们看策略类的接口中有什么功能
public interface PredicateStrategy {
Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition); // 构造出JPA Specification需要的条件,通过这些条件加上and或者or的拼接,我们就可以构造出一个查询条件了,关于JPA Specification的使用不太明白的小伙伴可以先去看看文档
String getConditionContent(ConditionDTO condition); //用于展示类似于SQL的字符串,因为给的是JSON配置,但是我们不知道JSON配的对不对,所以我们会返回一个类似SQL的字符串给用户展示
}
构造查询条件
我们要怎么根据构造出查询条件呢,其实就是递归到最后一层下面没有conditions的条件了,然后把这些conditions条件拼起来,通过上层的operation来拼接conditions中的条件,伪代码如下:
//伪代码
Condition condition = getCondition();
function getPredicate(Root<Entity> root,CriteriaBuilder criteriaBuilder,Condition condition){
if(condition.conditions is empty){ // 为空
// 根据conditionExpression 分发不同的策略,最后返回一个Predicate
return getPredicateByExpression(root,criteriaBuilder,condition)
}else{ //不为空
if(condition.operition == 'AND'){
return criteriaBuilder.and(
condition.conditions.map(c->getPredicate(root,criteriaBuilder,c)).toCollection().toArray(Predicate[]::new)
)
}else if(ondition.operition == 'OR'){
return criteriaBuilder.or(
condition.conditions.map(c->getPredicate(root,criteriaBuilder,c)).toCollection().toArray(Predicate[]::new)
)
}
}
}
主逻辑具体的代码实现
接下来就给出主逻辑的具体实现的代码了
@UtilityClass
public class ConditionUtils {
public static Predicate findByCondition(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
Predicate predicate = ConditionUtils.getPredicate(root, criteriaBuilder, condition);
if (Objects.isNull(predicate)) {
return criteriaBuilder.conjunction();
}
return predicate;
}
private static Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
if (CollectionUtils.isEmpty(condition.getConditions())) {
return getPredicateByExpression(root, criteriaBuilder, condition);
} else {
if (Objects.equals(condition.getOperation(), OperationEnum.AND)) {
return criteriaBuilder.and(
condition.getConditions().stream().map(c -> getPredicate(root, criteriaBuilder, c))
.filter(Objects::nonNull).collect(Collectors.toList()).toArray(Predicate[]::new)
);
} else if (Objects.equals(condition.getOperation(), OperationEnum.OR)) {
return criteriaBuilder.or(
condition.getConditions().stream().map(c -> getPredicate(root, criteriaBuilder, c))
.filter(Objects::nonNull).collect(Collectors.toList()).toArray(Predicate[]::new)
);
}
return null;
}
}
private static Predicate getPredicateByExpression(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
if (Objects.isNull(condition.getConditionExpression())) {
return null;
}
ConditionAssertUtils.isFalse(Objects.isNull(condition.getConditionExpression().getColumn())
|| Objects.isNull(condition.getConditionExpression().getOperateExpression())
|| Objects.isNull(condition.getConditionExpression().getType()), MISSING_CONDITION_DETAIL);
PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(root.getJavaType(), condition.getConditionExpression().getColumn());
ConditionAssertUtils.notNull(propertyDescriptor, String.format(MISSING_COLUMN, condition.getConditionExpression().getColumn()));
PredicateStrategy strategy = PredicateStrategyFactory.getByType(
MessageFormat.format("{0}-{1}",
condition.getConditionExpression().getOperateExpression().name(),
condition.getConditionExpression().getType()));
ConditionAssertUtils.notNull(strategy, String.format(MISSING_STRATEGY,
condition.getConditionExpression().getColumn(), condition.getConditionExpression().getOperateExpression().name(),
condition.getConditionExpression().getType()));
return strategy.getPredicate(root, criteriaBuilder, condition);
}
public static String getConditionContent(ConditionDTO condition) {
if (CollectionUtils.isEmpty(condition.getConditions())) {
return getConditionContentByExpression(condition);
} else {
if (Objects.equals(condition.getOperation(), OperationEnum.AND)) {
return String.format("(%s)", condition.getConditions().stream().map(ConditionUtils::getConditionContent)
.filter(StringUtils::isNotBlank).collect(Collectors.joining(" and ")));
} else if (Objects.equals(condition.getOperation(), OperationEnum.OR)) {
return String.format("(%s)", condition.getConditions().stream().map(ConditionUtils::getConditionContent)
.filter(StringUtils::isNotBlank).collect(Collectors.joining(" or ")));
}
return null;
}
}
private static String getConditionContentByExpression(ConditionDTO condition) {
ConditionAssertUtils.isFalse(Objects.isNull(condition.getConditionExpression())
|| Objects.isNull(condition.getConditionExpression().getColumn())
|| Objects.isNull(condition.getConditionExpression().getOperateExpression())
|| Objects.isNull(condition.getConditionExpression().getType()), MISSING_CONDITION_DETAIL);
PredicateStrategy strategy = PredicateStrategyFactory.getByType(
String.format("%s-%s",
condition.getConditionExpression().getOperateExpression(),
condition.getConditionExpression().getType()));
ConditionAssertUtils.notNull(strategy, String.format(MISSING_STRATEGY,
condition.getConditionExpression().getColumn(), condition.getConditionExpression().getOperateExpression().name(),
condition.getConditionExpression().getType()));
return strategy.getConditionContent(condition);
}
private static Expression<?> getExpression(Root<?> root, CriteriaBuilder cb, ConditionDTO condition) {
String path = condition.getConditionExpression().getColumn();
InternalDateFormatFunction dateFormatFunction = condition.getConditionExpression().getDateFormatFunction();
if (Objects.nonNull(dateFormatFunction) && StringUtils.isNotBlank(dateFormatFunction.getDateFormat())) {
PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(root.getJavaType(), condition.getConditionExpression().getColumn());
ConditionAssertUtils.isTrue(
LocalDate.class.isAssignableFrom(Objects.requireNonNull(propertyDescriptor).getPropertyType())
|| LocalDateTime.class.isAssignableFrom(Objects.requireNonNull(propertyDescriptor).getPropertyType())
, String.format(UN_SUPPORT_DATE_FORMAT_FUNCTION, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.isTrue(Objects.equals(condition.getConditionExpression().getType(), ColumnType.STRING), String.format(
UN_MATCH_COLUMN_TYPE_FOR_DATE_FORMAT_FUNCTION, ColumnType.STRING.name(), condition.getConditionExpression().getColumn()
));
return cb.function(InternalDateFormatFunction.FUNCTION_NAME,
InternalDateFormatFunction.RETURN_TYPE, root.get(path),
cb.literal(dateFormatFunction.getDateFormat()));
}
return root.get(path);
}
public static Predicate getEqualPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO condition) {
Object criteria = ConditionUtils.getValidCondition(condition);
String path = condition.getConditionExpression().getColumn();
if (Objects.isNull(criteria)) {
return cb.isNull(root.get(path));
}
return cb.equal(getExpression(root, cb, condition), criteria);
}
public static Predicate getLikePredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO condition) {
Object criteria = ConditionUtils.getValidCondition(condition);
return cb.like((Expression) getExpression(root, cb, condition), (String) criteria);
}
public static Predicate geInPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO condition) {
CriteriaBuilder.In<Object> in = cb.in(getExpression(root, cb, condition));
condition.getConditionExpression().getOperateValue().stream().map(item -> ConditionUtils.getValidCondition(item, condition))
.forEach(in::value);
return cb.and(in);
}
public static Predicate getBetweenPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO condition) {
String path = condition.getConditionExpression().getColumn();
List<Object> operateList = condition.getConditionExpression().getOperateValue().stream()
.map(item -> ConditionUtils.getValidCondition(item, condition)).collect(Collectors.toList());
return cb.between(root.get(path), (Comparable<Object>) operateList.get(0), (Comparable<Object>) operateList.get(1));
}
public static Predicate getEmptyPredicateCondition(Root<?> root, CriteriaBuilder cb, String path) {
return cb.or(cb.equal(root.get(path), StringUtils.EMPTY), cb.isNull(root.get(path)));
}
public static Predicate getNotEqualPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO conditionDTO) {
Object criteria = ConditionUtils.getValidCondition(conditionDTO);
String path = conditionDTO.getConditionExpression().getColumn();
if (Objects.isNull(criteria)) {
return cb.isNotNull(root.get(path));
}
return cb.or(cb.notEqual(getExpression(root, cb, conditionDTO), criteria), cb.isNull(root.get(path)));
}
private static Predicate getGtPredicateCondition(Root<?> root, CriteriaBuilder cb, String path, Number criteria) {
if (Objects.isNull(criteria)) {
return null;
}
return cb.gt(root.get(path), criteria);
}
private static Predicate getGePredicateCondition(Root<?> root, CriteriaBuilder cb, String path, Number criteria) {
if (Objects.isNull(criteria)) {
return null;
}
return cb.ge(root.get(path), criteria);
}
private static Predicate getLtPredicateCondition(Root<?> root, CriteriaBuilder cb, String path, Number criteria) {
if (Objects.isNull(criteria)) {
return null;
}
return cb.lt(root.get(path), criteria);
}
private static Predicate getLePredicateCondition(Root<?> root, CriteriaBuilder cb, String path, Number criteria) {
if (Objects.isNull(criteria)) {
return null;
}
return cb.le(root.get(path), criteria);
}
public static Predicate getLessThanPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO conditionDTO) {
Object criteria = ConditionUtils.getValidCondition(conditionDTO);
String path = conditionDTO.getConditionExpression().getColumn();
if (Objects.isNull(criteria)) {
return null;
}
if (criteria instanceof Number) {
return ConditionUtils.getLtPredicateCondition(root, cb, path, (Number) criteria);
}
return cb.lessThan((Expression) getExpression(root, cb, conditionDTO), (Comparable<Object>) criteria);
}
public static Predicate getLessThanOrEqualToPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO conditionDTO) {
Object criteria = ConditionUtils.getValidCondition(conditionDTO);
String path = conditionDTO.getConditionExpression().getColumn();
if (Objects.isNull(criteria)) {
return null;
}
if (criteria instanceof Number) {
return ConditionUtils.getLePredicateCondition(root, cb, path, (Number) criteria);
}
return cb.lessThanOrEqualTo((Expression) getExpression(root, cb, conditionDTO), (Comparable<Object>) criteria);
}
public static Predicate getGreaterThanPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO conditionDTO) {
Object criteria = ConditionUtils.getValidCondition(conditionDTO);
String path = conditionDTO.getConditionExpression().getColumn();
if (Objects.isNull(criteria)) {
return null;
}
if (criteria instanceof Number) {
return ConditionUtils.getGtPredicateCondition(root, cb, path, (Number) criteria);
}
return cb.greaterThan((Expression) getExpression(root, cb, conditionDTO), (Comparable<Object>) criteria);
}
public static Predicate getGreaterThanOrEqualToPredicateCondition(Root<?> root, CriteriaBuilder cb, ConditionDTO conditionDTO) {
Object criteria = ConditionUtils.getValidCondition(conditionDTO);
String path = conditionDTO.getConditionExpression().getColumn();
if (Objects.isNull(criteria)) {
return null;
}
if (criteria instanceof Number) {
return ConditionUtils.getGePredicateCondition(root, cb, path, (Number) criteria);
}
return cb.greaterThanOrEqualTo((Expression) getExpression(root, cb, conditionDTO), (Comparable<Object>) criteria);
}
public static Object getValidCondition(ConditionDTO condition) {
if (CollectionUtils.isEmpty(condition.getConditionExpression().getOperateValue())) {
return null;
}
return ConditionAssertUtils.notThrows(conditionDTO -> conditionDTO.getConditionExpression().getType()
.convert(conditionDTO.getConditionExpression().getOperateValue().get(0), conditionDTO.getConditionExpression().getDateformat()), condition,
String.format(UNABLE_CONVERT_OPERATE_VALUE, condition.getConditionExpression().getOperateValue().get(0)
, condition.getConditionExpression().getType(), condition.getConditionExpression().getColumn()));
}
public static Object getValidCondition(String operateValue, ConditionDTO condition) {
return ConditionAssertUtils.notThrows(conditionDTO -> conditionDTO.getConditionExpression().getType()
.convert(operateValue, conditionDTO.getConditionExpression().getDateformat()), condition,
String.format(UNABLE_CONVERT_OPERATE_VALUE, operateValue
, condition.getConditionExpression().getType(), condition.getConditionExpression().getColumn()));
}
public static String getDisplayColumn(ConditionDTO condition) {
InternalDateFormatFunction dateFormatFunction = condition.getConditionExpression().getDateFormatFunction();
if (Objects.isNull(dateFormatFunction) || StringUtils.isBlank(dateFormatFunction.getDateFormat())) {
return condition.getConditionExpression().getColumn();
}
ConditionAssertUtils.isTrue(Objects.equals(condition.getConditionExpression().getType(), ColumnType.STRING), String.format(
UN_MATCH_COLUMN_TYPE_FOR_DATE_FORMAT_FUNCTION, ColumnType.STRING.name(), condition.getConditionExpression().getColumn()
));
return String.format("%s(%s,'%s')", InternalDateFormatFunction.FUNCTION_NAME, condition.getConditionExpression().getColumn(),
dateFormatFunction.getDateFormat());
}
}
@UtilityClass
public class ConditionAssertUtils {
public static void notNull(Object object, String message) {
if (Objects.isNull(object)) {
throw new ConditionValidationException(message);
}
}
public static void notEmpty(List<String> objects, String message) {
if (CollectionUtils.isEmpty(objects)) {
throw new ConditionValidationException(message);
}
}
public static void isTrue(boolean bool, String message) {
if (!bool) {
throw new ConditionValidationException(message);
}
}
public static void isFalse(boolean bool, String message) {
if (bool) {
throw new ConditionValidationException(message);
}
}
public static Object notThrows(Function<ConditionDTO, Object> function, ConditionDTO condition, String message) {
try {
return function.apply(condition);
} catch (Exception e) {
throw new ConditionValidationException(message);
}
}
}
@UtilityClass
public class ConditionConstant {
public static final String MISSING_OPERATE_VALUE = "Missing operate value for condition column:[%s]";
public static final String MISSING_STRATEGY = "Can't find strategy for column:[%s] with operate:[%s] and operate type:[%s]";
public static final String MISSING_COLUMN = "Can't find the column:[%s]";
public static final String MISSING_CONDITION_DETAIL = "Missing condition expression detail,need supply column name and operate type and operate value";
public static final String UN_SUPPORT_DATE_FORMAT_FUNCTION = "Can't support internal date_format function for column name:[%s]";
public static final String UNABLE_CONVERT_OPERATE_VALUE = "Can't convert operate value:[%s] to column type:[%s] for column name:[%s]";
public static final String OPERATE_BETWEEN_MISSING_OPERATE_VALUE = "Operate between should have two operate value for column name:[%s]";
public static final String UN_MATCH_COLUMN_TYPE_FOR_DATE_FORMAT_FUNCTION = "Column type should be [%s] for column name:[%s] when using date_format function";
}
不同策略的具体实现
@Slf4j
@InjectPredicateStrategy //使用ImportBeanDefinitionRegistrar动态创建自定义Bean到Spring中
public class BetweenPredicateStrategy implements PredicateStrategy, InitializingBean { //用到了spring的注入+策略工厂模式,在spring注入完成之后把自身注册到了策略工厂中
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.isTrue(condition.getConditionExpression().getOperateValue().size() == 2,
String.format(OPERATE_BETWEEN_MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(ConditionUtils.getBetweenPredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.getBetweenPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.isTrue(condition.getConditionExpression().getOperateValue().size() == 2,
String.format(OPERATE_BETWEEN_MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s not %s %s", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(),
condition.getConditionExpression().getOperateValue().stream().map(item ->
String.format("'%s'", item)).collect(Collectors.joining(" and ")));
}
return String.format("%s %s %s", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(),
condition.getConditionExpression().getOperateValue().stream().map(item ->
String.format("'%s'", item)).collect(Collectors.joining(" and ")));
}
@Override
public void afterPropertiesSet() { //spring注入该对象之后,就会执行该方法,该方法会把自身注册到策略工厂中
for (ColumnType columnType : OperateExpressionEnum.BETWEEN.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.BETWEEN, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class EmptyPredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(ConditionUtils.getEmptyPredicateCondition(root, criteriaBuilder, condition.getConditionExpression().getColumn()));
}
return ConditionUtils.getEmptyPredicateCondition(root, criteriaBuilder, condition.getConditionExpression().getColumn());
}
@Override
public String getConditionContent(ConditionDTO condition) {
if (condition.getConditionExpression().isNot()) {
return String.format("(%s is not null and %s <> '')", condition.getConditionExpression().getColumn(),
condition.getConditionExpression().getColumn());
}
return String.format("(%s is null or %s = '')", condition.getConditionExpression().getColumn(),
condition.getConditionExpression().getColumn());
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.EMPTY.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.EMPTY, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class EqualsPredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
if (condition.getConditionExpression().isNot()) {
return ConditionUtils.getNotEqualPredicateCondition(root, criteriaBuilder, condition);
}
return ConditionUtils.getEqualPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
if (condition.getConditionExpression().isNot()) {
return String.format("%s %s %s", ConditionUtils.getDisplayColumn(condition),
CollectionUtils.isEmpty(condition.getConditionExpression().getOperateValue()) ? "is not" : "<>",
CollectionUtils.isEmpty(condition.getConditionExpression().getOperateValue()) ? "null"
: String.format("'%s'",condition.getConditionExpression().getOperateValue().get(0)));
}
return String.format("%s %s %s", ConditionUtils.getDisplayColumn(condition),
CollectionUtils.isEmpty(condition.getConditionExpression().getOperateValue()) ? "is"
: condition.getConditionExpression().getOperateExpression().getValue(),
CollectionUtils.isEmpty(condition.getConditionExpression().getOperateValue()) ? "null"
: String.format("'%s'",condition.getConditionExpression().getOperateValue().get(0)));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.EQUALS.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.EQUALS, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class GePredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(
ConditionUtils.getGreaterThanOrEqualToPredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.getGreaterThanOrEqualToPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s < '%s'", ConditionUtils.getDisplayColumn(condition), condition.getConditionExpression().getOperateValue().get(0));
}
return String.format("%s %s '%s'", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(), condition.getConditionExpression().getOperateValue().get(0));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.GE.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.GE, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class GtPredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(
ConditionUtils.getGreaterThanPredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.getGreaterThanPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s <= '%s'", ConditionUtils.getDisplayColumn(condition), condition.getConditionExpression().getOperateValue().get(0));
}
return String.format("%s %s '%s'", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(), condition.getConditionExpression().getOperateValue().get(0));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.GT.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.GT, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class InPredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(ConditionUtils.geInPredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.geInPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s not %s (%s)", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(),
condition.getConditionExpression().getOperateValue().stream().map(item ->
String.format("'%s'", item)).collect(Collectors.joining(",")));
}
return String.format("%s %s (%s)", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(),
condition.getConditionExpression().getOperateValue().stream().map(item ->
String.format("'%s'", item)).collect(Collectors.joining(",")));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.IN.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.IN, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class LePredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(
ConditionUtils.getLessThanOrEqualToPredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.getLessThanOrEqualToPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s > '%s'", ConditionUtils.getDisplayColumn(condition), condition.getConditionExpression().getOperateValue().get(0));
}
return String.format("%s %s '%s'", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(), condition.getConditionExpression().getOperateValue().get(0));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.LE.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.LE, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class LikePredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(ConditionUtils.getLikePredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.getLikePredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s not %s '%s'", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(),
condition.getConditionExpression().getOperateValue().get(0));
}
return String.format("%s %s '%s'", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(),
condition.getConditionExpression().getOperateValue().get(0));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.LIKE.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.LIKE, columnType, this);
}
}
}
@Slf4j
@InjectPredicateStrategy
public class LtPredicateStrategy implements PredicateStrategy, InitializingBean {
@Override
public Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return criteriaBuilder.not(ConditionUtils.getLessThanPredicateCondition(root, criteriaBuilder, condition));
}
return ConditionUtils.getLessThanPredicateCondition(root, criteriaBuilder, condition);
}
@Override
public String getConditionContent(ConditionDTO condition) {
ConditionAssertUtils.notEmpty(condition.getConditionExpression().getOperateValue(),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
ConditionAssertUtils.notNull(condition.getConditionExpression().getOperateValue().get(0),
String.format(MISSING_OPERATE_VALUE, condition.getConditionExpression().getColumn()));
if (condition.getConditionExpression().isNot()) {
return String.format("%s >= '%s'", ConditionUtils.getDisplayColumn(condition), condition.getConditionExpression().getOperateValue().get(0));
}
return String.format("%s %s '%s'", ConditionUtils.getDisplayColumn(condition),
condition.getConditionExpression().getOperateExpression().getValue(), condition.getConditionExpression().getOperateValue().get(0));
}
@Override
public void afterPropertiesSet() {
for (ColumnType columnType : OperateExpressionEnum.LT.getSupportTypes()) {
PredicateStrategyFactory.register(OperateExpressionEnum.LT, columnType, this);
}
}
}
继承JpaSpecificationExecutor 实现自己的SpecificationExecutor
public interface ConditionSpecificationExecutor<T> extends JpaSpecificationExecutor<T> {
default List<T> findAll(ConditionDTO condition) {
return this.findAll((Specification<T>) (root, query, criteriaBuilder) -> ConditionUtils.findByCondition(root, criteriaBuilder, condition));
}
default Long count(ConditionDTO condition) {
return this.count((root, query, criteriaBuilder) -> ConditionUtils.findByCondition(root, criteriaBuilder, condition));
}
}
之后我们只需要给具体的实体的repository实现这个接口即可以使用json来查询该实体的数据了
@Repository
public interface XXXRepository extends JpaRepository<XXXEntity, Long>, ConditionSpecificationExecutor<XXXEntity> {
//...
}
动态注册策略bean到Spring中
除了上面的步骤,我们还需要把我们的策略注册到spring中
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface InjectPredicateStrategy {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(InjectPredicateStrategyAutoConfigureRegistrar.class)
public @interface PredicatedStrategyAutoConfigure {
}
public class InjectPredicateStrategyAutoConfigureRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
PredicateStrategyBeanDefinitionScanner scanner = new PredicateStrategyBeanDefinitionScanner(registry, false);
scanner.setResourceLoader(resourceLoader);
scanner.registerFilters();
scanner.addIncludeFilter(new AnnotationTypeFilter(InjectPredicateStrategy.class));
scanner.doScan("com.xxx.common.condition");
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
之后我们在具体的启动类中加入注解,就可以把我们的这些策略注册到spring中了
@SpringBootApplication
@PredicatedStrategyAutoConfigure
public class XXXApplication {
@PostConstruct
void started() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
public static void main(String[] args) {
SpringApplication.run(XXXApplication.class, args);
}
}