如何解决mybatis-plus自动填充字段不生效问题
01前言
使用过mybatis-plus的朋友可能会知道,通过实现元对象处理器接口com.baomidou.mybatisplus.core.handlers.MetaObjectHandler可以实现字段填充功能。但如果在更新实体,使用boolean update(Wrapper updateWrapper)这个方法进行更新时,则自动填充会失效。今天就来聊聊这个话题,本文例子使用的mybatis-plus版本为3.1.2版本
02为何使用update(updateWrapper),自动填充会失效?
从mybatis-plus 3.1.2版本跟踪源码,可以得知,自动填充的调用代码实现逻辑是由下面的核心代码块实现
/**
* 自定义元对象填充控制器
*
* @param metaObjectHandler 元数据填充处理器
* @param tableInfo 数据库表反射信息
* @param ms MappedStatement
* @param parameterObject 插入数据库对象
* @return Object
*/
protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
MappedStatement ms, Object parameterObject, boolean isInsert) {
if (null == tableInfo) {
/* 不处理 */
return parameterObject;
}
/* 自定义元对象填充控制器 */
MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
// 填充主键
if (isInsert && !StringUtils.isEmpty(tableInfo.getKeyProperty())
&& null != tableInfo.getIdType() && tableInfo.getIdType().getKey() >= 3) {
Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
/* 自定义 ID */
if (StringUtils.checkValNull(idValue)) {
if (tableInfo.getIdType() == IdType.ID_WORKER) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
} else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
} else if (tableInfo.getIdType() == IdType.UUID) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
}
}
}
if (metaObjectHandler != null) {
if (isInsert && metaObjectHandler.openInsertFill()) {
// 插入填充
metaObjectHandler.insertFill(metaObject);
} else if (!isInsert) {
// 更新填充
metaObjectHandler.updateFill(metaObject);
}
}
return metaObject.getOriginalObject();
}
从源码分析我们可以得知当tableInfo为null时,是不走自动填充逻辑。而tableInfo又是什么从地方进行取值,继续跟踪源码,我们得知tableInfo可以由底下代码获取
if (isFill) {
Collection<Object> parameters = getParameters(parameterObject);
if (null != parameters) {
List<Object> objList = new ArrayList<>();
for (Object parameter : parameters) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
if (null != tableInfo) {
objList.add(populateKeys(metaObjectHandler, tableInfo, ms, parameter, isInsert));
} else {
/*
* 非表映射类不处理
*/
objList.add(parameter);
}
}
return objList;
} else {
TableInfo tableInfo = null;
if (parameterObject instanceof Map) {
Map<?, ?> map = (Map<?, ?>) parameterObject;
if (map.containsKey(Constants.ENTITY)) {
Object et = map.get(Constants.ENTITY);
if (et != null) {
if (et instanceof Map) {
Map<?, ?> realEtMap = (Map<?, ?>) et;
if (realEtMap.containsKey(Constants.MP_OPTLOCK_ET_ORIGINAL)) {
tableInfo = TableInfoHelper.getTableInfo(realEtMap.get(Constants.MP_OPTLOCK_ET_ORIGINAL).getClass());
}
} else {
tableInfo = TableInfoHelper.getTableInfo(et.getClass());
}
}
}
} else {
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
}
从源码可以很清楚看出,tableInfo 的获取依赖parameterObject.getClass(),则这个parameterObject就是数据库插入或者更新对象。即我们的实体对象,当实体对象为null时,则tableInfo 的值也是为null,这就会导致自动填充失效。
我们再来看下boolean update(Wrapper updateWrapper)这个代码的底层实现
default boolean update(Wrapper<T> updateWrapper) {
return this.update((Object)null, updateWrapper);
}
通过代码我们可以知道,当使用这个方法时,其实体对象是null,导致调用自动填充方法时,得到的tableInfo是null,因而无法进入自动填充实现逻辑,因此导致填充自动失效
03如何解决update(updateWrapper),自动填充不生效问题
通过源码分析我们得知,只要tableInfo不为空,则就会进入自动填充逻辑,而tableInfo不为空的前提是更新或者插入的实体不是null对象,因此我们的思路就是在调用update方法时,要确保实体不为null
方案一:实体更新时,直接使用update(updateWrapper)的重载方法update(entity, updateWrapper)
示例:
msgLogService.update(new MsgLog(),lambdaUpdateWrapper)
方案二:重写update(updateWrapper)方法
重写update的方法思路有如下
方法一:重写ServiceImpl的update方法
其核心思路如下,重写一个业务基类BaseServiceImpl
public class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {
/**
*
*
* @param updateWrapper
* @return
*/
@Override
public boolean update(Wrapper<T> updateWrapper) {
T entity = updateWrapper.getEntity();
if (null == entity) {
try {
entity = this.currentModelClass().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return update(entity, updateWrapper);
}
}
业务service去继承BaseServiceImpl,形如下
@Service
public class MsgLogServiceImpl extends BaseServiceImpl<MsgLogDao, MsgLog> implements MsgLogService {
}
方法二:通过动态代理去重写update(updateWrapper)
其核心代码如下
@Aspect
@Component
@Slf4j
public class UpdateWapperAspect implements ApplicationContextAware {
private ApplicationContext applicationContext;
private Map<String,Object> entityMap = new HashMap<>();
@Pointcut("execution(* com.baomidou.mybatisplus.extension.service.IService.update(com.baomidou.mybatisplus.core.conditions.Wrapper))")
public void pointcut(){
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint pjp){
Object updateEnityResult = this.updateEntity(pjp);
if(ObjectUtils.isEmpty(updateEnityResult)){
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
return updateEnityResult;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
*重写update(Wrapper<T> updateWrapper), 更新时自动填充不生效问题
* @param pjp
* @return
*/
private Object updateEntity(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
if(args != null && args.length == 1){
Object arg = args[0];
if(arg instanceof Wrapper){
Wrapper updateWrapper = (Wrapper)arg;
Object entity = updateWrapper.getEntity();
IService service = (IService) applicationContext.getBean(pjp.getTarget().getClass());
if(ObjectUtils.isEmpty(entity)){
entity = entityMap.get(pjp.getTarget().getClass().getName());
if(ObjectUtils.isEmpty(entity)){
Class entityClz = ReflectionKit.getSuperClassGenericType(pjp.getTarget().getClass(), 1);
try {
entity = entityClz.newInstance();
} catch (InstantiationException e) {
log.warn("Entity instantiating exception!");
} catch (IllegalAccessException e) {
log.warn("Entity illegal access exception!");
}
entityMap.put(pjp.getTarget().getClass().getName(),entity);
}
}
return service.update(entity,updateWrapper);
}
}
return null;
}
}
04总结
文章开头一直在指明mybatis-plus版本,是因为我跟过mybatis-plus3.1版本、3.3版本、3.4版本的自动填充的调用源码,其源码的实现各有不同,因为我github上的mybatis-plus引用的版本是3.1.2版本,因此就以3.1.2版本进行分析。不过其他版本的分析思路大同小异,都是去跟踪什么地方调用了自动填充的逻辑。
至于解决方案的几种思路,说下我的个人建议,如果项目初期的话,做好宣导,建议使用方案一,直接使用update(new MsgLog(),lambdaUpdateWrapper)这种写法。如果项目开发到一定程度了,发现很多地方都存在更新自动填充失效,则推荐使用直接底层重写update的方案
05demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-mybatisplus-tenant
相关文章
- 分布式系统协调内核——Zookeeper
- Gartner:低代码在2021年将持续增长
- 影响WiFi速度的不仅是设备老旧 或许是存在信号干扰
- 从网络工程师的角度看网络自动化的现状
- 运营商内部会议再提如何竞争 称不要打价格战不许破坏市场
- 5G,物联网和人工智能等将会掀起怎样的风波呢?
- 2021年需要了解的5种网络趋势
- 云计算与DevOps:你的下一步职业发展方向
- 了解5种类型的服务器虚拟化
- Starlink互联网服务今年目标提速至300Mbps
- Docker 网络基础 | 虚拟网络设备对(Veth)原理
- 现代网络给企业带来的好处
- 中国移动高同庆:把握5G发展新机遇 共赢万物智联新时代
- 浅析:智能网卡究竟有什么作用?
- 华为与产业伙伴联合发布《5G确定性网络架构产业白皮书》
- 金融核心系统加速云原生转型 阿里云提供全栈技术方案
- 4G 登顶,5G 发力:2025 年 4G 占中国总连接数 53%,5G 占 47%
- 报告:Serverless 已成为许多软件堆栈的关键部分
- 我国多项技术赶超美国,AI、5G囊括在内,美国卡脖子越来越难
- 华为云发布新一代云原生防火墙 CFW,兼具极简运营和多场景防护!