org.mybatis.spring.MyBatisSystemException
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:97)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:439)
	at jdk.proxy2/jdk.proxy2.$Proxy110.selectList(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:164)
	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:77)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:152)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
	at jdk.proxy2/jdk.proxy2.$Proxy119.selectList(Unknown Source)
	at com.baomidou.mybatisplus.core.mapper.BaseMapper.selectOne(BaseMapper.java:306)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$DefaultMethodInvoker.invoke(MybatisMapperProxy.java:166)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
	at jdk.proxy2/jdk.proxy2.$Proxy119.selectOne(Unknown Source)
	at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.getOne(ServiceImpl.java:238)
	at com.baomidou.mybatisplus.extension.service.IService.getOne(IService.java:328)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:360)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:724)
	at cn.wh.dueremind.service.impl.ExcelImportRecordServiceImpl$$SpringCGLIB$$0.getOne(<generated>)
	at cn.wh.dueremind.DueremindApplicationTests.testQueryJnode(DueremindApplicationTests.java:246)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.apache.ibatis.reflection.ReflectionException: Error instantiating class cn.wh.dueremind.entity.ExcelImportRecord with invalid types (Long,String,String,String,ObjectNode,ObjectNode,ObjectNode,String,String,Integer,Integer,Byte,String,Byte,String,String,LocalDateTime,LocalDateTime) or values (3,BATCH_81a35c43_2025-11-04T092406.853202100,Employee_Insurance_Registration_Template,员工参保商业险导入模板_v2.xlsx,{"Row1": {"A1": "姓名name", "B1": "身份证号id_card", "C1": "电话phone", "D1": "用工单位employer", "E1": "岗位job_position", "F1": "要参保的保险公司requested_insurance_company", "G1": "要参保的保险类型insurance_type", "H1": "要参保的保单号requested_policy_no", "I1": "保险开始时间requested_start_date", "J1": "停保时间requested_end_date", "K1": "保额sum_insured", "L1": "保费premium", "M1": "备注notes"}, "Row2": {"A2": "李四", "B2": "530626198403232778", "C2": "18822224444", "D2": "吉大附中", "E2": "老师", "F2": "大地", "G2": "意外险", "H2": "12334", "I2": "2025-09-10", "J2": "2025-12-31", "K2": "300000", "L2": "275", "M2": "要最高保额的"}},{"Row1": {"A1": "STRING", "B1": "STRING", "C1": "STRING", "D1": "STRING", "E1": "STRING", "F1": "STRING", "G1": "STRING", "H1": "STRING", "I1": "STRING", "J1": "STRING", "K1": "STRING", "L1": "STRING", "M1": "STRING"}, "Row2": {"A2": "STRING", "B2": "STRING", "C2": "NUMERIC", "D2": "STRING", "E2": "STRING", "F2": "STRING", "G2": "STRING", "H2": "NUMERIC", "I2": "date", "J2": "date", "K2": "NUMERIC", "L2": "NUMERIC", "M2": "STRING"}},null,Row1,Row2,2,13,0,null,0,system,null,2025-11-04T09:24:09,2025-11-04T09:24:09). Cause: java.lang.IllegalArgumentException: argument type mismatch
	at org.apache.ibatis.reflection.factory.DefaultObjectFactory.instantiateClass(DefaultObjectFactory.java:86)
	at org.apache.ibatis.reflection.factory.DefaultObjectFactory.create(DefaultObjectFactory.java:53)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyConstructorAutomapping(DefaultResultSetHandler.java:780)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createByConstructorSignature(DefaultResultSetHandler.java:727)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createResultObject(DefaultResultSetHandler.java:689)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createResultObject(DefaultResultSetHandler.java:659)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue(DefaultResultSetHandler.java:411)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap(DefaultResultSetHandler.java:366)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValues(DefaultResultSetHandler.java:337)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSet(DefaultResultSetHandler.java:310)
	at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:202)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:66)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:80)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
	at jdk.proxy2/jdk.proxy2.$Proxy160.query(Unknown Source)
	at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:65)
	at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:336)
	at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:158)
	at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:110)
	at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:81)
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59)
	at jdk.proxy2/jdk.proxy2.$Proxy159.query(Unknown Source)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:154)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:142)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:425)
	... 25 more
Caused by: java.lang.IllegalArgumentException: argument type mismatch
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
	at org.apache.ibatis.reflection.factory.DefaultObjectFactory.instantiateClass(DefaultObjectFactory.java:77)
	... 58 more 

这个问题的根本原因是MyBatis在通过构造函数实例化对象时,无法正确处理ObjectNode类型的参数。即使你使用了JacksonTypeHandler,但在对象构造阶段类型处理器还没有生效。

package cn.wh.dueremind.entity;

import java.time.LocalDateTime;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
// @NoArgsConstructor 在报错时没有加无参构造函数。
// @AllArgsConstructor
@TableName(value = "excel_import_records", autoResultMap = true)
public class ExcelImportRecord {
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    // 导入基本信息
    @TableField("import_batch")
    private String importBatch;         // 导入批次号
    
    @TableField("template_type")
    private String templateType;        // 模板类型
    
    @TableField("original_filename")
    private String originalFilename;    // 原始文件名
    
    /*// 核心:整张表数据存入JSON
    @TableField("cell_values")
    private Object cellValues;          // 完整Excel数据
    
    @TableField("cell_types")
    private Object cellTypes;           // Excel单元格数据类型
    
    @TableField("cell_validations")
    private Object cellValidations;     // Excel单元格数据验证结构*/

    // 核心:整张表数据存入JSON
    @TableField(value = "cell_values", typeHandler = JacksonTypeHandler.class)  // JacksonTypeHandler 负责序列化:将 Java 对象转换为数据库可存储的格式 反序列化:从数据库读取数据并转换回 Java 对象
    // private JsonNode cellValues;          // 完整Excel数据
    private ObjectNode cellValues;
    // typeHandler = JacksonTypeHandler.class 负责序列化:将 Java 对象转换为数据库可存储的格式 反序列化:从数据库读取数据并转换回 Java 对象
    @TableField(value = "cell_types", typeHandler = JacksonTypeHandler.class)
    // private JsonNode cellTypes;           // Excel单元格数据类型
    private ObjectNode cellTypes;
    // typeHandler = JacksonTypeHandler.class 负责序列化:将 Java 对象转换为数据库可存储的格式 反序列化:从数据库读取数据并转换回 Java 对象
    @TableField(value = "cell_validations", typeHandler = JacksonTypeHandler.class)
    // private JsonNode cellValidations;     // Excel单元格数据验证结构
    private ObjectNode cellValidations;
    
    // 统计信息
    @TableField("first_row")
    private String firstRow;            // 首个有数据的行
    
    @TableField("last_row")
    private String lastRow;             // 最末尾的有数据的行
    
    @TableField("total_rows")
    private Integer totalRows;          // 总行数
    
    @TableField("total_columns")
    private Integer totalColumns;       // 总列数
    
    // 验证状态
    @TableField("validation_status")
    private Byte validationStatus;      // 0-未验证,1-验证通过,2-验证失败
    
    @TableField("validation_result")
    private String validationResult;    // 验证结果
    
    // 处理状态
    @TableField("process_status")
    private Byte processStatus;         // 0-待处理,1-处理中,2-处理完成
    
    // 系统字段
    @TableField("created_by")
    private String createdBy;
    
    @TableField("updated_by")
    private String updatedBy;           // 记录最后修改人
    
    @TableField("created_time")
    private LocalDateTime createdTime;
    
    @TableField("updated_time")
    private LocalDateTime updatedTime;

}

解决方案

方案1:使用无参构造函数 + Setter方法(推荐)

确保你的实体类有一个无参构造函数,并且所有字段都有对应的setter方法:

public class ExcelImportRecord {
    public ExcelImportRecord() {
        // 无参构造函数
    }
    
    // 其他有参构造函数可以保留,但必须有无参构造函数
    
    @TableField(value = "cell_values", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellValues;
    
    // 必须有setter方法
    public void setCellValues(ObjectNode cellValues) {
        this.cellValues = cellValues;
    }
    
    public ObjectNode getCellValues() {
        return cellValues;
    }
}

方案2:检查JacksonTypeHandler配置

确保你的JacksonTypeHandler正确配置:

@MappedTypes({ObjectNode.class, JsonNode.class, ArrayNode.class})
@MappedJdbcTypes(JdbcType.VARCHAR) // 或者 JdbcType.OTHER
public class JacksonTypeHandler extends BaseTypeHandler<ObjectNode> {
    
    private static final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, ObjectNode parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.toString());
    }
    
    @Override
    public ObjectNode getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        if (json != null) {
            try {
                return objectMapper.readValue(json, ObjectNode.class);
            } catch (Exception e) {
                throw new SQLException("Error parsing JSON", e);
            }
        }
        return null;
    }
    
    @Override
    public ObjectNode getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        if (json != null) {
            try {
                return objectMapper.readValue(json, ObjectNode.class);
            } catch (Exception e) {
                throw new SQLException("Error parsing JSON", e);
            }
        }
        return null;
    }
    
    @Override
    public ObjectNode getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        if (json != null) {
            try {
                return objectMapper.readValue(json, ObjectNode.class);
            } catch (Exception e) {
                throw new SQLException("Error parsing JSON", e);
            }
        }
        return null;
    }
}

方案3:在配置类中注册类型处理器

@Configuration
public class MybatisConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件等
        return interceptor;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            // 注册类型处理器
            configuration.getTypeHandlerRegistry().register(JacksonTypeHandler.class);
        };
    }
}

方案4:使用String类型接收,手动转换

如果上述方案仍有问题,可以暂时使用String类型:

public class ExcelImportRecord {
    
    @TableField(value = "cell_values")
    private String cellValuesJson;
    
    // 提供获取ObjectNode的方法
    public ObjectNode getCellValues() {
        if (cellValuesJson != null) {
            try {
                return new ObjectMapper().readValue(cellValuesJson, ObjectNode.class);
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }
    
    // 设置ObjectNode的方法
    public void setCellValues(ObjectNode cellValues) {
        if (cellValues != null) {
            this.cellValuesJson = cellValues.toString();
        } else {
            this.cellValuesJson = null;
        }
    }
}

方案5:检查数据库字段类型

确保数据库中的cell_values字段类型能够存储JSON数据:

-- 如果是MySQL
ALTER TABLE excel_import_record MODIFY cell_values JSON;
-- 或者
ALTER TABLE excel_import_record MODIFY cell_values TEXT;

-- 如果是PostgreSQL
ALTER TABLE excel_import_record ALTER COLUMN cell_values TYPE JSONB;

临时解决方案

如果急需解决问题,可以先使用原生SQL查询:

@Select("SELECT * FROM excel_import_record WHERE import_batch = #{importBatch}")
ExcelImportRecord findByImportBatch(String importBatch);

根本原因分析

MyBatis实例化对象有两种方式:

  1. 通过构造函数 - 需要参数类型完全匹配
  2. 通过无参构造函数+Setter - 类型处理器在此阶段生效

你的错误是因为MyBatis尝试使用构造函数实例化,但ObjectNode类型无法直接映射。方案1(无参构造函数)是最可靠的解决方案。

问题很明确了!你的实体类使用了 @Builder 注解,这会生成一个全参构造函数,但没有生成无参构造函数。MyBatis在实例化对象时需要一个无参构造函数。

解决方案

方案1:添加无参构造函数(推荐)

在类上添加 @NoArgsConstructor@AllArgsConstructor 注解:

package cn.wh.dueremind.entity;

import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor  // 添加这个注解
@AllArgsConstructor // 添加这个注解
@TableName(value = "excel_import_records", autoResultMap = true)
public class ExcelImportRecord {
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    // 导入基本信息
    @TableField("import_batch")
    private String importBatch;
    
    @TableField("template_type")
    private String templateType;
    
    @TableField("original_filename")
    private String originalFilename;
    
    @TableField(value = "cell_values", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellValues;
    
    @TableField(value = "cell_types", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellTypes;
    
    @TableField(value = "cell_validations", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellValidations;
    
    @TableField("first_row")
    private String firstRow;
    
    @TableField("last_row")
    private String lastRow;
    
    @TableField("total_rows")
    private Integer totalRows;
    
    @TableField("total_columns")
    private Integer totalColumns;
    
    @TableField("validation_status")
    private Byte validationStatus;
    
    @TableField("validation_result")
    private String validationResult;
    
    @TableField("process_status")
    private Byte processStatus;
    
    @TableField("created_by")
    private String createdBy;
    
    @TableField("updated_by")
    private String updatedBy;
    
    @TableField("created_time")
    private LocalDateTime createdTime;
    
    @TableField("updated_time")
    private LocalDateTime updatedTime;
}

方案2:手动添加无参构造函数

如果你不想使用 @NoArgsConstructor 注解,可以手动添加:

@Builder
@Data
@TableName(value = "excel_import_records", autoResultMap = true)
public class ExcelImportRecord {
    
    // 手动添加无参构造函数
    public ExcelImportRecord() {
    }
    
    // 其他字段保持不变...
    
    @TableField(value = "cell_values", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellValues;
    
    @TableField(value = "cell_types", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellTypes;
    
    @TableField(value = "cell_validations", typeHandler = JacksonTypeHandler.class)
    private ObjectNode cellValidations;
    
    // ... 其他字段
}

方案3:使用更完整的Lombok配置

@Builder
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)  // 保护性无参构造
@AllArgsConstructor(access = AccessLevel.PROTECTED) // 保护性全参构造
@TableName(value = "excel_import_records", autoResultMap = true)
public class ExcelImportRecord {
    // 字段定义保持不变...
}

验证解决方案

修改后,重新运行测试:

LambdaQueryChainWrapper<ExcelImportRecord> recordLambdaQueryChainWrapper = 
    excelImportRecordService.lambdaQuery()
        .eq(ExcelImportRecord::getImportBatch, "BATCH_81a35c43_2025-11-04T092406.853202100");
ExcelImportRecord one = recordLambdaQueryChainWrapper.one(); // 使用one()而不是getEntity()

额外建议

  1. 检查JacksonTypeHandler是否支持ObjectNode
    确保你的JacksonTypeHandler正确处理ObjectNode类型。

  2. 确认数据库字段类型

    -- 确保字段是TEXT或JSON类型
    DESC excel_import_records;
    
  3. 如果仍有问题,尝试使用JsonNode代替ObjectNode

    @TableField(value = "cell_values", typeHandler = JacksonTypeHandler.class)
    private JsonNode cellValues;
    

根本原因

  • @Builder 只生成全参构造函数,不生成无参构造函数
  • MyBatis需要通过无参构造函数实例化对象,然后通过setter方法设置属性
  • 当只有全参构造函数时,MyBatis尝试使用它,但ObjectNode类型无法正确映射

添加 @NoArgsConstructor 注解是最简单有效的解决方案。

思考过程:
根据异常信息,问题出现在MyBatis尝试实例化ExcelImportRecord类时,参数类型不匹配。异常中提到了类的构造参数类型为(Long,String,String,String,ObjectNode,ObjectNode,ObjectNode,String,String,Integer,Integer,Byte,String,Byte,String,String,LocalDateTime,LocalDateTime),但是传入的值中有一个是ObjectNode类型,而MyBatis在构造对象时可能无法直接处理。

你已经在cellValues字段上使用了@TableField(typeHandler = JacksonTypeHandler.class),但是异常仍然发生,可能有以下几个原因:

构造函数问题:ExcelImportRecord类可能有一个构造函数,其参数列表与数据库查询结果的列不匹配。特别是,MyBatis可能尝试使用构造函数来创建对象,而构造函数的参数类型与实际查询返回的列类型不匹配。

类型处理器未生效:虽然你指定了JacksonTypeHandler,但可能由于某些原因(例如,MyBatis配置、Spring集成等)导致类型处理器没有正确应用。

多个ObjectNode字段:从异常信息看,你的类中有三个ObjectNode类型的字段(对应数据库中的三列)。你可能需要为这三个字段都指定类型处理器。

MyBatis的自动映射与构造函数映射冲突:MyBatis可能首先尝试使用构造函数来创建对象,如果构造函数参数类型不匹配,就会报错。然后才会尝试使用无参构造函数并通过setter方法设置属性,但此时如果类型处理器没有正确应用,也会出错。

解决方案:

步骤1:确保所有ObjectNode字段都配置了类型处理器。
例如,如果你有三个ObjectNode字段,每个都应该加上@TableField(typeHandler = JacksonTypeHandler.class)。

步骤2:检查ExcelImportRecord类的构造函数。如果有一个包含所有参数的构造函数,请确保构造函数的参数类型与数据库中的列类型匹配。特别是,对于数据库中的JSON列,它们通常是以字符串形式存储的,但构造函数中却是ObjectNode类型,这会导致实例化失败。

建议:使用无参构造函数,然后通过setter方法注入属性。这样,MyBatis会使用无参构造函数创建对象,然后通过setter方法设置属性,此时类型处理器就会生效。

步骤3:如果确实需要使用有参构造函数,那么需要确保构造函数的参数类型与数据库查询返回的类型匹配。例如,如果数据库中cell_values列是字符串,那么构造函数中对应的参数应该是String类型,然后在构造函数内部自己使用Jackson将字符串转换为ObjectNode。但这样会比较麻烦。

步骤4:检查JacksonTypeHandler是否正确实现。确保它能够将数据库中的字符串(或JSON二进制)与ObjectNode之间进行转换。

步骤5:确认MyBatis配置中是否注册了类型处理器,或者在@TableField中指定类型处理器是否足够。在MyBatis中,可以通过在配置文件中注册类型处理器来全局处理,也可以使用注解。如果你使用了Spring Boot,可能需要在配置类中注册类型处理器。

例如,在Spring Boot中,你可以这样全局注册类型处理器:

java
@Configuration
public class MyBatisConfig {
@Bean
public MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MyBatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 扫描类型处理器包
sessionFactory.setTypeHandlersPackage(“com.yourpackage.handler”);
return sessionFactory;
}
}
或者,如果你使用的是MyBatis-Plus,可以在实体类的字段上使用@TableField(typeHandler = JacksonTypeHandler.class),并确保JacksonTypeHandler在MyBatis中已知。

步骤6:检查数据库中的列类型。对于存储JSON的列,通常使用varchar或json类型(如果数据库支持)。确保数据库中的列类型与类型处理器处理的数据类型匹配。

根据异常堆栈,问题出现在实例化时,所以很可能是构造函数参数类型不匹配。因此,我建议先检查构造函数。

如果问题仍然存在,请提供ExcelImportRecord类的定义,特别是构造函数的定义,以及JacksonTypeHandler的实现,这样我可以更准确地定位问题。

根据错误堆栈,问题在于MyBatis在通过构造函数实例化ExcelImportRecord时,参数类型不匹配。从错误信息中可以看到,它尝试使用一个包含Long,String,String,String,ObjectNode,ObjectNode,ObjectNode,…等类型的构造函数,但是传递的值中有些类型不匹配。

你使用了@Builder注解,这会生成一个全字段的构造器,而MyBatis在实例化对象时,如果没有无参构造器,可能会尝试使用这个全字段构造器。但是,由于数据库中的字段类型和Java类型不完全匹配(特别是ObjectNode类型),导致构造器调用失败。

解决方案:

确保实体类有一个无参构造器。因为MyBatis在实例化对象时,首先尝试无参构造器,然后通过setter方法设置属性。这样,类型处理器(JacksonTypeHandler)就会在setter方法被调用时生效,将数据库中的字符串转换为ObjectNode。

如果你使用了@Builder,那么通常会自动生成全参构造器,但不会生成无参构造器。因此,你需要显式地添加无参构造器。同时,为了保留@Builder的功能,你还需要一个全参构造器。

另一种方法是使用@NoArgsConstructor和@AllArgsConstructor注解。

修改后的实体类:

添加两个注解:@NoArgsConstructor和@AllArgsConstructor。这样就会生成无参构造器和全参构造器。

同时,确保JacksonTypeHandler能够正确转换。由于你已经在@TableField中指定了typeHandler,并且autoResultMap = true,所以MyBatis在通过setter设置属性时会使用类型处理器。

修改后的代码:

java
package cn.wh.dueremind.entity;

import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = “excel_import_records”, autoResultMap = true)
public class ExcelImportRecord {

@TableId(value = "id", type = IdType.AUTO)
private Long id;

@TableField("import_batch")
private String importBatch;

@TableField("template_type")
private String templateType;

@TableField("original_filename")
private String originalFilename;

@TableField(value = "cell_values", typeHandler = JacksonTypeHandler.class)
private ObjectNode cellValues;

@TableField(value = "cell_types", typeHandler = JacksonTypeHandler.class)
private ObjectNode cellTypes;

@TableField(value = "cell_validations", typeHandler = JacksonTypeHandler.class)
private ObjectNode cellValidations;

@TableField("first_row")
private String firstRow;

@TableField("last_row")
private String lastRow;

@TableField("total_rows")
private Integer totalRows;

@TableField("total_columns")
private Integer totalColumns;

@TableField("validation_status")
private Byte validationStatus;

@TableField("validation_result")
private String validationResult;

@TableField("process_status")
private Byte processStatus;

@TableField("created_by")
private String createdBy;

@TableField("updated_by")
private String updatedBy;

@TableField("created_time")
private LocalDateTime createdTime;

@TableField("updated_time")
private LocalDateTime updatedTime;

}
这样,MyBatis就会使用无参构造器创建对象,然后通过setter方法设置属性,这时类型处理器就会工作,将数据库中的字符串转换为ObjectNode。

如果问题仍然存在,请检查数据库中的字段类型是否与类型处理器匹配。JacksonTypeHandler通常用于处理JSON字符串,所以数据库字段类型应该是VARCHAR、TEXT等字符串类型。

另外,确保你的MyBatis-Plus配置中已经正确配置了类型处理器。如果你使用的是全局配置,可以在配置类中添加:

java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件等
return interceptor;
}
}
并且,由于你已经在字段上指定了typeHandler,所以不需要全局注册。

如果还是不行,可以尝试在application.yml中配置:

yaml
mybatis-plus:
type-handlers-package: 你的JacksonTypeHandler所在的包
但通常不需要,因为已经在注解中指定了。

最后,如果问题依旧,请检查JacksonTypeHandler是否能够正常处理ObjectNode与String的转换。可以写一个单元测试来验证。

如果以上步骤都完成了,那么应该可以解决问题。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐