This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Customize Specs

    延續 Extending @Spec 章節範例, 進階一點現在我們希望可以將 Entity Class 設計成可以配置, 這樣才能在 Customer 以外的 Entity 都可以使用!

    要完成這需求我們需要在 Annotation 中定義更多參數, 因此 Simple @Spec 不適用了, 我們需要的是定義新的 Annotation, 完整的程式碼如下:

    首先我們定義 Annotation:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface MaxCreatedTime {
    
      Class<?> from();
    }
    

    接著我們要撰寫負責處理 @MaxCreatedTime 的邏輯, 透過實作 SpecificationResolver 來擴充:

    public class MaxCreatedTimeSpecificationResolver implements SpecificationResolver {
    
      @Override
      public boolean supports(Databind databind) { 
        // 這邊告訴 SpecMapper 什麼時候要使用此 resolver
        return databind.getField().isAnnotationPresent(MaxCreatedTime.class);
      }
    
      @Override
      public Specification<Object> buildSpecification(Context context, Databind databind) {
        var def = databind.getField().getAnnotation(MaxCreatedTime.class);
        return databind.getFieldValue()
            .map(value -> subquery(def.from(), value.toString()))
            .orElse(null);
      }
    
      Specification<Object> subquery(Class<?> entityClass, String by) {
        // 以下提供 subquery 的實作, 僅供參考
        return (root, query, builder) -> {
          var subquery = query.subquery(LocalDateTime.class);
          var subroot = subquery.from(entityClass);
          subquery.select(
            builder.greatest(subroot.get("createdTime").as(LocalDateTime.class))
          ).where(builder.equal(root.get(by), subroot.get(by)));
          return builder.equal(root.get("createdTime"), subquery);
        };
      }
    }
    

    接著我們在 SpecMapper 建構時加入此 resolver:

    var mapper = SpecMapper.builder()
          .defaultResolvers()
          .resolver(new MaxCreatedTimeSpecificationResolver())
          .build();
    

    最後我們定義 POJO 及進行 Specification 的轉換:

    @Data
    public class CustomerCriteria {
    
      @MaxCreatedTime(from = Customer.class)
      String maxBy;
    }
    
    var criteria = new CustomerCriteria();
    criteria.setMaxBy("firstname");
    
    var spec = mapper.toSpec(criteria, Customer.class);
    repository.findAll(spec);
    

    執行的 SQL 會類似:

    ... where customer0_.created_time=(
      select max(customer1_.created_time) from customer customer1_ 
      where customer0_.firstname=customer1_.firstname
    )