在我们的项目中,有一个业务实体类包含一个JSON类型的字段,该字段存储的是一个对象列表。为了处理这个JSON字段,我们自定义了一个类型处理器(TypeHandler)来实现对象与JSON字符串之间的转换。
实体类定义大致如下:
public class BusinessModel {
private Long id;
// 其他普通字段...
/**
* JSON字段,存储关系列表
*/
@TableField(typeHandler = CustomJsonTypeHandler.class)
private List<RelationObject> relations;
// getter/setter方法...
}
在实际使用中,我们发现了一个奇怪的现象:
使用自定义Mapper方法保存对象时,JSON字段能正确处理
使用MyBatis-Plus的内置save方法保存对象时,JSON字段处理失败
// 方式1:使用自定义Mapper方法 - 正常工作
this.baseMapper.insertOrUpdate(data);
// 方式2:使用MyBatis-Plus内置方法 - JSON处理失败
this.save(data);
问题分析
经过深入分析,我们发现问题的根本原因在于MyBatis-Plus的自动SQL生成机制与自定义类型处理器的兼容性问题。
1. MyBatis-Plus的save方法工作机制
MyBatis-Plus的内置方法(如save、update等)会自动生成SQL语句,而不是使用我们在XML中定义的SQL映射。这种方式虽然简化了开发,但也带来了一些限制:
自动生成的SQL不会包含我们在XML中定义的typeHandler
对于自定义类型(如JSON字段),MyBatis-Plus无法正确识别和处理
2. 自定义Mapper方法的优势
当我们使用自定义Mapper方法时,MyBatis会严格按照XML中定义的SQL和映射规则执行操作,包括使用指定的typeHandler处理JSON字段。
在我们的XML映射文件中,明确指定了类型处理器:
<resultMap id="BaseResultMap" type="BusinessModel">
<id property="id" column="id" />
<!-- 其他字段映射... -->
<result property="relations" column="relations" typeHandler="CustomJsonTypeHandler"/>
</resultMap>
<insert id="insertOrUpdate">
INSERT INTO business_table (id, relations, ...)
VALUES (#{id}, #{relations, typeHandler=CustomJsonTypeHandler}, ...)
ON CONFLICT (id) DO UPDATE SET
relations = #{relations, typeHandler=CustomJsonTypeHandler}
</insert>
解决方案
为了让MyBatis-Plus的内置方法也能正确处理JSON字段,我们需要进行以下配置:
1. 配置MyBatis-Plus扫描类型处理器
在application.yml中添加类型处理器包的扫描配置:
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: AUTO
# 添加类型处理器包路径,让MyBatis-Plus自动扫描和注册
type-handlers-package: com.yourpackage.typehandler
2. 确保类型处理器正确实现
类型处理器需要正确实现MyBatis的BaseTypeHandler,并确保有无参构造函数:
@MappedJdbcTypes(JdbcType.OTHER)
@MappedTypes({List.class})
public abstract class BaseJsonTypeHandler<T> extends BaseTypeHandler<List<T>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
String content = CollectionUtils.isEmpty(parameter) ? null : JSON.toJSONString(parameter);
ps.setObject(i, content, java.sql.Types.OTHER);
}
@Override
public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return this.parseJson(rs.getString(columnName));
}
@Override
public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.parseJson(rs.getString(columnIndex));
}
@Override
public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.parseJson(cs.getString(columnIndex));
}
private List<T> parseJson(String content) {
if (StringUtils.isEmpty(content)) {
return new ArrayList<>();
}
return JSON.parseObject(content, this.getSpecificType());
}
/**
* 获取具体类型
*/
protected abstract TypeReference<List<T>> getSpecificType();
// 添加无参构造函数以支持MyBatis-Plus的自动实例化
public BaseJsonTypeHandler() {
}
}
3. 实体类正确使用注解
在实体类中正确使用@TableField注解指定typeHandler:
public class BusinessModel {
private Long id;
/**
* JSON字段,存储关系列表
*/
@TableField(typeHandler = CustomJsonTypeHandler.class)
private List<RelationObject> relations;
// getter/setter方法...
}
总结
通过以上配置,我们就可以正常使用MyBatis-Plus的内置方法来处理包含JSON字段的实体对象了。这个问题的核心在于MyBatis-Plus的自动SQL生成机制与自定义类型处理器之间的兼容性问题。
关键要点:
在application.yml中配置type-handlers-package让MyBatis-Plus能够自动扫描和注册类型处理器
确保类型处理器类有无参构造函数以支持自动实例化
实体类中正确使用@TableField注解指定typeHandler
这样配置后,MyBatis-Plus的save方法就能正确处理JSON字段的序列化和反序列化,与自定义Mapper方法达到同样的效果。
在实际项目开发中,遇到这类问题时,我们应该首先检查MyBatis-Plus的配置是否完整,确保自定义组件能够被正确识别和使用。