为什么我的H2数据库/Spring引导应用程序获得JdbcSQLException(非十六进制字符)?

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

但是,如果我能找出哪里出了问题,我就大吃一惊!

使用Hibernate 5在实体道具中使用J8 LocalDate的东西。

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

日志片段:

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 auto DB 创建脚本片段:

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]

答案 1

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

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

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

我还在属性下添加了此属性:<hibernate.version>5.0.5.Final</hibernate.version>

我的重现示例代码:

数据.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

人员存储库

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构建。


答案 2

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

如果要将属性存储在列中或列中,则需要定义到 或自己的映射。LocalDateDATELocalDateTimeTIMESTAMPjava.sql.Datejava.sql.Timestamp

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

转换本地日期

正如您在以下代码片段中看到的那样,为 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());
    }
}

您需要使用其两个方法和 实现接口。正如您在方法名称上看到的,其中一个定义了从实体属性类型 () 到数据库列类型 () 的转换,另一个定义了反向转换。转换本身非常简单,因为已经提供了与 .AttributeConverter<LocalDate, Date>convertToDatabaseColumnconvertToEntityAttributeLocalDateDatejava.sql.DateLocalDate

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

属性的转换对开发人员是透明的,并且该属性可以用作任何其他实体属性。例如,您可以将其用作查询参数。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();

转换本地日期时间

的属性转换器基本相同。您需要实现接口,并且转换器需要使用@Converter注释进行注释。与 类似,a 和 a 之间的转换是使用 的转换方法完成的。LocalDateTimeAttributeConverter<LocalDateTime, Timestamp>LocalDateConverterLocalDateTimejava.sql.TimestampTimestamp

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

    ...

}

推荐