web-dev-qa-db-fra.com

Pourquoi ai-je une exception JdbcSQLException (caractères non hexadécimaux) avec mon application de base de données H2/démarrage Spring?

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]
10
DaFoot

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.

12
Patrick

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;

    ...

}
2
Vazgen Torosyan

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

1
ashario

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.

0
Asoub