前言
上一篇文章JPA的简单使用 讲了JPA的基本使用方法,也实现了基本的审计日志,但只记录了操作人、操作时间、修改人、修改时间,不太人性化。比如:具体修改了什么信息,误删除的话有没有办法恢复,修改的数据是否能回滚,这些问题在系统实际使用过程中都会遇到。那么,对于简单的新增、修改、删除能不能做到以上几点呢?很显然,完全没问题,来,咱们看看简单的实现方式。
准备工作
编写操作记录的实体类
操作记录
主要记录操作的业务表、主键等相关信息。
@Data
public class PoEntity implements Serializable {
private static final long serialVersionUID = -4080920498644273280L;
/**
* 实体类全路径
*/
private String entityName;
/**
* 表名
*/
private String tableName;
/**
* 主键字段
*/
private String[] pkFields;
/**
* 主键字段值,与 pkFields 下标一一对应
*/
private Serializable[] pkValues;
/**
* 表对应的列详情
*/
private List<PoEntityField> fields;
/**
* 操作标识
*/
private String operateFlag;
/**
* 当前操作执行的SQL
*/
private String executeSql;
/**
* 回滚当前操作需要执行的SQL
*/
private String rollbackSql;
}
操作记录详情
主要记录每次操作时前后更改记录。
@Data
public class PoEntityField implements Serializable {
private static final long serialVersionUID = -3510567803633570818L;
/**
* 字段名
*/
private String fieldName;
/**
* 对于的java格式的字段类型
*/
private String fieldType;
/**
* 字段操作前的值
*/
private Object oldFieldValue;
/**
* 字段操作后的值
*/
private Object newFieldValue;
/**
* 操作前后的值是否更改
*/
private Boolean isChange;
}
编写操作对应的监听
我们在每次新增、删除、修改前调用我们的监听方法,这个可以根据实际情况调整。注:以下监听对应的类都来源于org.hibernate.event.spi包。
@Slf4j
@Component
public class JpaBaseOperateListener implements
PostInsertEventListener
, PreDeleteEventListener
, PreUpdateEventListener {
@Resource
private EntityManagerFactory entityManagerFactory;
/**
* 监听新增操作
* @param event
*/
@Override
public void onPostInsert(PostInsertEvent event) {
this.formatPoEntity(event, OPERATE.INSERT);
}
/**
* 监听删除操作,批量操作时会调用多次(注:调用实体类删除方法时才能监听到,根据id删除无法监听到)
* @param event
* @return
*/
@Override
public boolean onPreDelete(PreDeleteEvent event) {
this.formatPoEntity(event, OPERATE.DELETE);
return false;
}
/**
* 监听 修改操作 ,批量操作时会调用多次
* @param event
* @return
*/
@Override
public boolean onPreUpdate(PreUpdateEvent event) {
this.formatPoEntity(event, OPERATE.UPDATE);
return false;
}
/**
* 调用方法对应的枚举类
*/
private enum CallMethod {
getEntity,
getOldState,
getState,
getPersister,
getDeletedState
}
/**
* 格式化 字段信息
*
* @param eventObj
*/
private void formatPoEntity(Object eventObj, OPERATE operate) {
Object entityObj = ReflectUtil.invoke(eventObj, CallMethod.getEntity.name());
Method oldStateMethod = ReflectUtil.getMethod(eventObj.getClass(), ObjectUtil.equal(OPERATE.DELETE, operate) ? CallMethod.getDeletedState.name() : CallMethod.getOldState.name());
Object[] oldValueArr = null;
if (ObjectUtil.isNotNull(oldStateMethod)) {
oldValueArr = ReflectUtil.invoke(eventObj, oldStateMethod);
}
Method stateMethod = ReflectUtil.getMethod(eventObj.getClass(), CallMethod.getState.name());
Object[] newValueArr = null;
if (ObjectUtil.isNotNull(stateMethod)) {
newValueArr = ReflectUtil.invoke(eventObj, stateMethod);
}
Method getEpMethod = ReflectUtil.getMethod(eventObj.getClass(), CallMethod.getPersister.name());
SingleTableEntityPersister ep = null;
if (ObjectUtil.isNotNull(getEpMethod)) {
ep = ReflectUtil.invoke(eventObj, getEpMethod);
}
PoEntity entity = new PoEntity();
String operateName = operate.name();
entity.setOperateFlag(operateName);
// 表名
String tableName = ep.getTableName();
entity.setTableName(tableName);
List<PoEntityField> poEntityFields = new ArrayList<>();
String[] pkFields = ep.getIdentifierColumnNames();
if (ArrayUtil.isNotEmpty(pkFields)) {
Serializable[] pkValues = new Serializable[pkFields.length];
for (int i = 0; i < pkFields.length; i++) {
pkValues[i] = (Serializable) ReflectUtil.getFieldValue(entityObj, pkFields[i]);
}
entity.setPkValues(pkValues);
}
entity.setPkFields(pkFields);
// 实体类的全路径
String entityName = ep.getEntityName();
entity.setEntityName(entityName);
// 获得实体类的所有属性
String[] propertyNames = ep.getPropertyNames();
Object tempOldValue = null;
Object tempNewValue = null;
List<String> newValueList = new ArrayList<>();
for (int i = 0; i < propertyNames.length; i++) {
String propertyName = propertyNames[i];
PoEntityField field = new PoEntityField();
Type propertyType = ep.getPropertyType(propertyName);
field.setFieldName(propertyName);
String fieldType = propertyType.getReturnedClass().getName();
field.setFieldType(fieldType);
if (i < ArrayUtil.length(oldValueArr)) {
tempOldValue = oldValueArr[i];
}
field.setOldFieldValue(tempOldValue);
if (i < ArrayUtil.length(newValueArr)) {
tempNewValue = newValueArr[i];
}
newValueList.add(String.valueOf(tempNewValue));
field.setNewFieldValue(tempNewValue);
field.setIsChange(!ObjectUtil.equal(field.getOldFieldValue(), field.getNewFieldValue()));
poEntityFields.add(field);
}
// 新增主键对应的列
PoEntityField pkEntityField = new PoEntityField();
pkEntityField.setFieldName(entity.getPkFields()[0]);
pkEntityField.setOldFieldValue(entity.getPkValues()[0]);
pkEntityField.setNewFieldValue(entity.getPkValues()[0]);
pkEntityField.setIsChange(Boolean.FALSE);
poEntityFields.add(0, pkEntityField);
entity.setFields(poEntityFields);
this.formatExecuteAndRollbackSql(entity);
}
/**
* 删除 sql 模板
*/
private String DELETE_SQL_TEMPLATE = "delete from {} where {} = '{}';";
private String INSERT_SQL_TEMPLATE = "insert into {} (`{}`) values ('{}');";
private String UPDATE_SQL_TEMPLATE = "update {} set {} where {} = '{}';";
/**
* 得到执行sql以及回滚sql
*/
private void formatExecuteAndRollbackSql(PoEntity entity) {
String operateFlag = entity.getOperateFlag();
String tableName = entity.getTableName();
String[] pkFields = entity.getPkFields();
Serializable[] pkValues = entity.getPkValues();
List<PoEntityField> fields = entity.getFields();
String executeSql = "";
String rollbackSql = "";
String firstPkField = pkFields[0];
String firstPkValue = String.valueOf(pkValues[0]);
List<String> fieldNameList = fields.parallelStream().map(f -> StrUtil.toUnderlineCase(f.getFieldName())).collect(Collectors.toList());
List<Object> oldValueList = fields.parallelStream().map(PoEntityField::getOldFieldValue).collect(Collectors.toList());
List<Object> newValueList = fields.parallelStream().map(PoEntityField::getNewFieldValue).collect(Collectors.toList());
if (StrUtil.equals(OPERATE.INSERT.name(), operateFlag)) {
executeSql = StrUtil.format(INSERT_SQL_TEMPLATE, tableName,
CollUtil.join(fieldNameList,"`,`"),
CollUtil.join(newValueList,"','")
);
rollbackSql = StrUtil.format(DELETE_SQL_TEMPLATE, tableName, firstPkField, firstPkValue);
} else if (StrUtil.equals(OPERATE.UPDATE.name(), operateFlag)) {
executeSql = StrUtil.format(UPDATE_SQL_TEMPLATE, tableName,
CollUtil.join(fields.parallelStream().map(f -> {
Object newFieldValue = f.getNewFieldValue();
return " `" + StrUtil.toUnderlineCase(f.getFieldName()) + "` = '" + newFieldValue + "'";
}).collect(Collectors.toList()),",")
, firstPkField, firstPkValue);
rollbackSql = StrUtil.format(UPDATE_SQL_TEMPLATE, tableName,
CollUtil.join(fields.parallelStream().map(f -> {
Object oldFieldValue = f.getOldFieldValue();
return " `" + StrUtil.toUnderlineCase(f.getFieldName()) + "` = '" + oldFieldValue + "'";
}).collect(Collectors.toList()),",")
, firstPkField, firstPkValue);
} else if (StrUtil.equals(OPERATE.DELETE.name(), operateFlag)) {
executeSql = StrUtil.format(DELETE_SQL_TEMPLATE, tableName, firstPkField, firstPkValue);
rollbackSql= StrUtil.format(INSERT_SQL_TEMPLATE, tableName,
CollUtil.join(fieldNameList,"`,`"),
CollUtil.join(oldValueList,"','")
);
} else {
throw new RuntimeException("Nonsupport operate flag");
}
// 这里的操作详情只是做了部分数据打印,实际应用时可以直接存储只表中
entity.setExecuteSql(executeSql);
log.info("executeSql = > {}", executeSql);
entity.setRollbackSql(rollbackSql);
log.info("rollbackSql = > {}", rollbackSql);
}
/**
* 参考:https://www.coder.work/article/6818980
*/
@PostConstruct
protected void regListener() {
SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
// 注册 新增 后的操作监听
registry.getEventListenerGroup(EventType.POST_INSERT).appendListener(this);
// 注册 修改 前的操作监听
registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(this);
// 注册 删除 前的操作监听
registry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(this);
}
@Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
log.info("=====requiresPostCommitHanding======");
return false;
}
@Override
public boolean requiresPostCommitHandling(EntityPersister persister) {
log.info("=====requiresPostCommitHandling======");
return false;
}
/**
* 操作符标识
*/
private enum OPERATE {
INSERT,
UPDATE,
DELETE;
}
}
测试
新增
测试代码
@Test
void insertOne() {
String password = SecureUtil.md5("user");
UserPO userPO = new UserPO()
.setAccount("user")
.setPassword(password)
.setUserName("测试用户")
.setAge(20)
.setGender(1)
.setDeleteFlag("0");
this.userDao.save(userPO);
日志
executeSql = > insert into user (`id`,`account`,`age`,`create_time`,`create_user`,`delete_flag`,`gender`,`password`,`update_time`,`update_user`,`user_name`) values ('c6d51d34edb4456e991be767b670501f','user','20','2022-03-03T21:44:10.216','operateId','0','1','ee11cbb19052e40b07aac0ca060c23ee','2022-03-03T21:44:10.216','operateId','测试用户');
rollbackSql = > delete from user where id = 'c6d51d34edb4456e991be767b670501f';
修改
测试代码
@Test
public void update() {
String userId = "38f9303f2616413dbb600305b208f241";
Optional<UserPO> opt = this.userDao.findById(userId);
UserPO userPO = new UserPO();
if (opt.isPresent()) {
BeanUtil.copyProperties(opt.get(), userPO);
}
userPO.setUserName("777").setAccount("user777");
UserPO save = this.userDao.save(userPO);
System.out.println(save.toString());
}
日志
executeSql = > insert into user (`id`,`account`,`age`,`create_time`,`create_user`,`delete_flag`,`gender`,`password`,`update_time`,`update_user`,`user_name`) values ('6dec8d86f8c242f6ab148ca82dcd2d1f','user777','null','2022-03-03T21:50:04.516','operateId','null','null','null','2022-03-03T21:50:04.516','operateId','777');
rollbackSql = > delete from user where id = '6dec8d86f8c242f6ab148ca82dcd2d1f';
删除
测试代码
@Test
public void deleteOne() {
String userId = "38f9303f2616413dbb600305b208f241";
this.userDao.deleteById(userId);
}
日志
executeSql = > delete from user where id = '38f9303f2616413dbb600305b208f241';
rollbackSql = > insert into user (`id`,`account`,`age`,`create_time`,`create_user`,`delete_flag`,`gender`,`password`,`update_time`,`update_user`,`user_name`) values ('38f9303f2616413dbb600305b208f241','user888','20','2022-03-02T22:51:07.841','operateId','0','1','ee11cbb19052e40b07aac0ca060c23ee','2022-03-02T23:17:26.389','operateId','888');
至此,基础的审计日志功能以及完成啦,值得一提的是:本文中的操作记录及详情只是存储至在实体类中并未做持久化操作,实况开发过程中可能需要保存至数据库。
总结
本文中的示例只是对JPA的部分操作做了监听,其他监听操作可查看源码:org.hibernate.event.spi.EventType。
(注:相关的类在org.hibernate:hibernate-core:5.6.5.Final2.jar包中)