Spring数据-如何将YearMonth类型的@Param转换为LocalDate的开始和结束?
Spring Data - How to Convert @Param of type YearMonth to start and end LocalDate?
背景域
我有一个产品的费率,每月存储在数据库中。开始日期始终是上个月的最后一天,结束日期是当月的最后一天。例如,2020 年 3 月汇率的开始日期为 2020-02-29,结束日期为 2020-03-31。
当前实施
这是 Rate
class:
@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 数据存储库,它具有以下方法:
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
转换为两个数据库列 startDate
和 endDate
.
我知道我可以编写一个服务 class 来获取 YearMonth,然后在确定开始日期和结束日期后调用存储库。但我正在使用 Spring Data REST 并希望允许客户通过以下方式直接调用此 repo:
/api/rates/search/findByProductIdAndYearMonth
您始终可以做的是使用 SPeL 调用 @Query
中的自定义方法来为您进行转换。例如创建一个实用程序 class like
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 另一个问题。
如果您的数据库支持虚拟列,那么我会考虑创建一个名为 period
的虚拟列,计算为结束日期的 month/year。
如果该虚拟列与任何其他虚拟列一样映射到您的实体中 属性,那么查询就变得微不足道了。您可以将 JPA 属性 设为只读。一般来说,这里的查询可能有优势,因为任何查询都可以简单地写为句点 = '0320' 或其他任何内容,而不必担心开始和结束日期。
如果您的数据库不支持虚拟列,那么您可以通过 2 列数据库视图(rate_id,句点)实现类似的效果,然后通过 @SecondaryTable
连接到 Rate 实体.
在任一解决方案中,您的 Rate 实体都有一个新的 属性 period
,您可以像对任何其他列一样查询和排序。
背景域
我有一个产品的费率,每月存储在数据库中。开始日期始终是上个月的最后一天,结束日期是当月的最后一天。例如,2020 年 3 月汇率的开始日期为 2020-02-29,结束日期为 2020-03-31。
当前实施
这是 Rate
class:
@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 数据存储库,它具有以下方法:
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
转换为两个数据库列 startDate
和 endDate
.
我知道我可以编写一个服务 class 来获取 YearMonth,然后在确定开始日期和结束日期后调用存储库。但我正在使用 Spring Data REST 并希望允许客户通过以下方式直接调用此 repo:
/api/rates/search/findByProductIdAndYearMonth
您始终可以做的是使用 SPeL 调用 @Query
中的自定义方法来为您进行转换。例如创建一个实用程序 class like
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 另一个问题。
如果您的数据库支持虚拟列,那么我会考虑创建一个名为 period
的虚拟列,计算为结束日期的 month/year。
如果该虚拟列与任何其他虚拟列一样映射到您的实体中 属性,那么查询就变得微不足道了。您可以将 JPA 属性 设为只读。一般来说,这里的查询可能有优势,因为任何查询都可以简单地写为句点 = '0320' 或其他任何内容,而不必担心开始和结束日期。
如果您的数据库不支持虚拟列,那么您可以通过 2 列数据库视图(rate_id,句点)实现类似的效果,然后通过 @SecondaryTable
连接到 Rate 实体.
在任一解决方案中,您的 Rate 实体都有一个新的 属性 period
,您可以像对任何其他列一样查询和排序。