Donc, dans la version courte, j'imagine que j'ai un problème d'encodage des caractères, ou que la base de données stocke/renvoie la date dans un format que Hibernate/Spring-jpa n'aime pas, pour une raison quelconque.
Mais je suis secoué si je peux comprendre ce qui ne va pas!
Utilisation de Hibernate 5 pour utiliser les éléments J8 LocalDate dans les accessoires d’entité.
La base de données est en cours de création et les données insérées sont correctes (vous verrez dans l'extrait de journal ci-dessous que je récupère une valeur de date).
Extrait de journal:
2016-10-26 13:25:19.885 ERROR 1028 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException:
Could not read entity state from ResultSet : EntityKey[uk.co.deditech.entity.Person#2];
nested exception is org.hibernate.exception.GenericJDBCException: Could not read entity state from ResultSet :
EntityKey[uk.co.deditech.entity.Person#2]] with root cause org.h2.jdbc.JdbcSQLException: Hexadecimal string contains non-hex character: "2016-03-23" [90004-192]
at org.h2.message.DbException.getJdbcSQLException(DbException.Java:345) ~[h2-1.4.192.jar:1.4.192]
at org.h2.message.DbException.get(DbException.Java:179) ~[h2-1.4.192.jar:1.4.192]
at org.h2.message.DbException.get(DbException.Java:155) ~[h2-1.4.192.jar:1.4.192]
at org.h2.util.StringUtils.convertHexToBytes(StringUtils.Java:986) ~[h2-1.4.192.jar:1.4.192]
at org.h2.value.Value.convertTo(Value.Java:973) ~[h2-1.4.192.jar:1.4.192]
at org.h2.value.Value.getBytes(Value.Java:422) ~[h2-1.4.192.jar:1.4.192]
at org.h2.jdbc.JdbcResultSet.getBytes(JdbcResultSet.Java:1077) ~[h2-1.4.192.jar:1.4.192]
<snip>
Gradle:
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-freemarker")
compile group: 'com.h2database', name: 'h2', version:'1.4.192'
Entité:
@Entity
@Table(name = "person")
public @Data class Person {
...
@Column(name = "last_grading_date", nullable = true)
private LocalDate lastGradingDate;
}
Extraits de script de création de base de données automatique au démarrage de printemps:
schema.sql
create table PERSON
(
id int not null,
last_grading_date date
)
data.sql
insert into person (id, last_grading_date)
values (1, '2015-02-20');
Propriétés (le problème se produisait avant et après avoir ajouté la propriété d'encodage ci-dessous):
spring.datasource.url=jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.datasource.sql-script-encoding=UTF-8
EDIT: Après quelques recherches supplémentaires, j'ai découvert que "valider" était un paramètre de la propriété spring.jpa.hibernate.ddl-auto. Alors j'ai essayé ça.
Je reçois maintenant l'erreur suivante au démarrage ...
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: wrong column type encountered in column [last_grading_date] in table [person]; found [date (Types#DATE)], but expecting [binary(255) (Types#VARBINARY)]
at org.hibernate.tool.schema.internal.SchemaValidatorImpl.validateColumnType(SchemaValidatorImpl.Java:105) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
Je l'ai obtenu en ajoutant cette dépendance dans mon pom:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-Java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
Je ne sais pas pourquoi cela ne fonctionne pas immédiatement, mais avec cette dépendance, le problème est résolu.
J'ai aussi ajouté cette propriété sous propriétés: <hibernate.version>5.0.5.Final</hibernate.version>
Mon exemple de code pour la reproduction:
Data.sql:
insert into person (id, last_grading_date)
values (1, '2015-02-20');
application.properties
spring.datasource.url=jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.datasource.sql-script-encoding=UTF-8
PersonRepository
public interface PersonRepository extends JpaRepository<Person, Integer>{
}
La personne
@Entity
@Table(name = "person")
public class Person {
@Id
@Column
private int id;
@Column(name = "last_grading_date", nullable = true)
@Type(type = "Java.time.LocalDate")
private LocalDate lastGradingDate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LocalDate getLastGradingDate() {
return lastGradingDate;
}
public void setLastGradingDate(LocalDate lastGradingDate) {
this.lastGradingDate = lastGradingDate;
}
}
Applcation
@SpringBootApplication
public class TestApplication implements CommandLineRunner{
@Autowired
PersonRepository repo;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@Override
public void run(String... arg0) throws Exception {
Person p = repo.findOne(1);
System.out.println(p.getLastGradingDate());
}
}
résultat: 2015-02-20
Ajout d'un exemple de travail sur GitHub . La démo est construite sur Spring-boot
, Java 8
, Hibernate 5
, maven
et Java.time.LocalDate
.
JPA 2.1 a été publié avant Java 8 et l’API Date et heure n’existait tout simplement pas à ce moment-là. Par conséquent, l'annotation @Temporal ne peut être appliquée qu'aux attributs de type Java.util.Date et Java.util.Calendar.
Si vous souhaitez stocker un attribut LocalDate
dans une colonne DATE
ou un LocalDateTime
dans une colonne TIMESTAMP
, vous devez définir vous-même le mappage sur Java.sql.Date
ou Java.sql.Timestamp
.
Le convertisseur d'attributs fait partie de la spécification JPA 2.1 et peut donc être utilisé avec toute implémentation de JPA 2.1, par exemple. Hibernate ou EclipseLink. J'ai utilisé Wildfly 8.2 avec Hibernate 4.3 pour les exemples suivants.
Conversion de LocalDate
Comme vous pouvez le constater dans l'extrait de code suivant, vous n'avez pas besoin de beaucoup pour créer un convertisseur d'attribut pour LocalDate.
@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
@Override
public Date convertToDatabaseColumn(LocalDate locDate) {
return (locDate == null ? null : Date.valueOf(locDate));
}
@Override
public LocalDate convertToEntityAttribute(Date sqlDate) {
return (sqlDate == null ? null : sqlDate.toLocalDate());
}
}
Vous devez implémenter l'interface AttributeConverter<LocalDate, Date>
avec ses deux méthodes convertToDatabaseColumn
et convertToEntityAttribute
. Comme vous pouvez le constater sur les noms de méthodes, l'un d'entre eux définit la conversion du type de l'attribut d'entité (LocalDate
) au type de colonne de la base de données (Date
) et l'autre à la conversion inverse. La conversion en elle-même est très simple car Java.sql.Date
fournit déjà les méthodes pour effectuer la conversion vers et depuis une LocalDate
.
De plus, le convertisseur d'attribut doit être annoté avec l'annotation @Converter
. En raison de la propriété facultative autoApply = true, le convertisseur sera appliqué à tous les attributs de type LocalDate. Jetez un coup d'œil ici si vous souhaitez définir l'utilisation du convertisseur pour chaque attribut individuellement.
La conversion de l'attribut est transparente pour le développeur et l'attribut LocalDate
peut être utilisé comme tout autre attribut d'entité. Vous pouvez l'utiliser comme paramètre de requête par exemple.
LocalDate date = LocalDate.of(2015, 8, 11);
TypedQuery<MyEntity> query = this.em.createQuery("SELECT e FROM MyEntity e WHERE date BETWEEN :start AND :end", MyEntity.class);
query.setParameter("start", date.minusDays(2));
query.setParameter("end", date.plusDays(7));
MyEntity e = query.getSingleResult();
Conversion de LocalDateTime
Le convertisseur d'attribut pour LocalDateTime
est fondamentalement le même. Vous devez implémenter l'interface AttributeConverter<LocalDateTime, Timestamp>
et le convertisseur doit être annoté avec l'annotation @Converter. Semblable à la LocalDateConverter
, la conversion entre une LocalDateTime
et un Java.sql.Timestamp
est effectuée avec les méthodes de conversion de Timestamp
.
@Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
return (locDateTime == null ? null : Timestamp.valueOf(locDateTime));
}
@Override
public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
}
}
Exemple d'entité
@Entity
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Column
private LocalDate date;
@Column
private LocalDateTime dateTime;
...
}
Dans mon cas (Spring Boot 1.5.10), tout ce que j'avais à faire était d'ajouter ce qui suit dans la section des propriétés de pom.xml
<hibernate.version>5.2.12.Final</hibernate.version>
Il semble que par défaut cette version de Spring Boot utilise Hibernate 5.0.12.Final
Vous n'avez pas spécifié le type de colonne pour last_grading_date
dans hibernate . Vous pouvez utiliser:
@Column(name = "last_grading_date", nullable = true)
@Type(type="date")
private LocalDate lastGradingDate;
Remplacez LocalDate
class par Java.sql.Date
si cela ne fonctionne pas.