基于mybatis-plus对数据库数据批量加密

对数据库加密是个老生常谈的问题, 一般来说都是在开发阶段就会考虑到对于一些敏感的用户信息进行加密处理, 但是有一些情况是需要对之前不需要加密的数据进行加密, 这样一来就比较麻烦了, 需要对数据库已有的数据进行加密, 为了减轻负担, 搞了一个工具类

@FieldEncrypt

加密完之后我们查询的时候还需要对数据库进行解密, 这就需要用的@FieldEncrypt来进行方便的解密, 可以使用mybatis-plus提供的(收费项), 或者可以参考我之前的文章:

使用mybatis拦截器实现字段加密解密_使用mybatis拦截器实现在插入式进行字段加密-CSDN博客

 

 工具类

此工具类使用到了hutool中的加密工具

FieldEncryptUtil 

工具类核心原理很简单就是将数据库中的数据全部查询出来, 如果大于1000条则进行分批查询, 分批处理加密再进行更新插入, 为了方便调用, 工具类使用了反射的原理来加密更新需要进行加密的字段

@Component
@Slf4j
public class FieldEncryptUtil {

    private final MybatisBatchUtils mybatisBatchUtils;

    public FieldEncryptUtil(MybatisBatchUtils mybatisBatchUtils) {
        this.mybatisBatchUtils = mybatisBatchUtils;
    }

    public <T> void updateEncryptField(Class<? extends BaseMapper<T>> mapperClass, Class<T> entityClass, Collection<T> entityList, String... field) {
        entityList.forEach(entity -> {
            for (String s : field) {
                //通过反射获取entity中的属性值
                String value = (String) ReflectUtil.getFieldValue(entity, s);
                //将字段加密
                if (StringUtils.isNotBlank(value) && !StringUtils.equals("null", value)) {
                    //使用sm4对称加密算法进行加密
                    String encrypt = CryptoUtil.sm4Encrypt(value, "sm4算法key");
                    //将加密后的值设置到entity中
                    ReflectUtil.setFieldValue(entity, s, encrypt);
                }

            }
        });
        mybatisBatchUtils.updateBatchById(mapperClass, entityClass, entityList);
    }

    public <T> void updateEncryptField(Class<? extends BaseMapper<T>> mapperClass, Class<T> entityClass, String... field) {
        //获取表名
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        String tableName = tableInfo.getTableName();
        try {
            log.info("开始更新[{}]表", tableName);
            //查询所有数据
            SqlSession sqlSession = SqlHelper.sqlSession(entityClass);
            BaseMapper<T> mapper = SqlHelper.getMapper(entityClass, sqlSession);
            Long l = mapper.selectCount(null);
            //如果数据量大于1000条,分批处理
            if (l > 1000) {
                int pageSize = 1000;
                int pageNum = (int) (l / pageSize + 1);
                for (int i = 1; i <= pageNum; i++) {
                    log.info("开始更新[{}]表第{}页", tableName, i);
                    List<T> list = mapper.selectList(Wrappers.<T>query().last("limit " + pageSize + " offset " + (pageSize * (i - 1))));
                    updateEncryptField(mapperClass, entityClass, list, field);
                }
            } else {
                //小于1000条,直接更新
                Collection<T> entityList = mapper.selectList(null);
                updateEncryptField(mapperClass, entityClass, entityList, field);
            }
            log.info("更新[{}]表成功", tableName);
        } catch (Exception e) {
            log.error("更新[{}]表失败,报错信息:[{}]", tableName, e.getMessage(), e);
            throw new RuntimeException("更新" + tableName + "失败");
        }
    }
}

MybatisBatchUtils

public class MybatisBatchUtils {
    /**
     * 每次处理1000条
     */
    private static final int BATCH_SIZE = 1000;
    protected Log log = LogFactory.getLog(getClass());
    private SqlSessionFactory sqlSessionFactory;

    public MybatisBatchUtils(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    /**
     * 批量保存
     *
     * @param mapperClass 实体对应的mapper的class
     * @param entityClass 批量保存实体的class
     * @param entityList  实体列表
     * @param batchSize   批量保存大小
     * @param <T>         实体类型
     * @return 是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    public  <T> boolean saveBatch(Class<? extends BaseMapper<T>> mapperClass, Class<T> entityClass, Collection<T> entityList, int batchSize) {
        String sqlStatement = SqlHelper.getSqlStatement(mapperClass, SqlMethod.INSERT_ONE);
        return SqlHelper.executeBatch(entityClass, log, entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    @Transactional(rollbackFor = Exception.class)
    public  <T> boolean saveBatch(Class<? extends BaseMapper<T>> mapperClass, Class<T> entityClass, Collection<T> entityList) {
        return saveBatch(mapperClass, entityClass, entityList, BATCH_SIZE);
    }
    /**
     * 批量更新
     *
     * @param mapperClass 实体对应的mapper的class
     * @param entityClass 批量保存实体的class
     * @param entityList  实体列表
     * @param batchSize   批量保存大小
     * @param <T>         实体类型
     * @return 是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    public  <T> boolean updateBatchById(Class<? extends BaseMapper<T>> mapperClass, Class<T> entityClass, Collection<T> entityList, int batchSize) {
        String sqlStatement = SqlHelper.getSqlStatement(mapperClass, SqlMethod.UPDATE_BY_ID);
        return SqlHelper.executeBatch(entityClass, log, entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }
    @Transactional(rollbackFor = Exception.class)
    public  <T> boolean updateBatchById(Class<? extends BaseMapper<T>> mapperClass, Class<T> entityClass, Collection<T> entityList) {
        return updateBatchById(mapperClass, entityClass, entityList, BATCH_SIZE);
    }

    /**
     * 批量处理修改或者插入
     *
     * @param data        需要被处理的数据
     * @param mapperClass Mybatis的Mapper类
     * @param function    自定义处理逻辑
     * @return int 影响的总行数
     * 使用样例
     * mybatisBatchUtils.batchUpdateOrInsert(list, TestGeometryMapper.class, (e, m) -> m.insert(e));
     */
    public <T, U> int batchUpdateOrInsert(List<T> data, Class<U> mapperClass, BiConsumer<T, U> function) {
        int i = 1;
        SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        try {
            U mapper = batchSqlSession.getMapper(mapperClass);
            int size = data.size();
            for (T element : data) {
                function.accept(element, mapper);
                if ((i % BATCH_SIZE == 0) || i == size) {
                    batchSqlSession.flushStatements();
                }
                i++;
            }
            // 非事务环境下强制commit,事务情况下该commit相当于无效
            batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive());
        } catch (Exception e) {
            batchSqlSession.rollback();
            throw e;
        } finally {
            batchSqlSession.close();
        }
        return i - 1;
    }
}

使用方法

只需要调用工具类的方法, 然后入参分别为, Mapper, Entity, 需要进行加密的字段名(实体内的字段名)

     /**
     * ab_contact_history
     */
    public void updateAbContactHistory() {
        fieldEncryptUtil.updateEncryptField(AbContactHistoryMapper.class, AbContactHistory.class,
                "name", "telephone", "phone", "cardId", "age", "sex");
    }

### MyBatis-Plus 的工作原理与实现机制 MyBatis-Plus 是一款基于 MyBatis 的增强工具,在 MyBatis 基础之上仅做增强不做改变,为简化开发、提高效率而生。以下是对其工作原理和内部机制的深入分析: --- #### 1. **核心功能概述** MyBatis-Plus 提供了许多开箱即用的功能,例如分页查询、条件构造器、逻辑删除、乐观锁等。这些功能的实现依赖于底层的拦截器机制以及对 MyBatis 功能的封装。 - **分页查询** MyBatis-Plus 的分页功能通过拦截 SQL 语句并动态修改其行为来实现。具体来说,当接收到分页请求时,会先生成一条 `COUNT` 查询以统计总记录数,然后再生成带有 `LIMIT` 和 `OFFSET` 的分页查询语句[^4]。 - **条件构造器** 条件构造器(Wrapper)是 MyBatis-Plus 提供的一种链式 API,用于构建复杂的查询条件。它将用户的业务逻辑抽象为一组标准的方法调用,并最终将其转换为对应的 SQL 片段。 - **逻辑删除** 逻辑删除是指不真正从数据库中移除数据,而是通过设置某个字段的状态值标记该条记录已被删除。MyBatis-Plus 在执行增删改查操作前,会对指定字段进行判断,从而实现逻辑上的删除效果[^2]。 - **乐观锁** 乐观锁通常利用版本号控制并发冲突。MyBatis-Plus 提供了内置的支持,能够在更新操作时验证版本号是否匹配,如果不匹配则抛出异常提示用户重试。 --- #### 2. **自动填充机制** 为了减少重复代码,MyBatis-Plus 支持字段的自动填充功能。开发者可以通过在实体类中标记需要自动填充的字段及其策略(如创建时间、更新时间),并在插入或更新数据之前由框架自动完成赋值操作[^3]。 此功能的具体实现在于 MyBatis 的拦截器机制。每当触发插入或更新操作时,拦截器会捕获到相关事件并对符合条件的字段按照预设规则进行处理。 --- #### 3. **SQL 拦截器的作用** MyBatis-Plus 广泛使用了 MyBatis 的插件机制(Interceptor)。所有的高级特性几乎都离不开拦截器的帮助。以下列举几个典型的应用场景: - **分页拦截器** 分页功能的核心在于拦截原始 SQL 并对其进行改造,加入 `LIMIT` 和 `OFFSET` 子句以便提取指定范围的数据集。同时还会额外发起一次 `COUNT(*)` 查询用来获取总的记录数量[^4]。 - **逻辑删除拦截器** 当启用了逻辑删除选项后,每次查询都会附加过滤条件排除已标记为“已删除”的记录;而在执行删除动作时,则改为更新对应列而非彻底清除整行数据- **乐观锁拦截器** 更新过程中校验当前记录的版本号是否等于预期值,只有相等的情况下才允许继续提交事务。否则报错告知客户端重新尝试同步最新状态后再操作。 --- #### 4. **源码结构剖析** ##### (1)BaseMapper 接口 `BaseMapper<T>` 是 MyBatis-Plus 提供给用户的最基础 DAO 层接口,已经实现了常见的 CRUD 方法无需手写任何 Mapper XML 文件就能直接调用[^2]。 ```java public interface BaseMapper<T> extends Mapper<T> { int insert(T entity); // 插入单条记录 T selectById(Serializable id); // 根据 ID 查找对象 List<T> selectBatchIds(Collection<? extends Serializable> ids); // 批量根据ID查找列表 boolean updateById(T entity); // 修改单条记录 boolean deleteById(Serializable id); // 删除单条记录 } ``` ##### (2)GlobalConfiguration 类 全局配置管理类负责存储一些影响整个项目的参数设定,像默认的主键生成策略、租户隔离模式开关等均在此处定义。 ##### (3)MetaObjectHandler 抽象类 如果希望定制化某些特殊需求可以继承此类覆写相应方法来自定义处理器的行为逻辑,比如日期格式转化或者加密算法应用等。 --- #### 5. **总结** 综上所述,MyBatis-Plus 不仅仅是对原生态 MyBatis 的简单包装,更是站在巨人的肩膀上去探索更多可能性的一次大胆创新实践成果展示。凭借高度可扩展性和易用性的完美平衡赢得了众多开发者的青睐。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值