问题描述

在实现公共字段自动填充功能时,调用 updateById 方法时遇到了如下异常:

1
2
3
4
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database. Cause: java.lang.NullPointerException
### The error may exist in ...Mapper.xml
### Cause: java.lang.NullPointerException

表面上看是个 NPE,但翻阅代码发现业务逻辑并没有明显的空指针问题。

背景代码结构

我们在项目中使用了自定义注解 @AutoFill 配合 AOP 对实体类的公共字段(如 updateTime, updateUser 等)进行自动填充,类似如下结构:

Mapper 层

1
2
3
4
5
6
7
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {

@Override
@AutoFill(OperationType.UPDATE)
int updateById(Employee entity); // 注意这里!
}

AOP 切面逻辑(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

@Pointcut("@annotation(top.zfmx.annotation.AutoFill)")
public void autoFillPointCut() {}

@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
Object entity = joinPoint.getArgs()[0];
// 使用反射设置 updateTime、updateUser 等字段
}
}

Service 层调用

1
2
3
4
5
6
7
8
9
@Override
public void updateById(EmployeeVO employeeVO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeVO, employee);
if (employee.getEmployeeId() == null) {
throw new IllegalArgumentException("ID不能为空");
}
baseMapper.updateById(employee);
}

问题分析

MyBatis Plus 在底层执行 SQL 时会自动封装参数,使用 Wrapper 时默认会把参数对象包装成 et 这个别名:

1
2
3
4
<set>
update_time = #{et.updateTime},
update_user = #{et.updateUser}
</set>

所以,如果你在自定义了 updateById(Employee entity) 方法并加了注解 AOP,但又没有显式声明 @Param("et"),MyBatisPlus 无法识别 et,就会抛出空指针。


解决方案

只需要在 Mapper 的方法参数上加上如下注解即可:

1
@Param("et")

最终方法定义变成:

1
2
3
@Override
@AutoFill(OperationType.UPDATE)
int updateById(@Param("et") Employee entity);

总结

在使用 AOP + 自定义注解进行自动字段填充时,尤其是在重写 MyBatis Plus 的通用方法(如 updateById)时:

  • 必须明确参数别名,以确保 MyBatis 在解析 SQL 时能够正确引用对象属性。
  • @Param("et") 是关键! 它告诉 MyBatis:当前这个参数在 SQL 语句中叫 et,否则就会报错或者导致字段无法赋值。