2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PostgreSQLでtimestamp with time zone型のカラムを使った時に、MyBatis GeneratorでOffsetDateTime型のフィールドにしたい

Last updated at Posted at 2022-10-15

What's?

PostgreSQLでtimestamp with time zone型のカラムを定義して、MyBatis GeneratorでModelを生成すると困ったことになったので、それをなんとかしたいという話です。

具体的には、timestamp with time zone型のカラムであればModelのプロパティとしてはOffsetDateTime型になって欲しいところですが、LocalDateTimeになってしまいます。

まずは、アプリケーションを作成して確認してみましょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-128-generic", arch: "amd64", family: "unix"

PostgreSQLは、Dockerコンテナで用意。タイムゾーンはAsia/Tokyoにしました。

$ docker container run -it --rm -p 5432:5432 \
  -e POSTGRES_DB=example \
  -e POSTGRES_USER=charon \
  -e POSTGRES_PASSWORD=password \
  -e TZ=Asia/Tokyo \
  --name postgres \
  postgres:14.5

使用するテーブルは、以下の定義で作成済みとします。

create table test_table(
  id integer,
  created timestamp with time zone,
  primary key(id)
);

Spring Bootプロジェクトを作成する

アプリケーションは、Spring Bootプロジェクトとして作成することにします。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.7.4 \
  -d javaVersion=17 \
  -d name=spring-boot-mybatis-generator-postgres-timestamptz \
  -d groupId=com.example \
  -d artifactId=spring-boot-mybatis-generator-postgres-timestamptz \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=com.example.spring.mybatis \
  -d dependencies=mybatis,postgresql \
  -d baseDir=spring-boot-mybatis-generator-postgres-timestamptz | tar zxvf -

プロジェクト内に移動。

$ cd spring-boot-mybatis-generator-postgres-timestamptz

生成されたpom.xmlの主要設定。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.mybatis.spring.boot</groupId>
                        <artifactId>mybatis-spring-boot-starter</artifactId>
                        <version>2.2.2</version>
                </dependency>

                <dependency>
                        <groupId>org.postgresql</groupId>
                        <artifactId>postgresql</artifactId>
                        <scope>runtime</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>

生成されたソースコードは、今回は使わないので削除しておきます。

$ rm src/main/java/com/example/spring/mybatis/SpringBootMybatisGeneratorPostgresTimestamptzApplication.java src/test/java/com/example/spring/mybatis/SpringBootMybatisGeneratorPostgresTimestamptzApplicationTests.java

Spring BootおよびMyBatisの設定は、以下のようにしました。

src/main/resources/application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/example
spring.datasource.username=charon
spring.datasource.password=password

mybatis.configuration.map-underscore-to-camel-case=true

MyBatis Generatorを使ってModelを作成して、とりあえず動かしてみる

では、まずはふつうにMyBatis Generatorを導入してアプリケーションを作り、動かしてみましょう。

MyBatis Generatorは、以下のように導入。

pom.xml
        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                        <plugin>
                                <groupId>org.mybatis.generator</groupId>
                                <artifactId>mybatis-generator-maven-plugin</artifactId>
                                <version>1.4.1</version>
                                <configuration>
                                        <configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
                                        <overwrite>true</overwrite>
                                        <includeAllDependencies>true</includeAllDependencies>
                                </configuration>
                        </plugin>
                </plugins>
        </build>

MyBatis Generatorの設定。生成するのは、Modelのみにしています。

src/main/resources/generatorConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="tables" targetRuntime="MyBatis3">
        <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
        <plugin type="org.mybatis.generator.plugins.MapperAnnotationPlugin"/>
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <jdbcConnection
                driverClass="org.postgresql.Driver"
                connectionURL="jdbc:postgresql://localhost:5432/example"
                userId="charon"
                password="password"/>

        <javaTypeResolver>
            <property name="useJSR310Types" value="true"/>
        </javaTypeResolver>

        <javaModelGenerator
                targetPackage="com.example.spring.mybatis.model"
                targetProject="src/main/java"/>

        <table tableName="test_table"/>
    </context>
</generatorConfiguration>

MyBatis Generatorを実行。

$ mvn mybatis-generator:generate

生成されたModelを確認してみます。
※Javadocコメントは省略しています

src/main/java/com/example/spring/mybatis/model/TestTable.java
package com.example.spring.mybatis.model;

import java.time.LocalDateTime;

public class TestTable {
    private Integer id;

    private LocalDateTime created;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public LocalDateTime getCreated() {
        return created;
    }

    public void setCreated(LocalDateTime created) {
        this.created = created;
    }

    省略
}

timestamp with time zone型のカラムに対応するプロパティが、LocalDateTime型になってしまっています。

    private LocalDateTime created;

MyBatis GeneratorはJDBCのDatabaseMetaDataを使ってカラムの情報を取得しているのですが、PostgreSQLのJDBCドライバーがtimestamp型でもtimestamp with time zone型でもjava.sql.Types#TIMESTAMPを返してしまうため、このような結果になります。

MyBatis Generatorにもissueはありますが、「JDBCドライバーの問題だ」ということでクローズされています。

ちなみに、今回useJSR310Typesというプロパティをtrueに設定しているものの、これを指定しなくてもjava.sql.Types#TIMESTAMP_WITH_TIMEZONEの場合はOffsetDateTimeになるのがもともとの挙動のようです。

java.sql.Types#TIMESTAMP_WITH_TIMEZONEはPostgreSQLのJDBCドライバーは返さないのですが。

とりあえず、このままにして進めるとどうなるか確認してみます。

Mapperを作成。

src/main/java/com/example/spring/mybatis/mapper/TestTableMapper.java
package com.example.spring.mybatis.mapper;

import com.example.spring.mybatis.model.TestTable;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface TestTableMapper {
    @Insert("insert into test_table(id, created) values(#{id}, #{created})")
    int insert(TestTable testTable);

    @Select("select id, created from test_table where id = #{id}")
    TestTable selectById(Integer id);

    @Delete("truncate table test_table")
    int truncate();
}

mainメソッドを持ったクラス。

src/main/java/com/example/spring/mybatis/App.java
package com.example.spring.mybatis;

import java.time.LocalDateTime;

import com.example.spring.mybatis.mapper.TestTableMapper;
import com.example.spring.mybatis.model.TestTable;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App implements ApplicationRunner {
    TestTableMapper testTableMapper;

    public App(TestTableMapper testTableMapper) {
        this.testTableMapper = testTableMapper;
    }

    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        testTableMapper.truncate();

        TestTable testTable = new TestTable();
        testTable.setId(1);
        testTable.setCreated(LocalDateTime.of(2022, 10, 15, 18, 30, 0));

        testTableMapper.insert(testTable);

        TestTable selected = testTableMapper.selectById(1);
        System.out.printf("id = %d%n", selected.getId());
        System.out.printf("created = %s%n", selected.getCreated());
    }
}

生成された型がLocalDateTimeなので、いったんそれに合わせて実装。トランザクションは有効にしていません(後述)。

    @Override
    public void run(ApplicationArguments args) throws Exception {
        testTableMapper.truncate();

        TestTable testTable = new TestTable();
        testTable.setId(1);
        testTable.setCreated(LocalDateTime.of(2022, 10, 15, 18, 30, 0));

        testTableMapper.insert(testTable);

        TestTable selected = testTableMapper.selectById(1);
        System.out.printf("id = %d%n", selected.getId());
        System.out.printf("created = %s%n", selected.getCreated());
    }

実行。

$ mvn spring-boot:run

すると、データの取得時に例外が発生します。

Caused by: org.springframework.jdbc.BadSqlGrammarException: Error attempting to get column 'created' from result set.  Cause: org.postgresql.util.PSQLException: TIMESTAMPTZ型のカラムの値を指定の型 java.time.LocalDateTime に変換できませんでした。
; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: TIMESTAMPTZ型のカラムの値を指定の型 java.time.LocalDateTime に変換できませんでした。
        at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:101) ~[spring-jdbc-5.3.23.jar:5.3.23]
        at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70) ~[spring-jdbc-5.3.23.jar:5.3.23]
        at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:79) ~[spring-jdbc-5.3.23.jar:5.3.23]
        at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:79) ~[spring-jdbc-5.3.23.jar:5.3.23]
        at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:91) ~[mybatis-spring-2.0.7.jar:2.0.7]
        at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441) ~[mybatis-spring-2.0.7.jar:2.0.7]
        at jdk.proxy2/jdk.proxy2.$Proxy46.selectOne(Unknown Source) ~[na:na]
        at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:160) ~[mybatis-spring-2.0.7.jar:2.0.7]
        at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86) ~[mybatis-3.5.9.jar:3.5.9]
        at jdk.proxy2/jdk.proxy2.$Proxy51.selectById(Unknown Source) ~[na:na]
        at com.example.spring.mybatis.App.run(App.java:36) ~[classes/:na]
        at com.example.spring.mybatis.App$$FastClassBySpringCGLIB$$41d58896.invoke(<generated>) ~[classes/:na]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.23.jar:5.3.23]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) ~[spring-aop-5.3.23.jar:5.3.23]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.23.jar:5.3.23]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.23.jar:5.3.23]
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.23.jar:5.3.23]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.23.jar:5.3.23]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.23.jar:5.3.23]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.23.jar:5.3.23]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.23.jar:5.3.23]
        at com.example.spring.mybatis.App$$EnhancerBySpringCGLIB$$adaa3109.run(<generated>) ~[classes/:na]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762) ~[spring-boot-2.7.4.jar:2.7.4]
        ... 5 common frames omitted
Caused by: org.postgresql.util.PSQLException: TIMESTAMPTZ型のカラムの値を指定の型 java.time.LocalDateTime に変換できませんでした。
        at org.postgresql.jdbc.PgResultSet.getLocalDateTime(PgResultSet.java:727) ~[postgresql-42.3.7.jar:42.3.7]
        at org.postgresql.jdbc.PgResultSet.getObject(PgResultSet.java:3760) ~[postgresql-42.3.7.jar:42.3.7]
        at org.postgresql.jdbc.PgResultSet.getObject(PgResultSet.java:3780) ~[postgresql-42.3.7.jar:42.3.7]
        at com.zaxxer.hikari.pool.HikariProxyResultSet.getObject(HikariProxyResultSet.java) ~[HikariCP-4.0.3.jar:na]
        at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:38) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.type.LocalDateTimeTypeHandler.getNullableResult(LocalDateTimeTypeHandler.java:28) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:85) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java:561) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue(DefaultResultSetHandler.java:403) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap(DefaultResultSetHandler.java:355) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValues(DefaultResultSetHandler.java:329) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSet(DefaultResultSetHandler.java:302) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:195) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) ~[mybatis-3.5.9.jar:3.5.9]
        at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76) ~[mybatis-3.5.9.jar:3.5.9]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427) ~[mybatis-spring-2.0.7.jar:2.0.7]
        ... 25 common frames omitted

timestamp with time zone型のカラムをLocalDateTimeで扱おうとして失敗しているみたいです。

Caused by: org.postgresql.util.PSQLException: TIMESTAMPTZ型のカラムの値を指定の型 java.time.LocalDateTime に変換できませんでした。

PostgreSQLのJDBCドライバーとしても、OffsetDateTimeで扱うべきなんですよね。

Issuing a Query and Processing the Result / Using Java 8 Date and Time classes

ちなみに、insert文はLocalDateTimeのままでも動いたりはします。
※これを確認(ロールバックされないようにする)ためにトランザクションを使わないことにしました

example=# select * from test_table;
 id |        created
----+------------------------
  1 | 2022-10-15 18:30:00+09
(1 row)

まあ、データが取得できないのはふつうに困りますね。

columnOverrideを使って対応する型を上書きする

で、どうするかですが。

OffsetDateTimeを使うという前提に立つ場合、正攻法としてはissueに書かれているとおりcolumnOverride要素のjavaType属性を使います。

こちらですね。

The Element / Optional Attributes

generatorConfig.xmltableの部分を以下のように修正。カラム単位で対応するJavaの型を指定します。

src/main/resources/generatorConfig.xml
        <!--
        <table tableName="test_table"/>
        -->
        <table tableName="test_table">
            <columnOverride column="created" javaType="java.time.OffsetDateTime"/>
        </table>

再度Modelを生成。

$ mvn mybatis-generator:generate

生成されたModelを確認してみます。

src/main/java/com/example/spring/mybatis/model/TestTable.java
package com.example.spring.mybatis.model;

import java.time.OffsetDateTime;

public class TestTable {
    private Integer id;

    private OffsetDateTime created;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public OffsetDateTime getCreated() {
        return created;
    }

    public void setCreated(OffsetDateTime created) {
        this.created = created;
    }

    省略
}

OffsetDateTimeが使われるようになりましたね。

    private OffsetDateTime created;

当然のことながらsetterを呼び出している箇所はコンパイルが通らなくなるので、以下のように修正。

src/main/java/com/example/spring/mybatis/App.java
        TestTable testTable = new TestTable();
        testTable.setId(1);
        //testTable.setCreated(LocalDateTime.of(2022, 10, 15, 18, 30, 0));
        testTable.setCreated(OffsetDateTime.of(
                        LocalDateTime.of(2022, 10, 15, 18, 30, 0),
                        ZoneOffset.of("+09:00")
                )
        );

確認。

$ mvn spring-boot:run

今度はうまく動きました。

id = 1
created = 2022-10-15T09:30Z

ここまでで変更したソースコードのうち、一部しか載せていなかった部分を載せておきますね。

src/main/resources/generatorConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="tables" targetRuntime="MyBatis3">
        <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
        <plugin type="org.mybatis.generator.plugins.MapperAnnotationPlugin"/>
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <jdbcConnection
                driverClass="org.postgresql.Driver"
                connectionURL="jdbc:postgresql://localhost:5432/example"
                userId="charon"
                password="password"/>

        <javaTypeResolver>
            <property name="useJSR310Types" value="true"/>
        </javaTypeResolver>

        <javaModelGenerator
                targetPackage="com.example.spring.mybatis.model"
                targetProject="src/main/java"/>

        <!--
        <table tableName="test_table"/>
        -->
        <table tableName="test_table">
            <columnOverride column="created" javaType="java.time.OffsetDateTime"/>
        </table>
    </context>
</generatorConfiguration>
src/main/java/com/example/spring/mybatis/App.java
package com.example.spring.mybatis;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

import com.example.spring.mybatis.mapper.TestTableMapper;
import com.example.spring.mybatis.model.TestTable;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App implements ApplicationRunner {
    TestTableMapper testTableMapper;

    public App(TestTableMapper testTableMapper) {
        this.testTableMapper = testTableMapper;
    }

    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        testTableMapper.truncate();

        TestTable testTable = new TestTable();
        testTable.setId(1);
        //testTable.setCreated(LocalDateTime.of(2022, 10, 15, 18, 30, 0));
        testTable.setCreated(OffsetDateTime.of(
                        LocalDateTime.of(2022, 10, 15, 18, 30, 0),
                        ZoneOffset.of("+09:00")
                )
        );

        testTableMapper.insert(testTable);

        TestTable selected = testTableMapper.selectById(1);
        System.out.printf("id = %d%n", selected.getId());
        System.out.printf("created = %s%n", selected.getCreated());
    }
}

JavaTypeResolverのデフォルトの挙動を変更したい

columnOverrideを使ってカラム単位でデータ型を変更していくのが正攻法ではあるのですが、個々のカラムに対して設定が必要なので正直めんどうというか、ミスが起こりそうで嫌です…。

全体の挙動を変更する設定はなさそうなので、org.mybatis.generator.api.JavaTypeResolverの実装を変更するしか内容がします。

type属性で変更できそうですね。

デフォルトの実装クラスはJavaTypeResolverDefaultImplで、calculateTimestampTypeメソッドをオーバーライドすれば良さそうです。

ここからは、timestamp型に対して問答無用でOffsetDateTimeを使うというJavaTypeResolverの実装を作成します。

Mavenプロジェクトをもうひとつ用意。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>mybatis-generator-custom-type-resolver</artifactId>
    <version>0.0.1</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.4.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

calculateTimestampTypeメソッドをオーバーライドして、OffsetDateTimeを使うようにしました。

src/main/java/com/example/mybatis/generator/ForceOffsetDateTimeJavaTypeResolver.java
package com.example.mybatis.generator;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;

public class ForceOffsetDateTimeJavaTypeResolver extends JavaTypeResolverDefaultImpl {
    @Override
    protected FullyQualifiedJavaType calculateTimestampType(IntrospectedColumn column,
                                                            FullyQualifiedJavaType defaultType) {
        FullyQualifiedJavaType answer;

        if (useJSR310Types) {
            answer = new FullyQualifiedJavaType("java.time.OffsetDateTime");
        } else {
            answer = defaultType;
        }

        return answer;
    }
}

一応、useJSR310Typesは見るようにしています(有効にしない場合はjava.sql.Timestamp型にマッピングされます)。

こちらをmvn install

$ mvn install

元のSpring Bootプロジェクトに戻って、MyBatis Generatorのdependenciesに作成したモジュールを追加します。

pom.xml
			<plugin>
				<groupId>org.mybatis.generator</groupId>
				<artifactId>mybatis-generator-maven-plugin</artifactId>
				<version>1.4.1</version>
				<configuration>
					<configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
					<overwrite>true</overwrite>
					<includeAllDependencies>true</includeAllDependencies>
				</configuration>
				<dependencies>
					<dependency>
						<groupId>com.example</groupId>
						<artifactId>mybatis-generator-custom-type-resolver</artifactId>
						<version>0.0.1</version>
					</dependency>
				</dependencies>
			</plugin>

MyBatis Generatorの設定を変更し、先ほど作成したクラスをjavaTypeResolver要素のtype属性に指定します。

src/main/resources/generatorConfig.xml
        <javaTypeResolver type="com.example.mybatis.generator.ForceOffsetDateTimeJavaTypeResolver">
            <property name="useJSR310Types" value="true"/>
        </javaTypeResolver>

columnOverrideは不要になったので、削除します。

src/main/resources/generatorConfig.xml
        <table tableName="test_table"/>
        <!--
        <table tableName="test_table">
            <columnOverride column="created" javaType="java.time.OffsetDateTime"/>
        </table>
        -->

再度Modelを生成。

$ mvn mybatis-generator:generate

columnOverrideを使った時と同じように、OffsetDateTimeが使われるようになりました。

src/main/java/com/example/spring/mybatis/model/TestTable.java
package com.example.spring.mybatis.model;

import java.time.OffsetDateTime;

public class TestTable {
    private Integer id;

    private OffsetDateTime created;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public OffsetDateTime getCreated() {
        return created;
    }

    public void setCreated(OffsetDateTime created) {
        this.created = created;
    }

    省略
}

アプリケーションの実行結果は、先ほどと同じになるので省略します。

ケースバイケースかつ、ちょっと乱暴なのであんまりやりたくはないですが、JavaTypeResolverの実装を変更するやり方もあるというのは覚えておくとよいかもしれません。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?