为啥我的 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>
我不知道为什么它不能开箱即用,但是通过这种依赖关系它可以解决问题。
我还在属性下添加了这个属性:<hibernate.version>5.0.5.Final</hibernate.version>
我的复制示例代码:
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-boot
、Java 8
、Hibernate 5
、maven
和java.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.Date
或java.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());
您需要使用convertToDatabaseColumn
和convertToEntityAttribute
两个方法来实现AttributeConverter<LocalDate, Date>
接口。正如您在方法名称上看到的,其中一个定义了从实体属性类型(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<LocalDateTime, Timestamp>
接口,并且转换器需要使用@Converter 注解进行注解。与LocalDateConverter
类似,LocalDateTime
和java.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;
设为私有 Date
是 java.sql.Date
而不是 LocalDate
问题是关于使用 Java8 日期类。这应该可以从 HIbernate 5 中实现。
哦,是的,我应该更仔细地阅读。其他答案似乎解决了这个问题,通过使用正确的转换器添加依赖项(在这种情况下它似乎不需要@Type)以上是关于为啥我的 H2 数据库/Spring Boot 应用程序会出现 JdbcSQLException(非十六进制字符)?的主要内容,如果未能解决你的问题,请参考以下文章
为啥带有嵌入式 H2 的 Spring Boot 会抛出“org.h2.message.DbException”错误?