简体   繁体   English

以列表为参数的spring-data查询方法

[英]spring-data query method with a list as arguments

We have to query data from database where we need to find entities matching a list of key value pairs. 我们必须从数据库中查询数据,我们需要在数据库中查找与键值对列表匹配的实体。 We thought it would be a nice idea to use Spring Data JPA as we need also pagination. 我们认为使用Spring Data JPA是一个好主意,因为我们也需要分页。

The tables we created are like below: 我们创建的表如下所示:

terminal(ID,NUMBER,NAME);
terminal_properties(ID,KEY,VALUE,TERMINAL_FK);

Is it possible to define a query method to fetch all terminals with properties containing given key/value pairs ? 是否可以定义查询方法以获取具有包含给定键/值对的属性的所有终端?

Something like this: List<Terminal> findByPropertiesKeyAndValue(List<Property>); 这样的事情: List <Terminal> findByPropertiesKeyAndValue(List <Property>);

I didn't execute the code, but given the correct import statements, this at least compiles. 我没有执行代码,但是给出正确的import语句,至少可以编译。 Depending on your entity definition, some properties may need to be adapted and in any case, you should get an idea of how to approach this. 根据您的实体定义,某些属性可能需要修改,无论如何,您应该对如何实现这一点有所了解。

My criteria query is based on the following SQL: 我的条件查询基于以下SQL:

SELECT * FROM TERMINAL
  WHERE ID IN (
    SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES
      WHERE (KEY = 'key1' AND VALUE = 'value1')
        OR (KEY = 'key2' AND VALUE = 'value2')
        ...
      GROUP BY TERMINAL_FK
      HAVING COUNT(*) = 42
  )

Where you list each name/value pair and 42 simply represents the number of name/value pairs. 列出每个名称/值对的位置,而42仅代表名称/值对的数量。

So I assume you defined a repository like this: 所以我假设您定义了一个这样的存储库:

public interface TerminalRepository extends CrudRepository<Terminal, Long>, JpaSpecificationExecutor {
}

It's important to extend JpaSpecificationExecutor in order to make use of the criteria API. 扩展JpaSpecificationExecutor以便使用标准API很重要。

Then you can build a criteria query like this: 然后,您可以构建这样的条件查询:

public class TerminalService {

  private static Specification<Terminal> hasProperties(final Map<String, String> properties) {
    return new Specification<Terminal>() {
      @Override
      public Predicate toPredicate(Root<Terminal> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        // SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES
        Subquery<TerminalProperty> subQuery = query.subquery(TerminalProperty.class);
        Root propertyRoot = subQuery.from(TerminalProperty.class);
        subQuery.select(propertyRoot.get("terminal.id"));
        Predicate whereClause = null;
        for (Map.Entry<String, String> entry : properties.entrySet()) {
          // (KEY = 'key1' AND VALUE = 'value1')
          Predicate predicate = builder.and(builder.equal(propertyRoot.get("key"),
              entry.getKey()), builder.equal(propertyRoot.get("value"), entry.getValue()));
          if (whereClause == null) {
            whereClause = predicate;
          } else {
            // (...) OR (...)
            whereClause = builder.or(whereClause, predicate);
          }
        }
        subQuery.where(whereClause);
        // GROUP BY TERMINAL_FK
        subQuery.groupBy(propertyRoot.get("terminal.id"));
        // HAVING COUNT(*) = 42
        subQuery.having(builder.equal(builder.count(propertyRoot), properties.size()));

        // WHERE ID IN (...)
        return query.where(builder.in(root.get("id")).value(subQuery)).getRestriction();
      }
    };
  }

  @Autowired
  private TerminalRepository terminalRepository;

  public Iterable<Terminal> findTerminalsWith(Map<String, String> properties) {
    // this works only because our repository implements JpaSpecificationExecutor
    return terminalRepository.findAll(hasProperties(properties));
  }
}

You can obviously replace Map<String, String> with Iterable<TerminalProperty> , although that would feel odd because they seem to be bound to a specific Terminal . 您显然可以用Iterable<TerminalProperty>替换Map<String, String> ,尽管这听起来很奇怪,因为它们似乎绑定到了特定的Terminal

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM