使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据
文章目录
- 使用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数据来查询实体数据相关推荐
- Spring Data JPA Specification(规范)实现复杂查询
目录 持久化 API 接口概述 JpaSpecificationExecutor 常用 API JPA Specification 编码示例 持久化 API 接口概述 1.JPA 持久化 API 接口 ...
- 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 项目 ...
- Spring Boot Jpa 配置多个数据源,并读取其中一个表的具体数据
总体简介: Spring Boot Jpa配置多个数据源(此次两个mysql数据库),访问其中一个库 alime_counsel_assign_log下的assign_data_backflow表,实 ...
- spring boot jpa级联保存
spring boot jpa级联保存 CascadeType oneToMany关系 one的一方中加 @OneToMany(fetch = FetchType.EAGER, cascade = C ...
- 解决spring boot+JPA实现操作数据库时编辑时也变成了新增
场景:使用spring boot+JPA框架开发项目的时候,新增数据是正常的,但是编辑有时候会变成新增,JPA判断是否新增对象有两个方法:1根据id,2根据版本号.我在开发项目中用的是根据版本号进行判 ...
- Spring Boot JPA 中transaction的使用
文章目录 @Transactional的实现 @Transactional的使用 Transaction的传播级别 REQUIRED SUPPORTS MANDATORY NEVER NOT_SUPP ...
- Spring Boot JPA中关联表的使用
文章目录 添加依赖 构建Entity 构建Repository 构建初始数据 测试 Spring Boot JPA中关联表的使用 本文中,我们会将会通过一个Book和Category的关联关系,来讲解 ...
- Spring Boot JPA的查询语句
文章目录 准备工作 Containing, Contains, IsContaining 和 Like StartsWith EndsWith 大小写不敏感 Not @Query Spring Boo ...
- Spring Boot JPA中使用@Entity和@Table
文章目录 默认实现 使用@Table自定义表格名字 在JPQL Queries中重写表格名字 Spring Boot JPA中使用@Entity和@Table 本文中我们会讲解如何在Spring Bo ...
最新文章
- (转载)从无知到有知
- 2021年春季学期-信号与系统-第九次作业参考答案
- java进入下一个_在进入下一个循环迭代之前执行setTImeout操作
- php内容管理系统开源源码,fcontex 开源内容管理系统 php版 v1.0.3
- ADSL MODEM巧设置解决BT、电驴等下载软件掉线问题
- 理解CNN中的特征图 feature map
- 深度学习(四十)caffe使用点滴记录
- c++switch语句中不能进行变量定义吗_Go 指南--控制流语句
- C++基础:指针,函数指针
- android 开源框架
- 安装U8后服务器开机加载信息慢,用友U8 安装ADSL后,连接服务器非常慢
- 北工大计算机学院大赛,做北工大的竞赛咖!这些信息你一定不能错过!
- 营业增加值公式简要解析
- 如何在5个月内做出月入3万的业余项目
- 每日一道算法题 拿金币(蓝桥杯练习系统)简单的dp算法
- matlab如何在极坐标绘图,Matlab在极坐标中绘图
- linux三维动画软件,Blender2.9免费版下载
- 学习笔记:在Ubuntu16.04系统内安装Petalinux软件(包括如何安装Ubuntu16.04和相关支持库 详解)
- 解决安卓App启动页面会闪一下
- Coronary Artery Segmentation, A Review
热门文章
- hdu 逃生_从办公室逃生(Python简介)
- 面试Android开发工程师 3年工作经验 自我介绍
- 【英语:基础高阶_经典外刊阅读】L6.解题真功夫—阅读理解选择题
- 软工文档-软件需求说明书
- Swagger报错,Unable to scan document context default java.lang.NullPointerException: null
- GAMES101作业3及课程总结
- 透过Tracepoint理解内核 - 调度器框架和性能
- Java实现对局域网内PC的监控
- 关于clear属性的一些理解
- Android C/C++开发指南