【问题标题】:Date comparison using the JPA criteria API使用 JPA 标准 API 进行日期比较
【发布时间】:2016-07-17 18:22:23
【问题描述】:

我有一个包含两个日期的范围日期选择器:startend,两者都可以为空。我想过滤一个表,其中实体有一个确切的日期:date

所以,这里有一些例子。我想配。成像要匹配的日期是当前日期 (17/07/2016)。

  • null - 17/07/2016 -> 匹配
  • 17/07/2016 - null -> 匹配
  • 17/07/2016 - 17/07/2016 -> 匹配
  • null - null -> 匹配所有

在我看来,这些是边缘情况,也是我如此挣扎的原因。

其实我的代码是这样的:

CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Transaction> cq = cb.createQuery(Transaction.class);
Root<Transaction> root = cq.from(Transaction.class);

List<Predicate> predicates = new ArrayList<>();

Predicate startPredicate = cb.greaterThanOrEqualTo(root.get(Transaction_.date), start);
Predicate endPredicate = cb.greaterThanOrEqualTo(root.get(Transaction_.date), end);

predicates.add(startPredicate);
predicates.add(endPredicate);
cq.select(root).where(predicates.toArray(new Predicate[] {}));

TypedQuery<Transaction> query = getEm().createQuery(cq);
query.setFirstResult(firstRow);
query.setMaxResults(maxRow);
List<Transaction> resultList = query.getResultList();

我想得到这样的查询:

SELECT * FROM transaction
WHERE ((cast(date AS DATE) >= '2016-07-16' OR cast(date AS DATE) IS NULL))
       AND ((cast(date AS DATE) <= '2016-07-17' OR cast(date AS DATE) IS NULL))

请注意:静态日期是模拟开始和结束日期。 date 是表格列。

我知道我的代码是错误的。它只匹配范围,不考虑空值。此外,如果开始和结束是同一天,我将得到零结果。

你有什么想法吗?如何编辑我的代码以匹配所有提到的模式?

【问题讨论】:

  • 请先尝试在后端数据库上构造相应的SQL查询,直接产生预期结果。例如,您是否要显示开始日期(包括/不包括)和结束日期(包括/不包括)之间的所有行,包括 SELECT * FROM table_name WHERE (cast(start_date AS DATE) &gt; '07/17/2016' OR cast(start_date AS DATE) IS NULL) AND (cast(end_date AS DATE) &lt; '07/20/2016' OR cast(end_date AS DATE) IS NULL) 等列中的空值(或使用 BETWEEN 子句)?这样就可以更容易、更清晰地制作相应的条件查询。
  • 对不起,伙计,我没有考虑过这个选项。在我的脑海中只有标准 api,因为我认为查询很复杂(在我的脑海中)。到目前为止,您的查询看起来相当不错。我为我的问题添加了更新。你介意看看吗? :)

标签: date jpa criteria-api


【解决方案1】:

我在 MySQL 数据库中有一个名为 discount 的现有数据库表,其中两列类型为 TIMESTAMP,分别名为 discount_start_datediscount_end_date。因此,请根据表的名称和该表中的相应列调整您的查询。

基于问题中给出的SQL语句的完整条件查询可以构造如下(我希望代码是不言自明的)。

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Discount> criteriaQuery = criteriaBuilder.createQuery(Discount.class);
Root<Discount> root = criteriaQuery.from(entityManager.getMetamodel().entity(Discount.class));

SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
java.util.Date startDate = dateFormat.parse("24-02-2016");
java.util.Date endDate = dateFormat.parse("24-03-2016");

ParameterExpression<java.util.Date> parameter = criteriaBuilder.parameter(java.util.Date.class);

Predicate startDatePredicate = criteriaBuilder.greaterThanOrEqualTo(root.get(Discount_.discountStartDate).as(java.sql.Date.class), parameter);
Predicate endDatePredicate = criteriaBuilder.lessThanOrEqualTo(root.get(Discount_.discountEndDate).as(java.sql.Date.class), parameter);

Predicate startDateOrPredicate = criteriaBuilder.or(startDatePredicate, root.get(Discount_.discountStartDate).isNull());
Predicate endDateOrPredicate = criteriaBuilder.or(endDatePredicate, root.get(Discount_.discountEndDate).isNull());

Predicate and = criteriaBuilder.and(startDateOrPredicate, endDateOrPredicate);
criteriaQuery.where(and);

List<Discount> list = entityManager.createQuery(criteriaQuery)
        .setParameter(parameter, startDate, TemporalType.DATE)
        .setParameter(parameter, endDate, TemporalType.DATE)
        .getResultList();

它会生成您感兴趣的以下 SQL 查询(正如您所说,这两个字段都包含在内)。

select
    discount0_.discount_id as discount1_15_,
    discount0_.discount_code as discount2_15_,
    discount0_.discount_end_date as discount3_15_,
    discount0_.discount_percent as discount4_15_,
    discount0_.discount_start_date as discount5_15_ 
from
    project.discount discount0_ 
where
    (
        cast(discount0_.discount_start_date as date)>=? 
        or discount0_.discount_start_date is null
    ) 
    and (
        cast(discount0_.discount_end_date as date)<=? 
        or discount0_.discount_end_date is null
    )

在 Hibernate 4.3.6 final 上测试,但一般的 ORM 框架应该产生相同的查询,无需任何修改。


在此方法setParameter(parameter, startDate, TemporalType.DATE) 中,仅需要最后一个参数,即TemporalType.DATE,如果您的数据库中有DATETIMETIMESTAMP 类型的列,并且您想比较日期而忽略此类时间部分列。如果您的列没有像 DATE (MySQL) 这样的时间部分,您可以简单地排除该参数。

如有必要,您还可以使用其他日期(时间)处理 API,例如 java.timeJodaTime 替换 Date 以及 SimpleDateFormat

【讨论】:

  • 到目前为止工作正常。但它与当天的情况不符。我总是会得到一个空的结果集。你知道如何解决它吗?
  • 我的查询看起来像:....where (transactio0_.date&gt;=? or transactio0_.date is null) and (transactio0_.date&lt;=? or transactio0_.date is null)
  • 这个对测试表的查询返回正确的结果集:SELECT * FROM test WHERE (start_date &gt;= '2016/07/17' OR start_date IS NULL) AND (end_date &lt;= '2016/07/17' OR end_date IS NULL)。它返回与相应列中给定日期匹配的行,包括空行(如果有)。如果两列的类型都是DATE(因此,不是DATETIMETIMESTAMP),则完全不需要强制转换。因此,setParameter() 方法中的 TemporalType.DATE 以及这两个谓词中的 Expression.as() 可以安全地被忽略,并且在这种情况下它们对获取结果集没有明显影响
  • test 表中的两列,即start_dateend_date 在 MySQL 中都是 DATE 类型。因此,给定的条件查询预计会按预期工作(同样,如果列的类型确实是 DATE,则不需要强制转换。只需排除 TemporalType.DATEExpression.as()。我添加它们是因为我在具有TIMESTAMP 类型列的现有表)。
猜你喜欢
  • 2016-06-22
  • 1970-01-01
  • 2012-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
相关资源
最近更新 更多