为啥我的 H2 数据库/Spring Boot 应用程序会出现 JdbcSQLException(非十六进制字符)?

Posted

技术标签:

【中文标题】为啥我的 H2 数据库/Spring Boot 应用程序会出现 JdbcSQLException(非十六进制字符)?【英文标题】:Why am I getting JdbcSQLException (non-hex characters) with my H2 database / Spring boot application?为什么我的 H2 数据库/Spring Boot 应用程序会出现 JdbcSQLException(非十六进制字符)? 【发布时间】:2017-03-08 20:06:26 【问题描述】:

所以简短的版本,我猜我有某种字符编码问题,或者数据库正在以 Hibernate/Spring-jpa 出于某种原因不喜欢的格式存储/返回日期。

但如果我能弄清楚出了什么问题,我会感到不安!

使用 Hibernate 5 在实体道具中使用 J8 LocalDate 内容。

正在创建数据库并且数据插入正常(您将在下面的日志 sn-p 中看到我得到了一个日期值)。

记录sn-p:

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>

分级:

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'

实体:

@Entity
@Table(name = "person")
public @Data class Person 
   ...
   @Column(name = "last_grading_date", nullable = true)
   private LocalDate lastGradingDate;

Spring boot 自动DB创建脚本sn-ps:

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

属性(在我添加下面的编码属性之前和之后出现问题):

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

编辑: 经过一番挖掘后,我发现“验证”是 spring.jpa.hibernate.ddl-auto 属性的设置。所以我尝试了。

我现在在启动过程中遇到以下错误...

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]

【问题讨论】:

org.hibernate:hibernate-java8 添加为依赖项,否则它将不知道如何在Java8 日期时间对象之间进行转换。 【参考方案1】:

我通过在我的 pom 中添加这个依赖来让它工作:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>$hibernate.version</version>
</dependency>

我不知道为什么它不能开箱即用,但是通过这种依赖关系它可以解决问题。

我还在属性下添加了这个属性:&lt;hibernate.version&gt;5.0.5.Final&lt;/hibernate.version&gt;

我的复制示例代码:

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>


人物

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

应用

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

结果:2015-02-20


在GitHub 上添加了一个工作示例。该演示基于Spring-bootJava 8Hibernate 5mavenjava.time.LocalDate

【讨论】:

我使用的是 CrudRepo 而不是 JpaRepo,更改为 JpaRepo 并没有帮助。我也再次将@type 注释更改为 java.time.LocalDate,仍然是同样的错误(十六进制字符串包含非十六进制字符:“2016-03-23”)。 @DaFoot 您是否在应用程序中添加了依赖项?我认为你需要这个库才能让它工作。如果没有,我将添加我的 pom.xml,您可以尝试使用 maven。 是的,尝试添加依赖项。认为这里可能还有其他需要付出的代价。 @DaFoot 我在github 上添加了我的工作项目。克隆它并尝试它是否适合您。但它建立在 Maven 之上。希望这没问题。 哇,谢谢。签出,似乎按预期工作。令人沮丧!【参考方案2】:

JPA 2.1 在 Java 8 之前发布,而日期和时间 API 在那个时间点根本不存在。因此,@Temporal 注解只能应用于 java.util.Date 和 java.util.Calendar 类型的属性。

如果您想在DATE 列中存储LocalDate 属性或在TIMESTAMP 列中存储LocalDateTime,您需要自己定义到java.sql.Datejava.sql.Timestamp 的映射。

属性转换器是 JPA 2.1 规范的一部分,因此可以与任何 JPA 2.1 实现一起使用,例如休眠或 EclipseLink。我在以下示例中使用了 Wildfly 8.2 和 Hibernate 4.3。

转换 LocalDate

正如您在以下代码 sn-p 中看到的,您无需为 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());
    

您需要使用convertToDatabaseColumnconvertToEntityAttribute 两个方法来实现AttributeConverter&lt;LocalDate, Date&gt; 接口。正如您在方法名称上看到的,其中一个定义了从实体属性类型(LocalDate)到数据库列类型(Date)的转换,另一个定义了逆转换。转换本身非常简单,因为java.sql.Date 已经提供了与LocalDate 进行转换的方法。

此外,属性转换器需要使用@Converter 注释进行注释。由于可选的 autoApply=true 属性,转换器将应用于 LocalDate 类型的所有属性。如果您想为每个属性单独定义转换器的用法,请查看此处。

属性的转换对开发者来说是透明的,LocalDate 属性可以用作任何其他实体属性。例如,您可以将其用作查询参数。

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

转换 LocalDateTime

LocalDateTime 的属性转换器基本相同。您需要实现AttributeConverter&lt;LocalDateTime, Timestamp&gt; 接口,并且转换器需要使用@Converter 注解进行注解。与LocalDateConverter 类似,LocalDateTimejava.sql.Timestamp 之间的转换是通过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());
    

示例实体

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

    ...


【讨论】:

我正在尝试将 Hibernate 5 与 Java8 一起使用,它应该能够在没有转换器步骤的情况下进行转换。【参考方案3】:

在我的情况下(Spring Boot 1.5.10),我需要做的就是将以下内容添加到 pom.xml 属性部分

<hibernate.version>5.2.12.Final</hibernate.version>

这个版本的 Spring Boot 看起来默认使用 Hibernate 5.0.12.Final

【讨论】:

【参考方案4】:

您没有在休眠中为last_grading_date 指定列的类型。 您可以使用:

@Column(name = "last_grading_date", nullable = true)
@Type(type="date")
private LocalDate lastGradingDate;

如果这不起作用,请将 LocalDate 类更改为 java.sql.Date

【讨论】:

感谢您的建议。这会导致不同的错误,无论是否提到 hibernate-java8 依赖项:Can not set java.time.LocalDate field ... to java.sql.Date 我的意思是,你应该将 private Date lastGradingDate; 设为私有 Datejava.sql.Date 而不是 LocalDate 问题是关于使用 Java8 日期类。这应该可以从 HIbernate 5 中实现。 哦,是的,我应该更仔细地阅读。其他答案似乎解决了这个问题,通过使用正确的转换器添加依赖项(在这种情况下它似乎不需要@Type)

以上是关于为啥我的 H2 数据库/Spring Boot 应用程序会出现 JdbcSQLException(非十六进制字符)?的主要内容,如果未能解决你的问题,请参考以下文章

为啥带有嵌入式 H2 的 Spring Boot 会抛出“org.h2.message.DbException”错误?

为spring boot配置h2

使用 H2 数据库自动创建的 Spring Boot

如何从另一个 Spring Boot 应用程序访问一个 Spring Boot 应用程序的内存 H2 数据库

在 Spring Boot 中创建外键 - H2 数据库

如何在 Spring Boot 库中启用 H2 控制台?