2

我们必须从需要查找与键值对列表匹配的实体的数据库中查询数据。我们认为使用 Spring Data JPA 是个好主意,因为我们还需要分页。

我们创建的表如下所示:

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

是否可以定义一个查询方法来获取具有包含给定键/值对的属性的所有终端?

像这样的东西:List<Terminal> findByPropertiesKeyAndValue(List<Property>);

4

1 回答 1

1

我没有执行代码,但是给定正确的导入语句,这至少可以编译。根据您的实体定义,可能需要调整某些属性,无论如何,您应该了解如何处理此问题。

我的条件查询基于以下 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
  )

您在其中列出每个名称/值对并42简单地表示名称/值对的数量。

所以我假设你定义了一个这样的存储库:

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

JpaSpecificationExecutor为了使用标准 API进行扩展很重要。

然后你可以像这样构建一个条件查询:

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

您显然可以替换Map<String, String>Iterable<TerminalProperty>,尽管这会感觉很奇怪,因为它们似乎绑定到特定的Terminal.

于 2013-04-23T22:19:34.473 回答