こんにちわ、みけです。
最近、ずっとJava8のDate and Time APIをいじってきましたが、
多分、これでおわりです。
java.time.Instant
新APIと古いAPIでの相互変換はjava.time.Instant
を経由して行います。
@RunWith(Enclosed.class) public class DateTimeTest { //ゾーンなしフォーマット private static final DateTimeFormatter withoutZone = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //ゾーンつきフォーマット private static final DateTimeFormatter withZone = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss Z"); //東京 private static final ZoneId ASIA_TOKYO = ZoneId.of("Asia/Tokyo"); //GMT private static final ZoneId GMT = ZoneId.of("GMT"); //日時(ゾーン込み) private static final String DATE_TIME = "2014/10/24 14:09:20 +0900"; //メッセージをキューから出力するクラス private synchronized static void log(Queue<Output> outputs) {/* 省略 */} //メッセージとメソッド名とクラス名を文字列に連結するクラス private static class Output {/* 省略 */} public static class DateとLocalDateTimeの相互変換 { private static final String CLASS = ZonedDateTimeとLocalDateTimeの相互変換.class.getSimpleName(); private static final Queue<Output> QUEUE = new ConcurrentLinkedQueue<>(); //日時(Date用) private static final String DATE = "2014/10/24 14:09:20"; //フォーマット(Date用) private static final String pattern = "yyyy/MM/dd HH:mm:ss"; @Rule public TestName testName = new TestName(); @Test public void DateからLocalDateTimeへはInstantを経由して変換() throws ParseException { Date dateType = new SimpleDateFormat(pattern).parse(DATE); QUEUE.offer( new Output("Date [" + new SimpleDateFormat(pattern).format(dateType) + ']', CLASS, testName)); Instant instant = dateType.toInstant(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ASIA_TOKYO); QUEUE.offer(new Output("LocalDateTime [" + withoutZone.format(localDateTime) + ']', CLASS, testName)); } @Test public void LocalDateTimeからDateへもInstantを経由して変換() throws ParseException { LocalDateTime localDateTime = LocalDateTime.parse(DATE_TIME, withZone); QUEUE.offer(new Output("LocalDateTime [" + withoutZone.format(localDateTime) + ']', CLASS, testName)); Instant instant = localDateTime.toInstant(ZoneOffset.ofHours(9)); Date dateType = Date.from(instant); QUEUE.offer( new Output("Date [" + new SimpleDateFormat(pattern).format(dateType) + ']', CLASS, testName)); } //キューのメッセージを出力 @AfterClass public static void outputMessages() { log(QUEUE); } } }
最初のテストDateからLocalDateTimeへはInstantを経由して変換
では、
Date
からLocalDateTime
への変換が行われます。
Date
からInstant
を取り出すメソッドはDate#toInstant
です。
Instant
からLocalDateTime
に変換するメソッドは
LocalDateTime#ofInstant(Instant, ZoneId)
です。
これによって、Date
からLocalDateTime
に変換できます。
出力は次のとおりです。
Date [2014/10/24 14:09:20] LocalDateTime [2014/10/24 14:09:20]
次のテストLocalDateTimeからDateへもInstantを経由して変換
は、
最初のテストの逆で、LocalDateTime
からDate
への変換を行います。
LocalDateTime
からInstant
を取り出すメソッドは
LocalDateTime#toInstant(ZoneOffset)
です。
一方、Instant
からDate
に変換するメソッドはDate#from(Instant)
です。
これによって、LocalDateTime
からDate
に変換できます。
出力は次のとおりです。
LocalDateTime [2014/10/24 14:09:20] Date [2014/10/24 14:09:20]
JPAとLocalDateTime
最後にJPAでLocalDateTime
を永続化する場合の方法です。
JPAでLocalDateTime
を含んだエンティティを保存する場合、
次のようにします。
javax.persistence.AttributeConverter<LocalDateTime, java.sql.Timestamp>
を実装したクラスを作成する- 1.で実装したクラスに
@Converter
アノテーションを付与する @Converter
アノテーションのautoApply
にtrue
を設定する- エンティティクラスの
LocalDateTime
型のフィールドに、@Convert
アノテーションを付与する @Convert
のconverter
に1.で実装したクラスを指定しますpersistence.xml
のclass
エレメントに1.で作成したクラスを追加する
1.のコンバーターは次のような感じです。
@Converter(autoApply = true) public class LocalDateTimeToTimestampConverter implements AttributeConverter<LocalDateTime, Timestamp> { //データベースに保存するときに使われる(LocalDateTime→Timestamp) @Override public Timestamp convertToDatabaseColumn(LocalDateTime localDateTime) { return Timestamp.valueOf(localDateTime); } //データベースから復元するときに使われる(Timestamp→LocalDateTime) @Override public LocalDateTime convertToEntityAttribute(Timestamp timestamp) { return timestamp.toLocalDateTime(); } }
3.のエンティティクラスは次のような感じです。
@Entity @Table(name = "app_user") @NamedQuery(name = "find-user-by-user-id", query = "select u from User u where u.userId = :userId") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "user_id", nullable = false, length = 30) private String userId; //作成したコンバーターを@Convertで指定する @Column(nullable = false) @Convert(converter = LocalDateTimeToTimestampConverter.class) private LocalDateTime created; //作成したコンバーターを@Convertで指定する @Column(nullable = false) @Convert(converter = LocalDateTimeToTimestampConverter.class) private LocalDateTime updated; public User() {} public User(String userId, LocalDateTime created) { this.userId = userId; this.created = created; this.updated = created; } //getter、setterを省略 //toString、equals、hashCodeを省略 }
persistence.xml
の記述は次のような感じになります。
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1"> <persistence-unit name="test" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <!-- エンティティクラス --> <class>org.mikeneck.time.jpa.User</class> <!-- コンバーター --> <class>org.mikeneck.time.LocalDateTimeToTimestampConverter</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="hibernate.connection.url" value="jdbc:derby:db/test"/> <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver"/> <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyTenSixDialect"/> <property name="hibernate.hbm2ddl.auto" value="create"/> <property name="hibernate.connection.username" value=""/> <property name="hibernate.connection.password" value=""/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> </properties> </persistence-unit> </persistence>
さて、これで簡単なテストを作って、流してみると、
次のようなログが出力されます。
INFO: HHH000227: Running hbm2ddl schema export Hibernate: create table app_user ( id bigint generated by default as identity, created timestamp not null, updated timestamp not null, user_id varchar(30) not null, primary key (id) )
DDLがtimestamp
型で形成されている様子がわかります。
というわけで、JPAでもLocalDateTime
のフィールドを保存できそうです。
また、既存のエンティティに関してもLocalDateTime
に
移行させることは可能そうです。
また、参考にしたのが下記のページですが、
java.sql.Date
に対してはLocalDate
型と変換するようです。
Using the Java 8 DateTime Classes with JPA!
なお、ZonedDateTime
やOffsetDateTime
のようなゾーンつきの
日時については、JDBCにそもそもそのような概念がないらしいので、
文字列で保存するようです(´・ω・`)
まあ、文字列からZonedDateTime
を生成するのは、
お手のものですけどね。
以上