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。