文章目录

  • 使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据
    • 需求概要
    • JSON 结构的设计
    • 使用策略模式执行不同的查询条件
    • 构造查询条件
    • 主逻辑具体的代码实现
    • 不同策略的具体实现
    • 继承JpaSpecificationExecutor 实现自己的SpecificationExecutor
    • 动态注册策略bean到Spring中

使用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.Defaultprivate 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.Defaultprivate List<String> operateValue = new ArrayList<>();private String dateformat;private InternalDateFormatFunction dateFormatFunction;
}
@Getter
public enum ColumnType implements ConvertType {STRING(String.class) {@Overridepublic String convert(String value) {return value;}},BOOLEAN(Boolean.class) {@Overridepublic Boolean convert(String value) { //为了把字符串转换成具体的类型return Boolean.valueOf(value);}},NUMBER(Number.class) {@Overridepublic Number convert(String value) {return new BigDecimal(value);}},DATE(LocalDate.class) {@Overridepublic LocalDate convert(String value) {DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DateTimeUtils.DATE_FORMAT);return LocalDate.parse(value, formatter);}@Overridepublic 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) {@Overridepublic LocalDateTime convert(String value) {DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DateTimeUtils.DATE_TIME_FORMAT);return LocalDateTime.parse(value, formatter);}@Overridepublic 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()));@JsonValueprivate 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 分发不同的策略,最后返回一个Predicatereturn 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注入完成之后把自身注册到了策略工厂中@Overridepublic 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);}@Overridepublic 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 ")));}@Overridepublic void afterPropertiesSet() { //spring注入该对象之后,就会执行该方法,该方法会把自身注册到策略工厂中for (ColumnType columnType : OperateExpressionEnum.BETWEEN.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.BETWEEN, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class EmptyPredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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());}@Overridepublic 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());}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.EMPTY.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.EMPTY, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class EqualsPredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic Predicate getPredicate(Root<?> root, CriteriaBuilder criteriaBuilder, ConditionDTO condition) {if (condition.getConditionExpression().isNot()) {return ConditionUtils.getNotEqualPredicateCondition(root, criteriaBuilder, condition);}return ConditionUtils.getEqualPredicateCondition(root, criteriaBuilder, condition);}@Overridepublic 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)));}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.EQUALS.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.EQUALS, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class GePredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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);}@Overridepublic 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));}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.GE.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.GE, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class GtPredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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);}@Overridepublic 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));}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.GT.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.GT, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class InPredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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);}@Overridepublic 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(",")));}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.IN.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.IN, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class LePredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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);}@Overridepublic 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));}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.LE.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.LE, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class LikePredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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);}@Overridepublic 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));}@Overridepublic void afterPropertiesSet() {for (ColumnType columnType : OperateExpressionEnum.LIKE.getSupportTypes()) {PredicateStrategyFactory.register(OperateExpressionEnum.LIKE, columnType, this);}}
}
@Slf4j
@InjectPredicateStrategy
public class LtPredicateStrategy implements PredicateStrategy, InitializingBean {@Overridepublic 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);}@Overridepublic 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));}@Overridepublic 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;@Overridepublic 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");}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}
}

之后我们在具体的启动类中加入注解,就可以把我们的这些策略注册到spring中了

@SpringBootApplication
@PredicatedStrategyAutoConfigure
public class XXXApplication {@PostConstructvoid started() {TimeZone.setDefault(TimeZone.getTimeZone("UTC"));}public static void main(String[] args) {SpringApplication.run(XXXApplication.class, args);}}

使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据相关推荐

  1. Spring Data JPA Specification(规范)实现复杂查询

    目录 持久化 API 接口概述 JpaSpecificationExecutor 常用 API JPA Specification 编码示例 持久化 API 接口概述 1.JPA 持久化 API 接口 ...

  2. Spring Boot JPA 2.7.2

    icon: edit date: 2022-01-02 category: CategoryA tag: tag A tag B star: true Spring Boot JPA 2.7.2 项目 ...

  3. Spring Boot Jpa 配置多个数据源,并读取其中一个表的具体数据

    总体简介: Spring Boot Jpa配置多个数据源(此次两个mysql数据库),访问其中一个库 alime_counsel_assign_log下的assign_data_backflow表,实 ...

  4. spring boot jpa级联保存

    spring boot jpa级联保存 CascadeType oneToMany关系 one的一方中加 @OneToMany(fetch = FetchType.EAGER, cascade = C ...

  5. 解决spring boot+JPA实现操作数据库时编辑时也变成了新增

    场景:使用spring boot+JPA框架开发项目的时候,新增数据是正常的,但是编辑有时候会变成新增,JPA判断是否新增对象有两个方法:1根据id,2根据版本号.我在开发项目中用的是根据版本号进行判 ...

  6. Spring Boot JPA 中transaction的使用

    文章目录 @Transactional的实现 @Transactional的使用 Transaction的传播级别 REQUIRED SUPPORTS MANDATORY NEVER NOT_SUPP ...

  7. Spring Boot JPA中关联表的使用

    文章目录 添加依赖 构建Entity 构建Repository 构建初始数据 测试 Spring Boot JPA中关联表的使用 本文中,我们会将会通过一个Book和Category的关联关系,来讲解 ...

  8. Spring Boot JPA的查询语句

    文章目录 准备工作 Containing, Contains, IsContaining 和 Like StartsWith EndsWith 大小写不敏感 Not @Query Spring Boo ...

  9. Spring Boot JPA中使用@Entity和@Table

    文章目录 默认实现 使用@Table自定义表格名字 在JPQL Queries中重写表格名字 Spring Boot JPA中使用@Entity和@Table 本文中我们会讲解如何在Spring Bo ...

最新文章

  1. (转载)从无知到有知
  2. 2021年春季学期-信号与系统-第九次作业参考答案
  3. java进入下一个_在进入下一个循环迭代之前执行setTImeout操作
  4. php内容管理系统开源源码,fcontex 开源内容管理系统 php版 v1.0.3
  5. ADSL MODEM巧设置解决BT、电驴等下载软件掉线问题
  6. 理解CNN中的特征图 feature map
  7. 深度学习(四十)caffe使用点滴记录
  8. c++switch语句中不能进行变量定义吗_Go 指南--控制流语句
  9. C++基础:指针,函数指针
  10. android 开源框架
  11. 安装U8后服务器开机加载信息慢,用友U8 安装ADSL后,连接服务器非常慢
  12. 北工大计算机学院大赛,做北工大的竞赛咖!这些信息你一定不能错过!
  13. 营业增加值公式简要解析
  14. 如何在5个月内做出月入3万的业余项目
  15. 每日一道算法题 拿金币(蓝桥杯练习系统)简单的dp算法
  16. matlab如何在极坐标绘图,Matlab在极坐标中绘图
  17. linux三维动画软件,Blender2.9免费版下载
  18. 学习笔记:在Ubuntu16.04系统内安装Petalinux软件(包括如何安装Ubuntu16.04和相关支持库 详解)
  19. 解决安卓App启动页面会闪一下
  20. Coronary Artery Segmentation, A Review

热门文章

  1. hdu 逃生_从办公室逃生(Python简介)
  2. 面试Android开发工程师 3年工作经验 自我介绍
  3. 【英语:基础高阶_经典外刊阅读】L6.解题真功夫—阅读理解选择题
  4. 软工文档-软件需求说明书
  5. Swagger报错,Unable to scan document context default java.lang.NullPointerException: null
  6. GAMES101作业3及课程总结
  7. 透过Tracepoint理解内核 - 调度器框架和性能
  8. Java实现对局域网内PC的监控
  9. 关于clear属性的一些理解
  10. Android C/C++开发指南