使用日期参数时的 Spring Data JPA 日期“之间”查询问题

Posted

技术标签:

【中文标题】使用日期参数时的 Spring Data JPA 日期“之间”查询问题【英文标题】:Spring Data JPA date "between" query issue when using Date parameters 【发布时间】:2016-05-16 01:36:52 【问题描述】:

在我的应用程序中,我使用 Spring Data 和 hibernate 作为 JPA 提供程序来持久化和读取数据。

我有***实体类:

@Entity
@Getter @Setter
@Table(name = "operation")
@Inheritance(strategy = InheritanceType.JOINED)
@EqualsAndHashCode(of = "operationId")
public abstract class Operation implements Serializable 
    public static final int OPERATION_ID_LENGTH = 20;

    @Id
    @Column(name = "operation_id", length = OPERATION_ID_LENGTH, nullable = false, columnDefinition = "char")
    private String operationId;

    @Column(name = "operation_type_code")
    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private String operationTypeCode;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "begin_timestamp", nullable = false)
    private Date beginTimestamp = new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "end_timestamp")
    private Date endTimestamp;

    @Column(name = "operation_number", length = 6, columnDefinition = "char")
    private String operationNumber;


    @Enumerated(EnumType.STRING)
    @Column(name = "operation_status", length = 32, nullable = false)
    private OperationStatus status;

    @ManyToOne(optional = false)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "terminal_id")
    private Terminal terminal;


    @Column(name = "training_mode", nullable = false)
    private boolean trainingMode;

对于继承的类,我有相应的存储库:

public interface ConcreteOperationRepository extends JpaRepository<ConcreteOperation, String> 

    @Query("SELECT o FROM ConcreteOperation o WHERE o.beginTimestamp BETWEEN :from AND :to AND o.status = :status AND o.terminal.deviceId = :deviceId AND o.trainingMode = :trainingMode")
    Collection<ConcreteOperation> findOperations(@Param("from") Date startDay,
                                                   @Param("to") Date endDay,
                                                   @Param("status") OperationStatus status,
                                                   @Param("deviceId") String deviceId,
                                                   @Param("trainingMode") boolean trainingMode);

我有以下方法的集成测试:

@Transactional
@Test
public void shouldFindOperationByPeriodAndStatusAndWorkstationId() 
    Date from = new Date(Calendar.getInstance().getTime().getTime());
    List<String> terminalIds = loadTerminalIds();
    List<OperationStatus> typeForUse = Arrays.asList(OperationStatus.COMPLETED,
            OperationStatus.LOCKED, OperationStatus.OPEN);
    int countRowsForEachType = 3;
    int id = 100001;
    for (String terminalId : terminalIds) 
        for (OperationStatus status : typeForUse) 
            for (int i = 0; i < countRowsForEachType; i++) 
                concreteOperationRepository.save(createConcreteOperation(status, terminalId,
                        String.valueOf(++id)));
            
        
    
    Date to = new Date(Calendar.getInstance().getTime().getTime());
    for (String terminalId : terminalIds) 
        for (OperationStatus status : typeForUse) 
            Collection<ConcreteOperation> operations =
                    concreteOperationRepository.findOperations(from, to, status, terminalId, false);
            assertEquals(countRowsForEachType, operations.size());
        
    

但是当我使用 mysql 数据库时,由于结果为空,此测试失败(但当我切换到 HSQLDB 时通过)

此外,如果我在测试开始时将延迟“Thread.sleep(1000)”放在第一行之后一秒钟,则此测试通过。

当我从 Hibernate 日志执行 SQL 时,它给了我正确的结果。我的代码有什么问题?

【问题讨论】:

我已将 Date 更改为 LocalDateTime 但它没有解决问题 【参考方案1】:

我意识到了我的问题。问题是由于 MySql 中的字段类型(默认时间戳精度削减毫秒)和 Java 日期(毫秒)之间的精度差异 我改变了我的桌子:

ALTER TABLE transaction modify end_timestamp TIMESTAMP(6)

这解决了我的问题。

【讨论】:

你的意思是说“microseconds” for MySQL? Java 8 及更高版本中内置的 java.time 框架没有此类问题,可处理纳秒分辨率(最多 9 位小数秒)。如果您使用java.sql.Timestampjava.time.Instant,您将不会遇到丢失数据的问题。避免臭名昭著的麻烦 java.util.Date/.Calendar 类。 是的,感谢您的更正。我的意思是“微秒”。但是微秒被MySql切断了。例如 - 当我插入“2016-02-09 08:11:37.567”时,这个时间戳作为“2016-02-09 08:11:37”插入到 MySql 中。【参考方案2】:

在 JPA 中,Date 需要时间提示。一般在设置JPA Query参数时可以设置TemporalType

query.setParameter("from", from), TemporalType.TIMESTAMP);

使用 Spring Data,您需要使用 @Temporal 注释,因此您的查询变为:

@Query("SELECT o FROM ConcreteOperation o WHERE o.beginTimestamp BETWEEN :from AND :to AND o.status = :status AND o.terminal.deviceId = :deviceId AND o.trainingMode = :trainingMode")
Collection<ConcreteOperation> findOperations(
    @Param("from") @Temporal(TemporalType.TIMESTAMP) Date startDay,
    @Param("to") @Temporal(TemporalType.TIMESTAMP) Date endDay,
    @Param("status") OperationStatus status,
    @Param("deviceId") String deviceId,
    @Param("trainingMode") boolean trainingMode
);

【讨论】:

我在日期之前添加了注释@Temporal(TemporalType.TIMESTAMP)。但这并没有给出任何结果 然后最好使用 P6Spy 记录带有参数的 SQL 查询 一切似乎都是正确的。我已经从客户端执行了查询,一切正常(concreop0_1_.begin_timestamp 在 '2016-02-07 11:44:21' 和 '2016-02-07 11:44:22' 之间)和 concreop0_1_.training_mode=false跨度> 不,当原始 sql 正确时它不起作用。仅当我在定义日期后设置小延迟时才有效。但如果我不这样做,结果是空的。 也许将参数重命名为查询中使用的占位符以避免@Param 的疯狂? ?

以上是关于使用日期参数时的 Spring Data JPA 日期“之间”查询问题的主要内容,如果未能解决你的问题,请参考以下文章

意外的令牌:在休眠状态下使用 spring data jpa 时的位置

Spring Data Jpa项目使用ManyToMany关系时的生成查询

集成 Spring JPA Data 和 Spring Cache 时的奇怪行为

使用 Spring Data JPA 获取两个日期之间的记录

有插入时的数据库轮询 - Spring Data JPA

从父类更新日期字段在 Spring Data Jpa 中不起作用