如何在JPA Criteria API中正确应用join fetch

时间:2017-02-01 20:25:28

标签: hibernate jpa spring-data spring-data-jpa jpa-criteria

我在基于Spring数据JPA的应用程序中使用JPA Criteria API。我的服务类使用静态方法来检索import itertools def format_data(input_data): known_keys = ["title", "slug", "date", "modified", "category", "tags"] xtra_keys = set(input_data.keys()).difference(known_keys) output = [] for key in itertools.chain(known_keys, xtra_keys): try: output.append("{}: {}".format(key.title(), data[key])) except KeyError as e: pass return '\n'.join(output) data = {"tags": "one, two", "slug": "post-title", "date": "2017-02-01", "title": "Post Title", "foo": "bar"} >>> print format_data(data) Title: Post Title Slug: post-title Date: 2017-02-01 Tags: one, two Foo: bar >>> ,然后可以将它们组合在一起形成一个特定的查询。 E.g。

Specifications

在这里,我使用两种方法返回规范,并应用适当的标准。这就是两种方法的样子

repository.findAll(where(matchById(str)).or(matchByName(str)))

这很好用。我想添加一个

public static Specification<SomeEntity> matchById(String str) { return (root, criteriaQuery, cb) -> cb.like(root.get(SomeEntity_.id).as(String.class), str + "%"); } public static Specification<SomeEntity> matchByName(String str) { return (root, criteriaQuery, cb) -> { cb.or(cb.like(cb.lower(root.get(SomeEntity_.firstName)), str.toLowerCase() + "%"), cb.like(cb.lower(root.get(SomeEntity_.lastName)), str.toLowerCase() + "%") ); }

以这种方式使用任何静态规范方法组合构建的所有查询都使用root.fetch(SomeEntity_.employee, JoinType.INNER);

如果我将这个语句添加到两个静态方法中,那么INNER JOIN会被应用两次,这看似不对。理想情况下,我认为我应该有另一个静态方法,只应用FETCH JOIN并返回规范但我似乎无法弄清楚如何在不使用任何FETCH JOIN方法的情况下返回Predicate 。为了澄清,这就是我的方法应该是这样的:

criteriaBuilder

任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:1)

我过去使用过的一个解决方案是引入一个CriteriaQueryHelper类,它允许我为它提供几个JPA类,它将确定是应该构造一个新的连接或提取还是重用现有的连接。 / p>

通过使用以下内容,您的Specification实现只需通过调用#getOrCreateJoin(...)来使用帮助程序类,它将返回(a)现有连接而不创建新连接或(b)新创建的实例(如果不存在)。

这可以很容易地避免您使用多个连接所描述的问题。

public class CriteriaQueryHelper {

  // for List<> attributes, get or create a join
  // other implementations would be needed for other container types likely.
  public static <X, Y, Z> ListJoin<X, Y> getOrCreateJoin(
                From<Z, X> root, 
                ListAttribute<X, Y> attribute,
                JoiNType joinType) {
    ListJoin<X, Y> join = (ListJoin<X, Y>) getJoin( root, attribute, joinType );
    return join != null ? join : root.join( attribute, joinType );
  }

  // gets the join, looking at join-fetch first, followed by joins
  private static <X, Y, Z> Join<X, Y> getJoin(
                From<Z,X> root, 
                Attribute<?, Y> attribute, 
                JoinType joinType) {
    Join<X, Y> fetchJoin = getJoinFromFetches( root, attribute );
    if ( fetchJoin != null ) {
      return fetchJoin; 
    }
    Join<X, Y> join = getJoinFromJoins( root, attribute, joinType );
    return join;
  }

  // gets a join from fetch
  private static <X, Y, Z> Join<X, Y> getJoinFromFetches(
                 From<Z, X> root, 
                 Attribute<?, Y> attribute) {
    for ( Fetch<X, ?> fetch : root.getFetches() ) {
      final Class<?> attributeClass = fetch.getAttribute().getClass();
      if ( attributeClass.isAssignableFrom( attribute.getClass() ) ) {       
        final String name = attribute.getName();
        if ( name.equals( fetch.getAttribute().getName() ) ) {
          return (Join<X, Y>) fetch;
        }
      }
    }
    return null;
  }      

  // gets a join from joins
  private static <X, Y, Z> Join<X, Y> getJoinFromJoins(
                 From<Z, X> root, 
                 Attribute<?, Y> attribute,
                 JoinType joinType) {
    for ( Join<?, ?> fetch : root.getJoins() ) {
      final String joinName = join.getAttribute().getName();
      if ( joinName.equals( attribute.getName() ) ) {
        if ( join.getJoinType().equals( joinType ) ) {
          return (Join<X, Y>) join;
        }
      }
    }
    return null;
  }
}