SpringのLocalTimeをMySQLのTimeでINSERTしたい(millisecondsも)


概要

String型で取得した 11:12:13.14 (時間:分:秒.ミリ秒)を
MySQLにINSERTして

とミリ秒まで表示させる方法についてです。(日付情報はいらない)

そんなの普通に実装すればできそうじゃない?
って思うじゃないですか。

簡単そうに見えて面倒くさい処理を挟まないとうまくいきませんでした。。。
もっとスマートな方法をお存じの方がいましたら、教えていただけるととっても嬉しいです!!

バージョン

Spring

build.gradle
id 'org.springframework.boot' version '2.2.2.BUILD-SNAPSHOT'
sourceCompatibility = '1.8'

    runtimeOnly('mysql:mysql-connector-java')
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')

データベース

MySQL 8.0

ステップその1 テーブル作成

小数点3桁まで対応
MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.1.2 日付と時間型の概要

create table timesample( 
    ...
    start_time TIME(3)
);

ステップその2 AttributeConverterの作成

JPA がエンティティのフィールドの型としてjava.time.LocalDate、java.time.LocalTime、java.time.LocalDateTimeは対応していないため、次のようなAttributeConverterを作成する必要があります。

package app.entities.converter;

import java.time.LocalTime;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class LocalTimeConverter implements AttributeConverter<LocalTime,  java.sql.Time> {

    @Override
    public java.sql.Time convertToDatabaseColumn(LocalTime time) {
        return time == null ? null : java.sql.Time.valueOf(time);
    }

    @Override
    public LocalTime convertToEntityAttribute(java.sql.Time  value) {
        return value == null ? null : value.toLocalTime();
    }
}
Entity
    @Column(name = "start_time")
    private LocalTime startTime;
entity.setStartTime(LocalTime.of(11, 12, 13, 14*10000000));

repository.save(entity);

ミリ秒が入ってないぞ!

ステップその3 ミリ秒を保有してjava.sql.Timeに変換する

先程作成したLocalTimeConverterのconvertToDatabaseColumnメソッドを変更
spring-ミリ秒のjava.time.LocalTimeをミリ秒のjava.sql.Timeにマッピングする方法

LocalTimeConverter
package app.entities.converter;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class LocalTimeConverter implements AttributeConverter<LocalTime,  java.sql.Time> {

    @Override
    public java.sql.Time convertToDatabaseColumn(LocalTime time) {
        if(time == null) return null;

        // この日付はコンバートで捨てられるので何でもOK
        long epochMilli = time.atDate(LocalDate.of(2019, 1, 1)).atZone(java.time.ZoneId.systemDefault())
                .toInstant().toEpochMilli();
        java.sql.Time sqlTime = new java.sql.Time(epochMilli);
        return sqlTime;
    }

    @Override
    public LocalTime convertToEntityAttribute(java.sql.Time  value) {
        if (value == null) return null;

        String localTimeStr = new SimpleDateFormat("HH:mm:ss.SSS").format(value);
        LocalTime localTime = LocalTime.parse(localTimeStr);
        return localTime;
    }
}