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?
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.
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
);