JPA进阶-实现审计日志

前言

上一篇文章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包中)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇