mybatis/mybatis-dynamic-sql

Support LocalDate type abnormality

vt167098 opened this issue · 9 comments

springboot version: 2.7.17
jdk version: 17
mybatis-spring-boot-starter: 2.3.1
mybatis-dynamic-sql: 1.5.0
mybatis-typehandlers-jsr310: 1.0.2
connection pool: hikari

It works normally when using java.util.date, but an error occurs when using java.time.localdate instead.
The error message is as follows:
[ERROR] contextLoads Time elapsed: 0.243 s <<< ERROR! org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'create_date' from result set. Cause: java.lang.ClassCastException: class java.sql.Date cannot be cast to class java.time.LocalDate (java.sql.Date is in module java.sql of loader 'platform'; java.time.LocalDate is in module java.base of loader 'bootstrap') at com.vt.demo23.Demo23ApplicationTests.contextLoads(Demo23ApplicationTests.java:28) Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'create_date' from result set. Cause: java.lang.ClassCastException: class java.sql.Date cannot be cast to class java.time.LocalDate (java.sql.Date is in module java.sql of loader 'platform'; java.time.LocalDate is in module java.base of loader 'bootstrap') at com.vt.demo23.Demo23ApplicationTests.contextLoads(Demo23ApplicationTests.java:28) Caused by: java.lang.ClassCastException: class java.sql.Date cannot be cast to class java.time.LocalDate (java.sql.Date is in module java.sql of loader 'platform'; java.time.LocalDate is in module java.base of loader 'bootstrap') at com.vt.demo23.Demo23ApplicationTests.contextLoads(Demo23ApplicationTests.java:28)

java.time.localdate can work normally using jdbc template connection.

It is true that java.sql.Date cannot be cast to java.time.LocalDate. It seems your JDBC driver is returning java.sql.Date for the create_date column. You may need to change the column definition, or perhaps write your own MyBatis type handler.

This probably isn't related to MyBatis Dynamic SQL. It's how the base MyBatis library works. Let us know the database and driver you are using and the table definition - maybe we can help you a bit more.

I think this has nothing to do with the jdbc driver, I used another data framework(ex: Bean Searcher) and it work fine.
I have used javaTypeResolver
<javaTypeResolver> <property name="forceBigDecimals" value="false" /> <property name="useJSR310Types" value="true" /> </javaTypeResolver>
It's successfully generate LocalDate type.

If you want our help, you'll need to provide the information I asked for. Otherwise it is just wild guessing.

I just did a small local test and everything works as expected. You might try removing the "mybatis-typehandlers-jsr310" dependency - that is no longer needed as MyBatis supports these types natively now. Perhaps there is some conflict between that obsolete library and MyBatis core.

If you want our help, you'll need to provide the information I asked for. Otherwise it is just wild guessing.

Sorry, it's my fail. I didn't explain the problem clearly.

The following is the content of 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.17</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.vt</groupId>
	<artifactId>demo-23</artifactId>
	<version>0.0.1</version>
	<name>demo-23</name>
	<description>test localdate </description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.ibm.informix</groupId>
			<artifactId>jdbc</artifactId>
			<version>4.50.4.1</version>
		</dependency>
		<dependency>
		    <groupId>org.mybatis.spring.boot</groupId>
		    <artifactId>mybatis-spring-boot-starter</artifactId>
		    <version>2.3.1</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis.generator</groupId>
			<artifactId>mybatis-generator-core</artifactId>
			<version>1.4.2</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis.dynamic-sql</groupId>
			<artifactId>mybatis-dynamic-sql</artifactId>
			<version>1.5.0</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-lang3</artifactId>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

The following is the content of mybatis generator's 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>
	<properties resource="generator.properties" />
	<context id="db1" targetRuntime="MyBatis3DynamicSql">
		<!--可以自定義生成model的程式碼註釋 -->
		<commentGenerator>
			<!-- 是否去除自動生成的註釋 true:是 : false:否 -->
			<property name="suppressAllComments" value="true" />
			<property name="suppressDate" value="true" />
			<property name="addRemarkComments" value="true" />
		</commentGenerator>
		<!--配置資料庫連線 -->
		<jdbcConnection driverClass="${jdbc.driverClass}"
			connectionURL="${jdbc.db1.connectionURL}" userId="${jdbc.userId}"
			password="${jdbc.password}">
		</jdbcConnection>
		<javaTypeResolver>
			<property name="forceBigDecimals" value="false" />
			<property name="useJSR310Types" value="true" />
		</javaTypeResolver>
		<!--指定生成model的路徑 -->
		<javaModelGenerator
			targetPackage="com.vt.demo23.entity"
			targetProject="demo-23/src/main/java">
			<property name="enableSubPackages" value="true" />
			<property name="trimStrings" value="true" />
		</javaModelGenerator>
		<!--Build mapping file storage location -->
		<sqlMapGenerator targetPackage="com.vt.demo23.dao"
			targetProject="demo-23/src/main/java">
			<property name="enableSubPackages" value="true" />
		</sqlMapGenerator>
		<!--指定生成mapper介面的的路徑 -->
		<javaClientGenerator type="XMLMAPPER"
			targetPackage="com.vt.demo23.dao"
			targetProject="demo-23/src/main/java">
			<property name="enableSubPackages" value="true" />
		</javaClientGenerator>
		<table tableName="cntm070">
		</table>
	</context>
</generatorConfiguration>

The following is the content of Cntm070Mapper.java

package com.vt.demo23.dao;

import static com.vt.demo23.dao.Cntm070DynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;

import com.vt.demo23.entity.Cntm070;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.annotation.Generated;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter;
import org.mybatis.dynamic.sql.select.CountDSLCompleter;
import org.mybatis.dynamic.sql.select.SelectDSLCompleter;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.update.UpdateDSL;
import org.mybatis.dynamic.sql.update.UpdateDSLCompleter;
import org.mybatis.dynamic.sql.update.UpdateModel;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper;
import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils;

@Mapper
public interface Cntm070Mapper extends CommonCountMapper, CommonDeleteMapper, CommonInsertMapper<Cntm070>, CommonUpdateMapper {
    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    BasicColumn[] selectList = BasicColumn.columnList(item, itemAttr, itemDesc, taxType, createId, createDate, updateId, updateDate, outCode, dsgnCode);

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @Results(id="Cntm070Result", value = {
        @Result(column="item", property="item", jdbcType=JdbcType.CHAR, id=true),
        @Result(column="item_attr", property="itemAttr", jdbcType=JdbcType.CHAR),
        @Result(column="item_desc", property="itemDesc", jdbcType=JdbcType.CHAR),
        @Result(column="tax_type", property="taxType", jdbcType=JdbcType.CHAR),
        @Result(column="create_id", property="createId", jdbcType=JdbcType.CHAR),
        @Result(column="create_date", property="createDate", jdbcType=JdbcType.DATE),
        @Result(column="update_id", property="updateId", jdbcType=JdbcType.CHAR),
        @Result(column="update_date", property="updateDate", jdbcType=JdbcType.DATE),
        @Result(column="out_code", property="outCode", jdbcType=JdbcType.CHAR),
        @Result(column="dsgn_code", property="dsgnCode", jdbcType=JdbcType.CHAR)
    })
    List<Cntm070> selectMany(SelectStatementProvider selectStatement);

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("Cntm070Result")
    Optional<Cntm070> selectOne(SelectStatementProvider selectStatement);

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default long count(CountDSLCompleter completer) {
        return MyBatis3Utils.countFrom(this::count, cntm070, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int delete(DeleteDSLCompleter completer) {
        return MyBatis3Utils.deleteFrom(this::delete, cntm070, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int deleteByPrimaryKey(String item_) {
        return delete(c -> 
            c.where(item, isEqualTo(item_))
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int insert(Cntm070 row) {
        return MyBatis3Utils.insert(this::insert, row, cntm070, c ->
            c.map(item).toProperty("item")
            .map(itemAttr).toProperty("itemAttr")
            .map(itemDesc).toProperty("itemDesc")
            .map(taxType).toProperty("taxType")
            .map(createId).toProperty("createId")
            .map(createDate).toProperty("createDate")
            .map(updateId).toProperty("updateId")
            .map(updateDate).toProperty("updateDate")
            .map(outCode).toProperty("outCode")
            .map(dsgnCode).toProperty("dsgnCode")
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int insertMultiple(Collection<Cntm070> records) {
        return MyBatis3Utils.insertMultiple(this::insertMultiple, records, cntm070, c ->
            c.map(item).toProperty("item")
            .map(itemAttr).toProperty("itemAttr")
            .map(itemDesc).toProperty("itemDesc")
            .map(taxType).toProperty("taxType")
            .map(createId).toProperty("createId")
            .map(createDate).toProperty("createDate")
            .map(updateId).toProperty("updateId")
            .map(updateDate).toProperty("updateDate")
            .map(outCode).toProperty("outCode")
            .map(dsgnCode).toProperty("dsgnCode")
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int insertSelective(Cntm070 row) {
        return MyBatis3Utils.insert(this::insert, row, cntm070, c ->
            c.map(item).toPropertyWhenPresent("item", row::getItem)
            .map(itemAttr).toPropertyWhenPresent("itemAttr", row::getItemAttr)
            .map(itemDesc).toPropertyWhenPresent("itemDesc", row::getItemDesc)
            .map(taxType).toPropertyWhenPresent("taxType", row::getTaxType)
            .map(createId).toPropertyWhenPresent("createId", row::getCreateId)
            .map(createDate).toPropertyWhenPresent("createDate", row::getCreateDate)
            .map(updateId).toPropertyWhenPresent("updateId", row::getUpdateId)
            .map(updateDate).toPropertyWhenPresent("updateDate", row::getUpdateDate)
            .map(outCode).toPropertyWhenPresent("outCode", row::getOutCode)
            .map(dsgnCode).toPropertyWhenPresent("dsgnCode", row::getDsgnCode)
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default Optional<Cntm070> selectOne(SelectDSLCompleter completer) {
        return MyBatis3Utils.selectOne(this::selectOne, selectList, cntm070, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default List<Cntm070> select(SelectDSLCompleter completer) {
        return MyBatis3Utils.selectList(this::selectMany, selectList, cntm070, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default List<Cntm070> selectDistinct(SelectDSLCompleter completer) {
        return MyBatis3Utils.selectDistinct(this::selectMany, selectList, cntm070, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default Optional<Cntm070> selectByPrimaryKey(String item_) {
        return selectOne(c ->
            c.where(item, isEqualTo(item_))
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int update(UpdateDSLCompleter completer) {
        return MyBatis3Utils.update(this::update, cntm070, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    static UpdateDSL<UpdateModel> updateAllColumns(Cntm070 row, UpdateDSL<UpdateModel> dsl) {
        return dsl.set(item).equalTo(row::getItem)
                .set(itemAttr).equalTo(row::getItemAttr)
                .set(itemDesc).equalTo(row::getItemDesc)
                .set(taxType).equalTo(row::getTaxType)
                .set(createId).equalTo(row::getCreateId)
                .set(createDate).equalTo(row::getCreateDate)
                .set(updateId).equalTo(row::getUpdateId)
                .set(updateDate).equalTo(row::getUpdateDate)
                .set(outCode).equalTo(row::getOutCode)
                .set(dsgnCode).equalTo(row::getDsgnCode);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    static UpdateDSL<UpdateModel> updateSelectiveColumns(Cntm070 row, UpdateDSL<UpdateModel> dsl) {
        return dsl.set(item).equalToWhenPresent(row::getItem)
                .set(itemAttr).equalToWhenPresent(row::getItemAttr)
                .set(itemDesc).equalToWhenPresent(row::getItemDesc)
                .set(taxType).equalToWhenPresent(row::getTaxType)
                .set(createId).equalToWhenPresent(row::getCreateId)
                .set(createDate).equalToWhenPresent(row::getCreateDate)
                .set(updateId).equalToWhenPresent(row::getUpdateId)
                .set(updateDate).equalToWhenPresent(row::getUpdateDate)
                .set(outCode).equalToWhenPresent(row::getOutCode)
                .set(dsgnCode).equalToWhenPresent(row::getDsgnCode);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int updateByPrimaryKey(Cntm070 row) {
        return update(c ->
            c.set(itemAttr).equalTo(row::getItemAttr)
            .set(itemDesc).equalTo(row::getItemDesc)
            .set(taxType).equalTo(row::getTaxType)
            .set(createId).equalTo(row::getCreateId)
            .set(createDate).equalTo(row::getCreateDate)
            .set(updateId).equalTo(row::getUpdateId)
            .set(updateDate).equalTo(row::getUpdateDate)
            .set(outCode).equalTo(row::getOutCode)
            .set(dsgnCode).equalTo(row::getDsgnCode)
            .where(item, isEqualTo(row::getItem))
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int updateByPrimaryKeySelective(Cntm070 row) {
        return update(c ->
            c.set(itemAttr).equalToWhenPresent(row::getItemAttr)
            .set(itemDesc).equalToWhenPresent(row::getItemDesc)
            .set(taxType).equalToWhenPresent(row::getTaxType)
            .set(createId).equalToWhenPresent(row::getCreateId)
            .set(createDate).equalToWhenPresent(row::getCreateDate)
            .set(updateId).equalToWhenPresent(row::getUpdateId)
            .set(updateDate).equalToWhenPresent(row::getUpdateDate)
            .set(outCode).equalToWhenPresent(row::getOutCode)
            .set(dsgnCode).equalToWhenPresent(row::getDsgnCode)
            .where(item, isEqualTo(row::getItem))
        );
    }
}

The following is the content of Cntm070.java(mybatis generator)

package com.vt.demo23.entity;

import java.time.LocalDate;
import javax.annotation.Generated;

public class Cntm070 {
    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String item;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String itemAttr;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String itemDesc;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String taxType;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String createId;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private LocalDate createDate;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String updateId;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private LocalDate updateDate;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String outCode;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String dsgnCode;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getItem() {
        return item;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setItem(String item) {
        this.item = item == null ? null : item.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getItemAttr() {
        return itemAttr;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setItemAttr(String itemAttr) {
        this.itemAttr = itemAttr == null ? null : itemAttr.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getItemDesc() {
        return itemDesc;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setItemDesc(String itemDesc) {
        this.itemDesc = itemDesc == null ? null : itemDesc.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getTaxType() {
        return taxType;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setTaxType(String taxType) {
        this.taxType = taxType == null ? null : taxType.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getCreateId() {
        return createId;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setCreateId(String createId) {
        this.createId = createId == null ? null : createId.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public LocalDate getCreateDate() {
        return createDate;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setCreateDate(LocalDate createDate) {
        this.createDate = createDate;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getUpdateId() {
        return updateId;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setUpdateId(String updateId) {
        this.updateId = updateId == null ? null : updateId.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public LocalDate getUpdateDate() {
        return updateDate;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setUpdateDate(LocalDate updateDate) {
        this.updateDate = updateDate;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getOutCode() {
        return outCode;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setOutCode(String outCode) {
        this.outCode = outCode == null ? null : outCode.trim();
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getDsgnCode() {
        return dsgnCode;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setDsgnCode(String dsgnCode) {
        this.dsgnCode = dsgnCode == null ? null : dsgnCode.trim();
    }
}

The following is run command "mvn test"


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.7.17)

2023-11-10 13:45:54.272  INFO 22304 --- [           main] com.vt.demo23.Demo23ApplicationTests     : Starting Demo23ApplicationTests using Java 21.0.1 on 3PC112-72-W11N with PID 22304 (started by vt167 in C:\WorkSpace\sts\demo-23)
2023-11-10 13:45:54.275  INFO 22304 --- [           main] com.vt.demo23.Demo23ApplicationTests     : No active profile set, falling back to 1 default profile: "default"
2023-11-10 13:45:56.792  INFO 22304 --- [           main] com.vt.demo23.Demo23ApplicationTests     : Started Demo23ApplicationTests in 2.896 seconds (JVM running for 4.073)
2023-11-10 13:45:57.175  INFO 22304 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-11-10 13:46:04.454  INFO 22304 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 11.206 s <<< FAILURE! - in com.vt.demo23.Demo23ApplicationTests
[ERROR] contextLoads  Time elapsed: 7.73 s  <<< ERROR!
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'create_date' from result set.  Cause: java.lang.ClassCastException: class java.sql.Date cannot be cast to class java.time.LocalDate (java.sql.Date is in module java.sql of loader 'platform'; java.time.LocalDate is in module java.base of loader 'bootstrap')
	at com.vt.demo23.Demo23ApplicationTests.contextLoads(Demo23ApplicationTests.java:19)
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'create_date' from result set.  Cause: java.lang.ClassCastException: class java.sql.Date cannot be cast to class java.time.LocalDate (java.sql.Date is in module java.sql of loader 'platform'; java.time.LocalDate is in module java.base of loader 'bootstrap')
	at com.vt.demo23.Demo23ApplicationTests.contextLoads(Demo23ApplicationTests.java:19)
Caused by: java.lang.ClassCastException: class java.sql.Date cannot be cast to class java.time.LocalDate (java.sql.Date is in module java.sql of loader 'platform'; java.time.LocalDate is in module java.base of loader 'bootstrap')
	at com.vt.demo23.Demo23ApplicationTests.contextLoads(Demo23ApplicationTests.java:19)

2023-11-10 13:46:04.599  INFO 22304 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-11-10 13:46:11.708  INFO 22304 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   Demo23ApplicationTests.contextLoads:19 » MyBatisSystem nested exception is org...
[INFO] 
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  21.879 s
[INFO] Finished at: 2023-11-10T13:46:12+08:00
[INFO] ------------------------------------------------------------------------
[

It seems that it is indeed a problem that java.sql.date cannot be converted into java.time.LocalDate. Mybatis also indicates that it supports localdate, but I don't know how to solve this problem.

So...Informix. The issue is probably that the Informix JDBC driver does not support type conversion to LocalDate.

MyBatis takes a relatively simple approach for LocalDate - it calls the ResultSet.getObject() method and relies on the driver to do the type conversion. Here's the MyBatis built in type handler for reference: https://github.com/mybatis/mybatis-3/blob/master/src/main/java/org/apache/ibatis/type/LocalDateTypeHandler.java

Other frameworks may work differently, but this is how MyBatis works. So if the driver doesn't support the type conversion, you will see errors like this.

You can solve this by writing your own MyBatis type handler that calls ResultSet.getDate() and then converts the value to LocalDate.

I found an announcement claiming that the driver supports LocalDate.

JDBC 4.2 Compliance

As late as 2016, the JDBC driver for Informix was stuck on JDBC 3.0 standard, and was over a hundred APIs away from even reaching the 4.0 JDBC specification. Now I'm proud to say that the new 4.50.JC1 JDBC driver has reached the JDBC 4.2 required feature specifications parity.

JDBC 4.2 specification gives a new SQLType enum which is easier to use than the older Types.java constants class. Tt allows the use of LocalDate, LocalTime, LocalDateTime objects which are faster and sometimes easier to work with than the older Date/Time/Timestamp classes. Some APIs are not supported by the server itself, so you still might find some pieces unavailable. We have worked hard to enable as many APIs as possible so you have the flexibility to use as much of the JDBC specification as you need in your applications.

So, I did some tests and it seems that LocalDate is supported in PreparedStatement#setObject(), but not in ResultSet#getObject() [1].

@vt167098 ,
If you can contact Informix' technical support, please forward this information or share a link to this issue.
A workaround, as @jeffgbutler explained, is to write a custom type handler.

[1] When calling resultSet.getObject("columnName", LocalDate.class), the Informix driver silently returns java.sql.Date which violates the API contract (and causes the ClassCastException, obviously). The driver should either 1) return an instance of LocalDate or 2) throw an exception if they don't support returning LocalDate. We expect 1) from a JDBC 4.2 compliant driver, of course.

Thanks @harawata! This is great information.

OK, I will try to report this issue to informix. Thank you for your help on this issue.

Confirmed that it is a problem with the jdbc driver, then I will close this issue, thank you all.