Spring Data - 如何将 YearMonth 类型的 @Param 转换为 LocalDate 的开始和结束?

Posted

技术标签:

【中文标题】Spring Data - 如何将 YearMonth 类型的 @Param 转换为 LocalDate 的开始和结束?【英文标题】:Spring Data - How to Convert @Param of type YearMonth to start and end LocalDate? 【发布时间】:2021-10-24 05:31:30 【问题描述】:

后台域

我有每月存储在数据库中的产品的费率。开始日期始终是上个月的最后一天,结束日期是当月的最后一天。例如,2020 年 3 月汇率的开始日期为 2020 年 2 月 29 日,结束日期为 2020 年 3 月 31 日。

当前实施

这是Rate 类:

@Entity
@Data
public class Rate 

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name="productId")
    @JsonBackReference
    private Product product;
    
    private LocalDate startDate;
    private LocalDate endDate;
    private BigDecimal value;
    

我有一个 Spring Data 存储库,它具有以下方法:

Rate findByProductIdAndStartDateAndEndDate(
        @Param("productId") Long productId,
        @Param("startDate") @DateTimeFormat(iso=DateTimeFormat.ISO.DATE) LocalDate startDate, 
        @Param("endDate") @DateTimeFormat(iso=DateTimeFormat.ISO.DATE) LocalDate endDate);

目标

我想通过(例如)通过重写findByProductIdAndStartDateAndEndDate 来调用Rate 存储库来从客户那里抽象出这个细节,如下所示:

//OK with writing JPQL here via @Query
Rate findByProductIdAndYearMonth(
        @Param("productId") Long productId,
        @Param("yearMonth") @DateTimeFormat(iso=DateTimeFormat.ISO.DATE) YearMonth yearMonth);

我尝试过/考虑过的事情

在阅读this post 后,我考虑过AttributeConverter。但我正在尝试从一个字段 yearMonth 转换为两个数据库列 startDateendDate

我知道我可以编写一个服务类来获取 YearMonth,然后在确定开始和结束日期后调用 repo。但我正在使用 Spring Data REST 并希望允许客户端通过以下方式直接调用此 repo:

/api/rates/search/findByProductIdAndYearMonth

【问题讨论】:

@Query注释findByProductIdAndYearMonth怎么样?然后编写您的自定义查询。只是一个提示。我不擅长 JPQL。 【参考方案1】:

如果您的数据库支持虚拟列,那么我会考虑创建一个名为 period 的虚拟列,它计算为结束日期的月/年。

如果该虚拟列在您的实体中映射为任何其他属性,则查询变得微不足道。您可以将 JPA 属性设为只读。一般来说,这里的查询可能有优势,因为任何查询都可以简单地写为 period = '0320' 或其他任何内容,而不必担心开始和结束日期。

如果您的数据库不支持虚拟列,那么您可以通过 2 列数据库视图(rate_id、period)实现类似的效果,然后通过 @SecondaryTable 将其连接到 Rate 实体。

在任一解决方案中,您的 Rate 实体都有一个新属性 period,您可以像查询任何其他列一样对其进行查询和排序。

【讨论】:

好建议。由于 OP 显然使用 Hibernate,org.hibernate.annotations.Formula 可用于映射(虚拟)列。【参考方案2】:

您始终可以做的是使用 SPeL,它调用 @Query 中的自定义方法,为您进行转换。例如创建一个实用程序类,如

package com.acme.util;

import java.time.LocalDate;
import java.time.YearMonth;

public class RateDateUtil 

   public static LocalDate toEndDate(YearMonth ym) 
     return ym.atEndOfMonth();
   
    
   public static LocalDate toStartDate(YearMonth ym) 
     return ym.minusMonths(1).atEndOfMonth();
   

然后在你的@Query中使用它

@Query("from Rate r join r.product p where p.id = :##productId and r.startDate = :#T(com.acme.util.RateDateUtil).toStartDate(#yearMonth) and r.endDate = :#T(com.acme.util.RateDateUtil).toEndDate(#yearMonth)")
Rate findByProductIdAndYearMonth(@Param("productId") Long productId, @Param("yearMonth") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) YearMonth yearMonth);

请注意,在较旧的 spring-data-jpa 版本中,您不能混合使用 SPeL 和 JPA 方式,这就是 productId 也需要包装的原因。这可能已在较新的版本中得到修复(我使用 1.1.x 版本对此进行了测试)。有关更多信息,请参阅 this answer 以了解其他问题。

【讨论】:

以上是关于Spring Data - 如何将 YearMonth 类型的 @Param 转换为 LocalDate 的开始和结束?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 spring-data-rest 与 spring websocket 混合到一个实现中

如何将多个日期之间的搜索与 Spring Data JPA 的 CrudRepository 结合起来?

在将它们添加到 MongoDB 之前,如何在 Spring Data 中处理插入请求?

如何将 Spring Data JPA 页面初始化为空?

如何将列表发布到 Spring Data Rest?

如何将默认过期的 RedisCacheManager 迁移到 Spring Data Redis 2.0?