Backup local changes before resolving remote repository issue

This commit is contained in:
2026-01-27 10:05:25 +08:00
parent 11c2758289
commit 86bca03b04
29 changed files with 2626 additions and 126 deletions

View File

@@ -0,0 +1,290 @@
# 深度排查 MyBatis-Plus 自动填充不生效问题
## 问题概述
尽管对 MyBatis-Plus 的自动填充处理器进行了多次优化和配置,但 `create_by``create_time` 字段仍然没有被自动填充。
## 深度排查步骤
### 1. 检查 AOP 代理是否生效
MyBatis-Plus 的自动填充功能依赖于 AOP 代理。如果实体类的方法被直接调用而非通过代理调用,自动填充可能不会生效。
### 2. 验证 Service 层实现
确保使用的是 MyBatis-Plus 提供的通用 Service 方法,而不是自定义的 SQL。
### 3. 检查 @TableField 注解配置
确认实体类中的字段注解配置正确。
### 4. 检查事务配置
某些事务配置可能会影响 AOP 代理的生效。
## 解决方案
### 方案一:在 Service 层手动设置审计字段
创建一个工具类来统一处理审计字段的设置:
```java
@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 实现中使用:
```java
@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 层处理:
```java
@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 拦截器来自动填充字段:
```java
@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. 方案二是最后的选择,需要重写具体的插入逻辑
## 验证方法
创建一个测试来验证自动填充是否生效:
```java
@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());
}
}
```
通过这些方案,应该能够解决自动填充不生效的问题。