标准 API 加入过滤器

Criteria API join with filter

所以我试图用 JPA 标准 Api 替换以下原生 SQL:

select CAT.* from CAT
join OWNER.ID = CAT.OWNER_ID
where OWNER.NAME = :ownerName

select CAT.* from CAT, OWNER
where OWNER.ID = CAT.OWNER_ID 
and OWNER.NAME = :ownerName

实体看起来有点像这样:

class Owner {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", unique = true, nullable = false)
    private Long id;

    @Column(name = "NAME", length = 15)
    private String name;

    ...
}

class Cat {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", unique = true, nullable = false)
    private Long id;

    @Column(name = "OWNER_ID", nullable = false)
    private Long ownerId;

    @Column(name = "NAME", length = 15)
    private String name;

    ...
}

我实现了一个 org.springframework.data.jpa.domain.Specification 我可以与 JpaSpecificationExecutor 一起使用,例如:

@Primary
@Repository
public interface CatRepository
    extends JpaRepository<Cat, Long>, JpaSpecificationExecutor<Cat> {}

interface Function3<ARG1, ARG2, ARG3, RETURN> {
   RETURN apply(ARG1 arg1, ARG2 arg2, ARG3 arg3);
}

public static <TYPE> Specification<TYPE> create(
      final Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> predicate) {
    return (root, query, criteriaBuilder) -> {
      return predicate.apply(root, (AbstractQuery<TYPE>) query, criteriaBuilder);
    };
}

 public static <TYPE, JOINTYPE>
      Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> join(
          Class<JOINTYPE> joinClass,
          String joinColumn,
          String joiningColumn,
          final Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> predicate,
          final Function3<Root<JOINTYPE>, AbstractQuery<JOINTYPE>, CriteriaBuilder, Predicate>
              joinPredicate) {
    return (root, criteriaQuery, criteriaBuilder) -> {
      CriteriaQuery<JOINTYPE> joinQuery = criteriaBuilder.createQuery(joinClass);
      Root<JOINTYPE> joinRoot = joinQuery.from(joinClass);
      //TODO add a filter here for Owner.name here
      // joinQuery.where(joinPredicate.apply(joinRoot, joinQuery, criteriaBuilder));

      return criteriaBuilder.equal(root.get(joinColumn), joinRoot.get(joiningColumn));
    };
}

但我只得到 join/select 的一侧,例如:

select 
   generatedAlias0 
from 
   com.some.pckg.Cat as generatedAlias0 
where 
   generatedAlias0.ownerId=generatedAlias1.id

当我这样做时:

catRepo.find(create(join(Owner.class,"ownerId","id", null, null)));

如何在此处将 table 添加到 select?所以最终生成的值看起来像:

select 
   generatedAlias0 
from 
   com.some.pckg.Cat as generatedAlias0,
   com.some.pckg.Owner as generatedAlias1 
where 
   generatedAlias0.ownerId=generatedAlias1.id

问题是您正在规范中创建新查询。
您需要扩展一个已传递到规范中的现有的。
1.未指定实体之间的关系

    static <TYPE, JOINTYPE> Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> join(Class<JOINTYPE> joinClass, String joinColumn, String joiningColumn) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            Root<JOINTYPE> joinRoot = criteriaQuery.from(joinClass);
            return criteriaBuilder.equal(root.get(joinColumn), joinRoot.get(joiningColumn));
        };

2。 @ManyToOne关系已定义

@Entity
public class Cat {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", unique = true, nullable = false)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "OWNER_ID", nullable = false)
    private Owner owner;

    @Column(name = "NAME", length = 15)
    private String name;
}

    static <TYPE, JOINTYPE> Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> join(Class<JOINTYPE> joinClass, String relationPropery) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            root.join(relationPropery);
            return criteriaBuilder.conjunction();
        };
    }

完全实施:

@Repository
public interface CatSpecificationRepository extends  JpaRepository<Cat, Long>, JpaSpecificationExecutor<Cat> {

    static <TYPE> Specification<TYPE> create(final Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> predicate) {
        return (root, query, criteriaBuilder) -> predicate.apply(root, (AbstractQuery<TYPE>) query, criteriaBuilder);
    }

    static <TYPE, JOINTYPE> Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> join(
            Class<JOINTYPE> joinClass,
            String joinColumn,
            String joiningColumn,
            final Function3<Root<TYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> predicate,
            final Function3<Root<JOINTYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate> joinPredicate) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            Root<JOINTYPE> joinRoot = criteriaQuery.from(joinClass);
            List<Predicate> predicates = new LinkedList<>();
            predicates.add(criteriaBuilder.equal(root.get(joinColumn), joinRoot.get(joiningColumn)));

            if (joinPredicate != null) {
                predicates.add(joinPredicate.apply(joinRoot, criteriaQuery, criteriaBuilder));
            }

            if (predicate != null) {
                predicates.add(predicate.apply(root, criteriaQuery, criteriaBuilder));
            }

            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }

    static <TYPE, JOINTYPE> Function3<Root<JOINTYPE>, AbstractQuery<TYPE>, CriteriaBuilder, Predicate>isNameEquals(String value) {
        return value != null ? (root, query, builder) -> builder.equal(root.get("name"), value) : null;
    }
}

用法示例

catSpecificationRepository.findAll(create(join(Owner.class,"ownerId","id", null, isNameEquals(value))));

生成的查询

    select
        cat0_.id as id1_2_,
        cat0_.name as name2_2_,
        cat0_.owner_id as owner_id3_2_ 
    from
        cat cat0_ cross 
    join
        owner owner1_ 
    where
        cat0_.owner_id=owner1_.id 
        and owner1_.name=?

请注意, 当前 SQL 查询构造:

from 
   cat cross join owner
where 
   cat.owner_id=owner.id 

相当于

from 
   cat inner join owner on (cat.owner_id=owner.id )

from 
   cat, owner
where 
   cat.owner_id=owner.id