Files
his/deep_dive_autofill_solution.md

10 KiB
Raw Blame History

深度排查 MyBatis-Plus 自动填充不生效问题

问题概述

尽管对 MyBatis-Plus 的自动填充处理器进行了多次优化和配置,但 create_bycreate_time 字段仍然没有被自动填充。

深度排查步骤

1. 检查 AOP 代理是否生效

MyBatis-Plus 的自动填充功能依赖于 AOP 代理。如果实体类的方法被直接调用而非通过代理调用,自动填充可能不会生效。

2. 验证 Service 层实现

确保使用的是 MyBatis-Plus 提供的通用 Service 方法,而不是自定义的 SQL。

3. 检查 @TableField 注解配置

确认实体类中的字段注解配置正确。

4. 检查事务配置

某些事务配置可能会影响 AOP 代理的生效。

解决方案

方案一:在 Service 层手动设置审计字段

创建一个工具类来统一处理审计字段的设置:

@Component
public class AuditFieldUtil {
    
    public static void setCreateInfo(Object entity) {
        if (entity == null) return;
        
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            String username = loginUser != null ? loginUser.getUsername() : "system";
            Date currentTime = new Date();
            
            // 使用反射设置字段值
            Field createByField = getField(entity.getClass(), "createBy");
            if (createByField != null) {
                createByField.setAccessible(true);
                if (createByField.get(entity) == null || "".equals(createByField.get(entity))) {
                    createByField.set(entity, username);
                }
            }
            
            Field createTimeField = getField(entity.getClass(), "createTime");
            if (createTimeField != null) {
                createTimeField.setAccessible(true);
                if (createTimeField.get(entity) == null) {
                    createTimeField.set(entity, currentTime);
                }
            }
            
            // 处理下划线命名的字段
            Field createByFieldUnderscore = getField(entity.getClass(), "create_by");
            if (createByFieldUnderscore != null) {
                createByFieldUnderscore.setAccessible(true);
                if (createByFieldUnderscore.get(entity) == null || "".equals(createByFieldUnderscore.get(entity))) {
                    createByFieldUnderscore.set(entity, username);
                }
            }
            
            Field createTimeFieldUnderscore = getField(entity.getClass(), "create_time");
            if (createTimeFieldUnderscore != null) {
                createTimeFieldUnderscore.setAccessible(true);
                if (createTimeFieldUnderscore.get(entity) == null) {
                    createTimeFieldUnderscore.set(entity, currentTime);
                }
            }
            
        } catch (Exception e) {
            System.err.println("设置审计字段时发生异常: " + e.getMessage());
        }
    }
    
    private static Field getField(Class<?> clazz, String fieldName) {
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            if (clazz.getSuperclass() != null) {
                return getField(clazz.getSuperclass(), fieldName);
            }
            return null;
        }
    }
}

然后在 Service 实现中使用:

@Service
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner> 
        implements IPractitionerService {
    
    @Autowired
    private AuditFieldUtil auditFieldUtil;
    
    @Override
    @Transactional
    public boolean save(Practitioner entity) {
        // 在保存前手动设置审计字段
        auditFieldUtil.setCreateInfo(entity);
        return super.save(entity);
    }
    
    @Override
    @Transactional
    public boolean saveBatch(Collection<Practitioner> entityList) {
        entityList.forEach(auditFieldUtil::setCreateInfo);
        return super.saveBatch(entityList);
    }
}

方案二:重写 BaseMapper 方法

如果 Service 层的方法不起作用,可以直接在 Mapper 层处理:

@Mapper
public interface PractitionerMapper extends BaseMapper<Practitioner> {
    
    @Insert({
        "<script>",
        "INSERT INTO adm_practitioner (",
        "id, active_flag, name, name_json, gender_enum, birth_date, deceased_date,",
        "phone, address, address_province, address_city, address_district, address_street,",
        "address_json, py_str, wb_str, bus_no, yb_no, user_id, tenant_id, delete_flag,",
        "create_by, create_time, update_by, update_time, org_id,",
        "phar_prac_cert_no, prsc_dr_cert_code, dr_profttl_code, kpd_code, signature, pos_no",
        ") VALUES (",
        "#{id}, #{activeFlag}, #{name}, #{nameJson}, #{genderEnum}, #{birthDate}, #{deceasedDate},",
        "#{phone}, #{address}, #{addressProvince}, #{addressCity}, #{addressDistrict}, #{addressStreet},",
        "#{addressJson}, #{pyStr}, #{wbStr}, #{busNo}, #{ybNo}, #{userId}, #{tenantId}, #{deleteFlag},",
        "#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{orgId},",
        "#{pharPracCertNo}, #{prscDrCertCode}, #{drProfttlCode}, #{kpdCode}, #{signature}, #{posNo}",
        ")",
        "</script>"
    })
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertWithAuditFields(Practitioner record);
}

方案三:使用 MyBatis 拦截器

创建一个 MyBatis 拦截器来自动填充字段:

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Component
public class AuditFieldInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        
        String sqlCommandType = ms.getSqlCommandType().toString();
        
        if ("INSERT".equals(sqlCommandType)) {
            setCreateAuditFields(parameter);
        } else if ("UPDATE".equals(sqlCommandType)) {
            setUpdateAuditFields(parameter);
        }
        
        return invocation.proceed();
    }
    
    private void setCreateAuditFields(Object parameter) {
        if (parameter == null) return;
        
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            String username = loginUser != null ? loginUser.getUsername() : "system";
            Date currentTime = new Date();
            
            // 设置 createBy 和 createTime
            setFieldValue(parameter, "createBy", username);
            setFieldValue(parameter, "create_time", username);
            setFieldValue(parameter, "createTime", currentTime);
            setFieldValue(parameter, "create_time", currentTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void setUpdateAuditFields(Object parameter) {
        if (parameter == null) return;
        
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            String username = loginUser != null ? loginUser.getUsername() : "system";
            Date currentTime = new Date();
            
            // 设置 updateBy 和 updateTime
            setFieldValue(parameter, "updateBy", username);
            setFieldValue(parameter, "update_by", username);
            setFieldValue(parameter, "updateTime", currentTime);
            setFieldValue(parameter, "update_time", currentTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void setFieldValue(Object obj, String fieldName, Object value) {
        try {
            Field field = getField(obj.getClass(), fieldName);
            if (field != null) {
                field.setAccessible(true);
                if (field.get(obj) == null) { // 只在原值为 null 时设置
                    field.set(obj, value);
                }
            }
        } catch (Exception e) {
            // 忽略无法设置的字段
        }
    }
    
    private Field getField(Class<?> clazz, String fieldName) {
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            if (clazz.getSuperclass() != null) {
                return getField(clazz.getSuperclass(), fieldName);
            }
            return null;
        }
    }
    
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }
    
    @Override
    public void setProperties(Properties properties) {}
}

推荐实施顺序

  1. 首先尝试方案一Service 层手动设置),这是最简单且可控的方式
  2. 如果方案一不行尝试方案三MyBatis 拦截器),它在更底层起作用
  3. 方案二是最后的选择,需要重写具体的插入逻辑

验证方法

创建一个测试来验证自动填充是否生效:

@SpringBootTest
public class AuditFieldTest {
    
    @Autowired
    private IPractitionerService practitionerService;
    
    @Test
    public void testAuditFieldFill() {
        Practitioner practitioner = new Practitioner();
        practitioner.setName("Test Practitioner");
        
        // 记录保存前的值
        System.out.println("保存前 - createBy: " + practitioner.getCreateBy());
        System.out.println("保存前 - createTime: " + practitioner.getCreateTime());
        
        boolean success = practitionerService.save(practitioner);
        
        // 从数据库重新查询以验证
        Practitioner saved = practitionerService.getById(practitioner.getId());
        System.out.println("保存后 - createBy: " + saved.getCreateBy());
        System.out.println("保存后 - createTime: " + saved.getCreateTime());
        
        Assertions.assertTrue(success);
        Assertions.assertNotNull(saved.getCreateBy());
        Assertions.assertNotNull(saved.getCreateTime());
    }
}

通过这些方案,应该能够解决自动填充不生效的问题。