MyBatis-Plus TypeHander不生效

1 现象

mysql 使用json类型字段 存储List<String>,java类属性使用TypeHandler 没有生效。

代码如下

实体类

java 复制代码
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
public class MessageDO {
        @TableField(typeHandler = FastjsonTypeHandler.class)
        private List<String> notifyType;
   
    }

service 层

scss 复制代码
@Override
public void messageUpdate(MessageUpdateReqDTO reqDTO) {
    lambdaUpdate()
            .set(PartnerDO::getWxNotifyType,reqDTO.getWxNotifyType())
            .eq(PartnerDO::getCompanyId,reqDTO.getCompanyId())
            .update();
}

报错如下

关键信息:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.

php 复制代码
 Cause: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
; Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.; nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
	at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:104)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:88)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
	at com.sun.proxy.$Proxy146.update(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:287)
	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:64)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
	at com.sun.proxy.$Proxy238.update(Unknown Source)
	at com.baomidou.mybatisplus.extension.conditions.update.ChainUpdate.update(ChainUpdate.java:45)
	at com.baomidou.mybatisplus.extension.conditions.update.ChainUpdate.update(ChainUpdate.java:35)

令人疑惑,我明明配置TypeHandler,但是没有生效,在 com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler#toJson 的方法打了断点,还是没有进入断点。

2.源码分析

TypeHandler的源码

com.baomidou.mybatisplus.core.MybatisParameterHandler#setParameters

ini 复制代码
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (this.boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                    value = this.boundSql.getAdditionalParameter(propertyName);
                } else if (this.parameterObject == null) {
                    value = null;
                } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = this.configuration.getJdbcTypeForNull();
                }
                try {
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException | SQLException e) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                }
            }
        }
    }
}

Debug了一下,发现根本没有解析出TypeHandler

ParameterMapping 解析

org.apache.ibatis.builder.SqlSourceBuilder#parse

typescript 复制代码
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
  this.sql = sql;
  this.parameterMappings = parameterMappings;
  this.configuration = configuration;
}

@Override
public BoundSql getBoundSql(Object parameterObject) {
  return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

org.apache.ibatis.builder.SqlSourceBuilder#parse

typescript 复制代码
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  String sql = parser.parse(originalSql);
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler#buildParameterMapping

ini 复制代码
private ParameterMapping buildParameterMapping(String content) {
  Map<String, String> propertiesMap = parseParameterMapping(content);
  String property = propertiesMap.get("property");
  Class<?> propertyType;
  if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
    propertyType = metaParameters.getGetterType(property);
  } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
    propertyType = parameterType;
  } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
    propertyType = java.sql.ResultSet.class;
  } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
    propertyType = Object.class;
  } else {
    MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
    if (metaClass.hasGetter(property)) {
      propertyType = metaClass.getGetterType(property);
    } else {
      propertyType = Object.class;
    }
  }
  ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
  Class<?> javaType = propertyType;
  String typeHandlerAlias = null;
  for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
    String name = entry.getKey();
    String value = entry.getValue();
    if ("javaType".equals(name)) {
      javaType = resolveClass(value);
      builder.javaType(javaType);
    } else if ("jdbcType".equals(name)) {
      builder.jdbcType(resolveJdbcType(value));
    } else if ("mode".equals(name)) {
      builder.mode(resolveParameterMode(value));
    } else if ("numericScale".equals(name)) {
      builder.numericScale(Integer.valueOf(value));
    } else if ("resultMap".equals(name)) {
      builder.resultMapId(value);
    } else if ("typeHandler".equals(name)) {
      typeHandlerAlias = value;
    } else if ("jdbcTypeName".equals(name)) {
      builder.jdbcTypeName(value);
    } else if ("property".equals(name)) {
      // Do Nothing
    } else if ("expression".equals(name)) {
      throw new BuilderException("Expression based parameters are not supported yet");
    } else {
      throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
    }
  }
  if (typeHandlerAlias != null) {
    builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
  }
  return builder.build();
}

lambdaUpdate set源码

查看API,发现可以传入mapping参数,指定typeHandler。

com.baomidou.mybatisplus.core.conditions.update.Update#set(R, java.lang.Object)

sql 复制代码
default Children set(R column, Object val) {
    return set(true, column, val);
}
default Children set(boolean condition, R column, Object val) {
    return set(condition, column, val, null);
}
/**
 * 设置 更新 SQL 的 SET 片段
 *
 * @param condition 是否加入 set
 * @param column    字段
 * @param val       值
 * @param mapping   例: javaType=int,jdbcType=NUMERIC,typeHandler=xxx.xxx.MyTypeHandler
 * @return children
 */
Children set(boolean condition, R column, Object val, String mapping);

3.解决方法

1 手动转为字符串

css 复制代码
lambdaUpdate()
        .set(PartnerDO::getWxNotifyType,JSON.toJSONString(reqDTO.getWxNotifyType()))
        .eq(PartnerDO::getCompanyId,reqDTO.getCompanyId()) .update();

2 添加TypeHandler

css 复制代码
lambdaUpdate()
.set(PartnerDO::getWxNotifyType,reqDTO.getWxNotifyType(),"typeHandler=com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler")
        .eq(PartnerDO::getCompanyId,reqDTO.getCompanyId())
        .update();

4.环境

mybatis-plus

xml 复制代码
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3.4</version>
</dependency>

5.总结

MyBatis-Plus 使用 lambdaUpdate更新某个属性,该属性的typehandler无效,需要手动转化或传入typeHandler。

相关推荐
笃行3502 小时前
从零开始:SpringBoot + MyBatis + KingbaseES 实现CRUD操作(超详细入门指南)
后端
该用户已不存在2 小时前
这几款Rust工具,开发体验直线上升
前端·后端·rust
用户8356290780512 小时前
C# 从 PDF 提取图片教程
后端·c#
L2ncE3 小时前
高并发场景数据与一致性的简单思考
java·后端·架构
水涵幽树3 小时前
MySQL 时间筛选避坑指南:为什么格式化字符串比较会出错?
数据库·后端·sql·mysql·database
ERP老兵_冷溪虎山3 小时前
从ASCII到Unicode:"国际正则"|"表达式"跨国界实战指南(附四大语言支持对比+中医HIS类比映射表)
后端·面试
HyggeBest3 小时前
Golang 并发原语 Sync Cond
后端·架构·go
老张聊数据集成3 小时前
数据建模怎么做?一文讲清数据建模全流程
后端
颜如玉4 小时前
Kernel bypass技术遥望
后端·性能优化·操作系统
一块plus4 小时前
创造 Solidity、提出 Web3 的他回来了!Gavin Wood 这次将带领波卡走向何处?
javascript·后端·面试