web-dev-qa-db-de.com

Spring Data JPA date "zwischen" Abfrageproblem bei Verwendung von Date-Parametern

In meiner Anwendung verwende ich Spring Data und halte als JPA-Anbieter den Ruhezustand, um Daten zu erhalten und zu lesen.

Ich habe eine oberste Entity-Klasse:

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

Für die vererbte Klasse habe ich ein entsprechendes 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);
}

Und ich habe den Integrationstest mit folgender Methode:

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

Dieser Test schlägt jedoch fehl, wenn ich die MySql-Datenbank verwende, weil das Ergebnis leer ist (wird jedoch bestanden, wenn ich zu HSQLDB wechsle)

Dieser Test ist auch bestanden, wenn ich zu Beginn des Tests eine Sekunde lang "Thread.sleep (1000)" nach der ersten Zeile gesetzt habe.

Wenn ich SQL aus dem Hibernate-Protokoll ausführen, erhalten Sie das richtige Ergebnis. Was ist falsch an meinem Code?

6
Dmitry Shipaev

Ich habe mein Problem erkannt. Das Problem war auf die Genauigkeitsunterschiede zwischen dem Feldtyp in MySql (Standardzeitstempel für Millisekunden) und dem Java-Datum (mit Millisekunden) Zurückzuführen. Ich habe meine Tabelle geändert: 

ALTER TABLE transaction modify end_timestamp TIMESTAMP(6)

und das hat mein Problem gelöst. 

0
Dmitry Shipaev

In JPA erfordert Dateeinen zeitlichen Hinweis. Normalerweise können Sie den Parameter TemporalTypeeinstellen, wenn Sie den Parameter JPA Queryeinstellen:

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

Bei Spring Data müssen Sie die Annotation @Temporal verwenden, damit Ihre Abfrage zu:

@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
);
7
Vlad Mihalcea