したがって、短いバージョンでは、何らかの文字エンコーディングの問題があるか、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>
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'
エンティティ:
@Entity
@Table(name = "person")
public @Data class Person {
...
@Column(name = "last_grading_date", nullable = true)
private LocalDate lastGradingDate;
}
スプリングブート自動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
編集:さらに掘り下げた後、「validate」が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]
私の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
に基づいて構築されています。
JPA 2.1は、Java 8の前にリリースされ、Date and Time APIはその時点では存在しませんでした。したがって、@ TemporalアノテーションはタイプJava.util.DateおよびJava.util.Calendarの属性に適用されます
LocalDate
属性をDATE
列に、またはLocalDateTime
をTIMESTAMP
列に格納する場合は、Java.sql.Date
またはJava.sql.Timestamp
へのマッピングを定義する必要がありますあなた自身。
属性コンバーターはJPA 2.1仕様の一部であるため、任意のJPA 2.1実装で使用できます。 HibernateまたはEclipseLink。以下の例では、Wildfly 8.2とHibernate 4.3を使用しました。
LocalDateの変換
次のコードスニペットを見るとわかるように、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());
}
}
2つのメソッドconvertToDatabaseColumn
およびconvertToEntityAttribute
を使用してAttributeConverter<LocalDate, Date>
インターフェースを実装する必要があります。メソッド名を見るとわかるように、そのうちの1つはエンティティー属性のタイプ(LocalDate
)からデータベース列タイプ(Date
)への変換を定義し、もう1つは逆変換を定義します。 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;
...
}
私の場合(Spring Boot 1.5.10)必要なのは、pom.xmlのプロパティセクションに以下を追加することだけです。
<hibernate.version>5.2.12.Final</hibernate.version>
デフォルトでは、このバージョンのSpring BootはHibernate 5.0.12を使用しているようです。
Hibernateでlast_grading_date
の列のタイプを指定しませんでした。以下を使用できます。
@Column(name = "last_grading_date", nullable = true)
@Type(type="date")
private LocalDate lastGradingDate;
それが機能しない場合は、LocalDate
クラスをJava.sql.Date
に変更します。