web-dev-qa-db-fra.com

Spring Data JPA date "entre" problème de requête lors de l'utilisation des paramètres de date

Dans mon application, j'utilise Spring Data et hibernate en tant que fournisseur JPA pour persister et lire des données.

J'ai une classe d'entité de haut niveau:

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

Pour la classe héritée, j'ai le référentiel correspondant:

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

Et j'ai un test d'intégration avec la méthode suivante:

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

Mais ce test échoue lorsque j'utilise la base de données MySql à cause d'un résultat vide (mais passe quand je passe à HSQLDB)

En outre, ce test réussit si je mets le délai "Thread.sleep (1000)" pendant une seconde au début du test, juste après la première ligne.

Lorsque j'exécute SQL à partir du journal Hibernate, le résultat est correct. Qu'est ce qui ne va pas avec mon code?

6
Dmitry Shipaev

J'ai compris mon problème. Le problème était dû à la différence de précision entre le type de champ dans MySql (précision de l'horodatage par défaut, en millisecondes) et la date Java (en millisecondes) J'ai modifié ma table: 

ALTER TABLE transaction modify end_timestamp TIMESTAMP(6)

et cela a résolu mon problème. 

0
Dmitry Shipaev

Dans JPA, la Date nécessite un indice temporel. Normalement, vous pouvez définir TemporalType lors de la définition du paramètre JPA Query:

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

Avec Spring Data, vous devez utiliser l'annotation @Temporal pour que votre requête devienne:

@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