使用Spring Boot JPA Specification实现使用JSON数据来查询实体数据

使用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);
  }

}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Spring BootJPA实现数据源时,需要进行以下步骤: 1. 配置数据源:在application.properties文件中,配置多个数据源的连接信息。每个数据源都需要设置独特的前缀。 2. 创建多个数据源Bean:在代码中,使用@Configuration和@Bean注解创建多个数据源的实例。每个数据源的实例需要设置对应的连接信息。 3. 创建EntityManagerFactory:使用LocalContainerEntityManagerFactoryBean创建多个EntityManagerFactory实例。每个实例需要设置对应的数据源和持久化单元。 4. 创建JpaTransactionManager:使用PlatformTransactionManager的实现JpaTransactionManager创建多个事务管理器实例。每个实例都需要设置对应的EntityManagerFactory。 5. 定义Repository:创建多个Repository接口,并分别使用@PersistenceContext注解注入不同的EntityManager实例。 6. 配置事务:使用@EnableTransactionManagement注解启用事务管理,并使用@Transactional注解定义事务的范围。 7. 使用不同的数据源:在代码中使用@Qualifier注解指定需要使用数据源。在进行相关数据库操作时,使用相应的Repository来访问对应的数据源。 通过以上步骤,就能够使用Spring BootJPA实现数据源的配置和使用。每个数据源都可以连接到不同的数据库,对应的Repository可以访问并操作各自的数据源。这样就可以实现在同一个应用程序中对多个数据源的管理和操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值