Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data JPA date "between" query issue when using Date parameters

In my application, I am using Spring Data and hibernate as JPA provider to persist and read data.

I have top level Entity class:

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

For inherited class I have corresponding repository:

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

And I have integration test with following method:

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

But this test fails when I using MySql database due to empty result (but passes when I switch to HSQLDB)

Also, this test passes if I put delay "Thread.sleep(1000)" for one second at the beginning of the test, just after the first line.

When I execute SQL from Hibernate log it gives me right result. What's wrong with my code?

like image 993
Dmitry Shipaev Avatar asked Feb 05 '16 22:02

Dmitry Shipaev


1 Answers

In JPA, the Date requires a temporal hint. Normally, you could set the TemporalType when setting the JPA Query parameter:

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

With Spring Data you need to use the @Temporal annotation, so your query becomes:

@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
);
like image 157
Vlad Mihalcea Avatar answered Nov 06 '22 08:11

Vlad Mihalcea