news 2026/10/20 7:26:10

MyBatis拦截器实现数据加解密:原理与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis拦截器实现数据加解密:原理与实战

MyBatis 拦截器做数据加解密这个方向,我身边做后端的朋友问过很多次。一开始听到这个需求,很多人第一反应是“直接在 Service 层加密不就完了”,但实际上数据落库前后还有一堆隐性问题:分页查询、批量插入、模糊搜索、日志脱敏,每一项都能让“简单方案”翻车。这篇文章我直接拿一套可落地的实践来聊,从拦截器原理、AES 参数选型,到具体拦截点设计、字段映射策略,再到最后怎么排查问题,一条线讲完。适合正在做数据安全改造、或者被合规需求“赶着上”的 Java 后端开发参考,MyBatis 版本在 3.4 以上应该都能直接套用。

1. 为什么选择在 MyBatis 这一层做统一加解密

先解决一个根本问题:加解密逻辑到底放在哪一层才靠谱。如果你在一个老项目里做敏感字段保护,最忌讳的就是把 AES 调用散落到业务代码里。Service 层改一遍、Controller 层再改一遍,过两个月换套加密算法,那就是全局大扫除,漏掉一个字段都不知道去哪排查。

另一个很现实的场景是:项目里存在大量历史代码,所有出入参都是明文,数据库里也已经躺着几百万条“裸奔”数据。此时想在不改动业务逻辑的前提下给敏感字段加层保护,最省事的做法就是把拦截器架在 MyBatis 的 SQL 执行链路上。所谓“共用一个出入口”,指的就是 MyBatis 的四大组件在 Executor、StatementHandler、ParameterHandler、ResultSetHandler 这四层天然形成了请求流转闭环。写操作进来时在参数处理阶段做加密,读操作出去时在结果处理阶段做解密,加解密对上层 Service 完全透明,对底层数据库也完全透明。

这种方案的核心优势有三个。

  • 对业务代码零侵入。原来怎么写 SQL 还是怎么写,原来怎么传对象还是怎么传,拦截器在背后统一处理。
  • 可集中管控密钥与算法。加密逻辑只出现在一个地方,密钥轮转、算法升级只需要改一处。
  • 便于统一审计。哪些字段加密了、哪些字段没加密,配置一目了然,不用满项目搜索 crypto 相关调用。

当然,这种设计并非没有代价。拦截器一旦强制加密某个字段,意味着所有对该字段的查询条件都必须以加密后的密文来匹配,这里面的坑后面专门讲。

一个常见误解是只有“大厂”才有这种需求。实际上现在很多中小型项目也在应对等保测评、数据安全法这类合规要求,只要涉及手机号、身份证、银行卡、住址这类个人敏感信息,基本都跑不掉。早做比晚做省事,因为你永远不知道存量数据会在哪天变成一把悬着的刀。

2. 拦截器运行机制拆解:四条链路各管哪一段

MyBatis 四层组件拦截器的核心是实现了 Interceptor 接口的类,配合 @Intercepts 注解声明要拦截的方法签名。不少新手一看要写注解就发怵,其实拆开理解并不复杂。

先理清四个组件的职责。

  • Executor 负责整体调度,包括增删改查的入口方法 update、query、queryCursor,以及事务相关方法 commit、rollback。
  • StatementHandler 负责 SQL 语句的预处理,包括 prepare、parameterize、batch 等环节。
  • ParameterHandler 负责把 Java 参数转换成 JDBC 所需的参数,核心方法是 setParameters。
  • ResultSetHandler 负责把 JDBC 返回的结果集映射成 Java 对象,核心方法是 handleResultSets。

如果你只想对读写结果做加解密,最直观的落点是参数处理与结果处理这两层,即 ParameterHandler 和 ResultSetHandler。如果想对 SQL 文本身做改写,比如把明文查询条件改成密文匹配,则需要动 StatementHandler 或 Executor。

这里要特别强调一下:很多人以为一个拦截器能覆盖所有场景,实际上职责拆分越细越可控。我给自己的项目定的规则是,参数与结果尽量在一处拦截器里同时处理,避免出现“结果解密了但参数没加密”的错位状态;而 SQL 改写类需求单独拎出去,用独立的拦截器按 SQL 类型分别处理。

很多人问为什么不能直接在 Executor 层一把梭把所有活干了。Executor 层的参数对象类型比较复杂,可能是单个对象、Map、List,也可能是 @Param 标注的多个参数,需要做大量类型判断。而 ParameterHandler 层拿到的是已经格式化好的参数映射,处理起来反而干净。

还有个细节值得关注:拦截器支持 @Signature 配置多个 method,比如同时拦截 prepare 和 parameterize,这里建议按自己项目实际需要配置,别为了展示技术把所有方法都挂上,每个方法都有额外的类型判断开销。

用生活的类比来理解四层拦截器:Executor 像前台接待,负责总揽预约;StatementHandler 像厨师长,负责决定用哪种锅;ParameterHandler 像配菜员,负责把食材整理成规定形状;ResultSetHandler 像传菜员,负责把成品端上桌。你可以在配菜环节把调料换成加密后的形态,也可以在传菜环节把菜品还原成顾客认识的样子,至于中间环节怎么运作,完全不需要打扰它们。

理解了这套分工,再看拦截器实现代码,就会清晰很多。以 @Intercepts 注解为例,MyBatis 会给被拦截的类生成代理对象,并不是拦截器真的改写了 MyBatis 源码,这一点心里有底就行。

3. 到底该拦截 Executor 还是 ParameterHandler 与 ResultSetHandler

我见过不少方案,直接在 Executor 层 intercept 方法里获得 BoundSql 之后,开始手工解析 SQL 来定位字段和参数的位置。这种方法的问题在于:手写 SQL 解析器难度高、边界条件多,同样的功能 MyBatis 已经帮你在 ParameterHandler 层实现了参数占位符与真实值的绑定,何必重复造轮子。

实际项目中最稳妥的组合是:一个拦截器拦截 ParameterHandler.setParameters 做加密处理,同时拦截 ResultSetHandler.handleResultSets 做解密处理。这样加密发生在参数映射阶段,解密发生在结果集映射阶段,SQL 里本身仍然是“?”占位符,真正替换进去的是已经加密后的值,写进去的、读出来的都是完整的数据形态。

但这里有一个必须提前想清楚的点:拦截器拿到的只是 List

想省事的做法是给实体类或 DTO 字段加上自定义注解,比如 @SensitiveField,然后用反射方式检查参数对象里哪些属性带了这个注解。查询条件单值场景下,我们往往直接把参数对象当成载体来解析,插入更新也是如此。这样虽然要写一些反射代码,但结构清晰,还能顺便做空值判断。

我自己的实现里还处理了 MyBatis 的一个老问题:参数集合里既可能有 POJO 对象,也可能有 Map、List,甚至是 @Param 包装对象。判断逻辑如果是 if (param instanceof POJO) 就会漏掉 Map 场景,实际应该从上到下依次匹配已知类型,并且对 @Param 产生的包装对象做深度遍历,避免有字段“藏在套子里”。

另外一个隐蔽点:如果项目启用了二级缓存,ResultSetHandler 返回的不一定是数据库原生的“裸”结果集,也可能是缓存命中后的对象。拦截器这个时候照常解密即可,因为拦截器作用于返回链路之前,缓存读出的数据也会经过 ResultSetHandler 的处理流程。但如果直接操作 Executor 层就可能存在二次解密的风险,这也是我推荐选择后两层作为主落地点的原因。

4. AES 对称加密的参数选型与实践配置

AES 本身是一个块加密算法,块大小固定 128 位,因此加密模式、填充方式、密钥长度这三个参数决定了一套方案的安全等级。实际开发中最常见的组合是 AES-128-GCM,其次是 AES-128-CBC 配合 PKCS5Padding。

我用 GCM 模式有两个原因。CBC 模式只保证密文的“块级机密性”,不提供完整性校验。意味着如果有人篡改了密文中的某个块,解密端最多报填充错误,改完之后的数据还是能被“部分解密”出来,这在防篡改要求高的场景下是硬伤。GCM 是带认证的加密模式,解密时会同时校验完整性,一旦数据在传输或存储中被改动,解密会直接失败,不会产出垃圾明文。

参数配置上,有三项必须盯紧。

  • 密钥长度与随机性。AES 支持 128、192、256 位密钥,长度越长安全余量越大,但性能损耗也随之增加。Java 中 256 位密钥需要安装 JCE 无限强度权限策略文件,旧项目没配过的直接在加密报错时才会发现。如果项目环境统一且可控,直接用 256 位;如果有兼容性顾虑,128 位也够用。
  • GCM 的 IV 不可重用。同一把密钥下,每次加密必须生成新的随机 IV,否则相同的明文会产生相同的认证标签,安全性大打折扣。很多人在简单 demo 里写死了 IV,这在测试环境没问题,上线就是安全隐患。
  • 填充模式。GCM 内部自己处理了数据的分段逻辑,不太适合再叠加额外填充。Java 中 AES/GCM/NoPadding 是标准写法,不要画蛇添足。

实际中验证过的一组参数推荐如下。

参数项推荐值备注
密钥长度128 或 256 位128 位兼容性最好,256 位安全余量更高
加密模式GCM带完整性认证,优先选择
IV长度12 字节GCM 推荐值,性能最好
认证标签长度128 位(16字节)默认 128 位
编码方式Base64密文存储和传输都用 Base64,避免二进制乱码

AES-GCM 加密后的输出通常包括三部分:IV、密文、认证标签,实际存储时需要把它们拼接在一起。为了方便数据库存储和后续解密,我通常会把 IV 和密文按固定格式拼成一个字符串,例如 iv 用 Base64 编码后加冒号,再接密文。解密时先按冒号拆开,再分别处理。

很多人容易踩的坑是:直接把密文转成 UTF-8 字符串存库,结果某些二进制字符在入库时被截断或变成乱码。正确做法是加密后统一转 Base64,存储长度也会比较固定,对数据库字段类型的选择更友好。

还有一点很重要:AES 是算法,不是加密方案的终点。真正要保护的密钥本身必须妥善管理,绝不能硬编码在代码里或者提交到代码仓库。哪怕你的表结构字段全部加密了,只要密钥躺在源码里被人看到,整个方案等于白做。小型项目可以用环境变量配合配置中心;大型团队建议使用独立密钥管理服务,将密钥与业务系统隔离,并设置自动轮换策略。这一点在高版本的安全扫描和渗透测试里几乎是必查项。

5. 完整实现:基于拦截器的 AES 加解密 Demo 拆解

先说整体架构,再上关键代码。整个模块分为三部分:加密工具类 AESUtil、自定义注解 SensitiveField、拦截器 CryptoInterceptor。核心流程是:拦截 setParameters 时遍历参数对象,找到带注解的字段,将其值加密为密文;拦截 handleResultSets 时遍历结果对象,找到带注解的字段,将密文解密为明文。

加密工具类不刻意做成 Spring Bean,保持无状态静态方法即可,方便在任何环境中复用。核心方法涉及加密时的 IV 生成、Base64 编码,解密时的密文拆分。这里给出一个基础版本,实际项目可以直接在此基础上扩展。

public class AesUtil { private static final int IV_SIZE = 12; private static final String TRANSFORMATION = "AES/GCM/NoPadding"; public static String encrypt(String plainText, SecretKey key) { try { byte[] iv = new byte[IV_SIZE]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(iv); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv)); byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(iv) + ":" + Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new RuntimeException("加密失败", e); } } public static String decrypt(String cipherText, SecretKey key) { try { String[] parts = cipherText.split(":"); byte[] iv = Base64.getDecoder().decode(parts[0]); byte[] encrypted = Base64.getDecoder().decode(parts[1]); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv)); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted, StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException("解密失败", e); } } }

这里有个关键点:如果你在 GCM 模式下手动指定了每次加密随机生成 IV,那么解密时就必须严格使用加密时生成的那个 IV。示例代码里把 IV 放在密文前面存储,就是为了后续解密时能找到对应 IV,否则无法解密。

注解类非常轻量,主要是标记作用。需要注意的是,注解的保留策略要设置为 RUNTIME,否则拦截器里用反射获取不到。

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface SensitiveField { }

拦截器是核心,实现思路如下。

@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}), @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) public class CryptoInterceptor implements Interceptor { private SecretKey secretKey; @Override public Object intercept(Invocation invocation) throws Throwable { if (invocation.getTarget() instanceof ParameterHandler) { processParameter(invocation); } else if (invocation.getTarget() instanceof ResultSetHandler) { processResult(invocation); } return invocation.proceed(); } private void processParameter(Invocation invocation) throws Exception { ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); Field parameterField = ParameterHandler.class.getDeclaredField("parameterObject"); parameterField.setAccessible(true); Object parameterObject = parameterField.get(parameterHandler); encryptFields(parameterObject); } private void processResult(Invocation invocation) throws Exception { Object result = invocation.proceed(); decryptFields(result); return result; } private void encryptFields(Object obj) { // 反射遍历字段,匹配 @SensitiveField 注解 } private void decryptFields(Object obj) { // 反射遍历字段,匹配 @SensitiveField 注解,转换为明文 } }

一个容易被忽略的点是 ParameterHandler 的 parameterObject 字段声明在 MyBatis 的基类 BaseBuilderHandler 内部还是具体实现类里,不同 MyBatis 版本字段名可能有所变化。建议在 processParameter 中通过反射遍历当前类的所有字段,动态查找到名为 parameterObject 的字段,而不是写死路径,这样升级 MyBatis 小版本时不会炸。

processResult 中 invocation.proceed() 的返回值可能是单个对象,也可能是 List,在 decryptFields 里都要判断。MyBatis 对于单条查询返回单对象,对于列表查询返回 List,处理时注意区分。

另外一个常见但必须处理的场景:解密方法对结果对象做反射访问时,如果字段值是 null,跳过即可;如果原本就是普通字符串(比如未加密的历史数据),解密会报错,需要提前判断密文格式是否符合预期,再做解密尝试。实际项目中我习惯让 decrypt 方法内部先检查字符串是否包含分隔符,如果不符合格式直接原样返回,避免历史数据导致大批量解密失败。

6. 生命周期管理:密钥配置与拦截器装配

拦截器写完了,关键问题是在 Spring Boot 环境里怎么让它生效、密钥从哪里注入进来。

拦截器装配有两种方式:XML 配置中的 plugins 标签,或者 Java Configuration 中手动添加 Interceptor 实例。Spring Boot 项目更推荐第二种,直接在配置类中构建拦截器对象,同时把密钥注入进去。

@Configuration public class MybatisConfig { @Bean public CryptoInterceptor cryptoInterceptor(@Value("${crypto.key}") String cryptoKey) { CryptoInterceptor interceptor = new CryptoInterceptor(); interceptor.setSecretKey(buildKey(cryptoKey)); return interceptor; } }

注意一点:如果拦截器被声明为 Spring 的 @Component,它会被 Spring 容器托管,同时也需要手动注册到 MyBatis 配置里。常见的坑是项目用了 MyBatis 自动配置,@Component 已经被自动扫描并加入全局配置了,但你后来又手动 new 了一个新的拦截器实例,导致出现两份拦截器,参数处理重复加密,结果处理重复解密。这种问题非常隐蔽,因为偶尔“正常工作”,压力大的时候才出现怪异的密文,排查难度极高。

密钥管理上,我建议使用专用于加密的独立配置项,而不是把支付宝、数据库连接密码等所有密钥放到同一个 KV 里。针对敏感字段建一个单独的密钥,一旦发现有泄露风险,只影响该密钥关联的字段,不影响其他系统组件。密钥存储方式,至少要做到环境变量或配置中心加密字段,直接把明文 Base64 写在 application.yml 里属于很危险的做法。

初次上线加解密功能时,还有一个“存量数据如何处理”的经典问题。三种策略供参考:

  • 双写兼容。上线后新写入的数据加密,旧数据保持明文,读取时尝试解密,失败就返回明文,后台写个全量加密脚本逐步刷数据。这个方案相对平滑,但系统里会同时存在明文与密文,审计时比较难看。
  • 停机迁移。直接停服,全表扫描重写,适用于数据量小、可接受短时中断的场景。最干净,但代价高。
  • 影子表迁移。新建一张新表,写入加密数据,通过开关切换读写流量,逐步迁移。工作量最大,但对在线业务影响最小。

我个人比较推荐双写兼容配合离线清洗脚本,因为大部分项目的加密改造都不是从零开始的,存量数据一旦很多,停服迁移往往会被业务方否决。等加密上线跑稳了,再通过后台任务把所有明文记录替换成密文,最终状态达到全密文。

7. 解锁更多场景:分页、批量插入与模糊搜索该怎么处理

基础拦截器已经能把“传对象进去加密、查出来还原”这件事跑通了,但真实系统哪有这么规矩,分页查询、批量插入、模糊搜索这几个场景是拦路虎。

先说分页查询。MyBatis 的分页插件(常见的有物理分页插件)通常基于 Executor 拦截器实现,它在 query 方法中构建新的分页参数并覆盖原 SQL。如果我们的加密拦截器只拦截 ParameterHandler,那么分页插件拦截器先执行还是后执行会直接影响参数处理链路。建议让分页插件保持默认执行顺序,然后确保加密拦截器在参数绑定阶段仍能拿到完整参数集合。分页参数本身不是敏感字段,不会带 @SensitiveField 注解,所以加密逻辑不影响分页。

批量插入场景比较麻烦。批量插入通常是一个 List 参数,里面每个元素都可能含敏感字段。如果只加密了 List 本身而没有人处理内部元素,那密文入库的是对象内存地址,灾难性故障。所以在 encryptFields 方法里必须要支持 List 内部元素的遍历处理,同样 result 集合也要逐个解密。我在代码里专门写了一个处理 List 的分支,避免被 MyBatis 的参数包装类影响。

模糊搜索则是另一个完全不同的维度。数据库层面,如果某个字段加密成密文,那么 LIKE '%keyword%' 这种查询天然失效。因为密文是不可逆的,数据库无法从密文反推明文做模糊匹配。要支持模糊搜索,大体有三条路:

  • 放弃数据库模糊搜索,功能上改为精确匹配。
  • 增加冗余明文列,仅用于模糊搜索,且做好专门的脱敏处理,控制可见范围。
  • 引入加密可搜索方案,比如基于哈希的盲化索引,但实现难度较高,小型项目不一定划算。

我做过一个取舍:手机号这类字段模糊查询需求不强烈,直接退化到精确匹配;姓名这类字段增加一个冗余的“明文脱敏列”只存脱敏后的短数据,用于模糊匹配,同时保留加密列用于精确查询。这种方案在业务上往往能被接受,同时控制了实现复杂度。

模糊搜索还有一个坑:拦截器加密条件字段时,如果你用 ResultMap 里专门建了一个不带注解的查询 DTO 对象,里面放了一个叫 keyword 的字段,但它和数据库里加密的 name 字段对应不上。这种情况下拦截器是加密不到 keyword 的,因为注解是在新 DTO 的字段上,而不是数据库实体类上。这是设计上需要提前规划清楚的点——哪些入口属于“加密条件”,哪些入口属于“脱敏搜索条件”,要单独约定。

8. 常见问题与排查技巧实录

这套方案在真实项目中踩过的坑不少,挑几个高频的整理成速查表,都是血泪经验。

问题现象根因分析解决方案
解密报错 InvalidAlgorithmParameterExceptionGCM 模式下 IV 长度不为 12 字节固定 IV 生成长度,统一使用 12 字节随机 IV
加密后的字符串入库后出现乱码密文 Base64 被当成 UTF-8 文本处理确认数据库字段类型为 VARCHAR,长度足够存放 Base64 结果
二次加密导致同一条数据被加密两遍项目中存在两个拦截器实例排查自动配置与手动 new 并存的情况,只保留一个实例
查询条件带密文,查不到数据拦截器只加密了插入字段,没有加密查询条件字段统一在参数处理中检查 @SensitiveField 注解,所有入口一视同仁
历史明文数据解密报错存量记录未加密,被新逻辑当成密文处理在解密方法中对字符串格式做校验,不合法直接跳过或做兼容标记
分页插件和加密拦截器顺序混乱两个拦截器都修改 SQL 相关行为,执行顺序不同导致参数错位日志打印每个拦截器的执行顺序,按需调整 @Order 或注册顺序

一个特别有价值的排查技巧:在拦截器 intercept 方法里打日志,输出正在处理的参数对象类型与当前字段名,能快速定位“到底有没有进入加密逻辑”。很多问题出在位置判断上,参数对象包装类型不一致导致遍历没走到你的预期分支,日志是唯一能确认这条链路的途径。

另一个隐蔽坑是 Spring 事务与二级缓存的叠加效应。当 Service 方法上标注了 @Transactional,MyBatis 的一级缓存会在当前 SqlSession 中缓存结果,如果执行了更新操作后再次查询,查询结果可能命中了一级缓存,此时 ResultSetHandler 可能不再被调用。这意味着更新后的解密逻辑可能不会生效。如果发现“明明改了加密逻辑,但老数据怎么读都还是密文”,先查是不是缓存层的问题。解决方案是加缓存版本号或清缓存再读。

日志脱敏也是很多人忽略的部分。如果加密方案只在数据落库时生效,但 SQL 日志里已经把参数明文打出来了,那整个数据保护效果就会削减。打开 MyBatis 的 SQL 日志后,preparing 部分的占位符参数通常都是明文参数值,需要在日志框架层面同步配置脱敏拦截,或使用专门的日志组件对打印的 SQL 参数做过滤。加了日志脱敏之后,排查问题的时候也方便,因为日志里看不到明文,密文和加密条件也能对上,非常有助于定位是参数本身错了还是加密流程错了。

另外,批量场景下还需要注意异常回滚策略。如果加密过程中某个字段抛异常,默认情况下整个事务会回滚,这会让业务方很痛苦。我建议在拦截器中区分“加密失败”和“参数不合法”两种异常,前者直接往上抛并终止事务,后者在安全评估后选择返回业务层自定义异常,而不是让 MyBatis 抛一个难以理解的加密错误。

9. 性能考量与压测观察

很多人天然担心加解密影响性能,实际上 AES 对称加密速度很快,瓶颈往往不在算法本身,而在于反射遍历字段和序列化开销。

我在一个中等规模的项目里做过一次简单压测,数据量 10 万条,每条记录包含一个 18 位身份证号字段。使用 AES-128-GCM 加密,单条加密耗时大约在 30 微秒量级,解密类似。批量插入场景下,1000 条记录的总耗时约 50 毫秒以内,这相对于一次网络请求动辄几十毫秒的 IO 耗时,基本可以忽略。

真正的性能风险点在于反射。拦截器每处理一个对象都要做字段遍历和注解查找,如果对象字段特别多,比如 30 个字段,而这个对象在批量场景中又有 1000 条,反射开销就会被放大。优化手段有两种:一是使用反射字段缓存,把类的 Field 数组缓存到 ConcurrentHashMap 中,避免每次重复 getDeclaredFields;二是只在标注了 @SensitiveField 的类上启用加密逻辑,其他类直接放行,减少无效遍历。

还有一种优化思路是只在读操作时开启解密,写操作时不做不必要的字段遍历。比如 SQL 命令为 INSERT、UPDATE 时只处理加密,SELECT 时只处理解密。这样既能减少三分之一的反射调用,也让职责更明确。

但我要说一句:为了性能把加解密设计得过于复杂是不值得的。与其用各种花哨的缓存机制,不如把参数对象结构设计得简单一点,多写几个明确的传参 DTO,让拦截器的遍历逻辑更可控,比任何缓存都稳。

10. 一个并行方案:TypeHandler 是否更合适

说完拦截器,顺便聊聊很多人会问的 TypeHandler 方案。TypeHandler 是 MyBatis 中专门负责 Java 类型与 JDBC 类型之间映射的组件,允许你自定义类型处理器。比如给加密字段定义专用的 TypeHandler,在插入时加密,在查询时解密。

TypeHandler 方案和拦截器方案最大的区别在于作用范围。TypeHandler 只作用于单个字段的映射,你必须为每个加密字段显式指定 typeHandler。这意味着 XML 里每个字段都要写 typeHandler 引用,或者通过 统一声明。如果加密字段很多,XML 配置会非常臃肿,而且新增加密字段时很容易漏配。

拦截器方案则是统一编码在全局逻辑里,可维护性更好。TypeHandler 也有它的价值场景:如果你只需要处理两三个固定字段,而且不想引入写拦截器的复杂度,TypeHandler 反而是更轻量的选择。

我自己的使用习惯是:涉及加密字段数量少且字段固定,用 TypeHandler 简单省事;涉及多个模块多个表十来个加密字段,直接上拦截器方案。两者并非水火不容,也可以共存,但尽量让每个字段只被其中一个机制处理,避免重复加解密。

11. 踩坑之后的扩展:先做单表试点再做全局推广

项目推进过程中还有一个组织层面的建议:不要试图一步到位把系统里所有敏感字段全部加密。

最稳妥的路径是先选一个表、一个字段做试点,比如手机号。确认加密、解密、查询、分页、日志脱敏全部正常后,再推广到身份证号、银行卡号、地址等字段。这么做有几个好处:

  • 问题暴露在最小范围,不会引发全局故障。
  • 能让业务方看到相同的改造模式,对后续工作有信心。
  • 逐步形成一套内部检查清单,推广时按清单逐项审计。

如果一开始就把几十个字段全加密,一旦出现二次加密、解密失败、搜索失效等问题,排查面太广,很容易在“修复加密”和“保持业务可用”之间左右为难。

另外一个值得尽早确定的点是密钥隔离。不同敏感级别或不同业务模块可以使用不同的密钥,避免一个泄漏导致全部沦陷。密钥本身不要写死在代码里,而是通过配置中心下发,配合定期轮换策略。我见过把密钥写死在常量类里的项目,代码一旦打成 jar 包传到仓库里,等于把保险柜钥匙跟保险柜一起送出去了。

试点改造做完后,再洗存量数据。洗数据建议放在业务低峰期执行,并加上限流和进度监控。数据量特别大的表可以用分批条件更新,一次处理一万条左右,避免大事务压垮数据库。

最后再分享一个扩展技巧:在 encrypted 字段旁边可以增加一个冗余列用来标记是否已加密,比如 is_encrypted 字段。这样解密逻辑中遇到这个标记为未加密的数据时,可以直接跳过解密逻辑,避免所有历史数据都解析出错。等全量刷完加密后,这个标记字段就可以逐步废弃,最终删除。这个小技巧在实际存量迁移中帮了大忙,比单纯靠“字符串格式是否含冒号”判断靠谱得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 7:25:38

Photoshop影调调色全攻略:色阶、曲线与色彩工具实战

Photoshop教程三:影调&调色工具每次看到有人对着 Photoshop 界面问“曲线到底是什么、拉完色阶图怎么还是灰的、为什么照着网上的参数调色结果肤色像中毒”,我就知道这门《Photoshop教程三:影调&调色工具》是大多数人的刚需。调色不是…

作者头像 李华
网站建设 2026/10/20 7:25:27

FPGA实现HDMI收发:从TMDS解码到发送编码全流程解析

这块板子在我工作台上躺了快两周,终于把计划里的第二阶段调通了。上一篇文章写完了基础环境的搭建和方案选型,当时给自己留了两个目标:一是把外部输入的HDMI信号完整接收并解码成并行RGB数据,二是自己生成视频时序从FPGA侧发出去。…

作者头像 李华
网站建设 2026/10/20 7:24:47

H5开红包特效全解析:状态机、CSS动画与事件回调实践

简介:一份可直接运行的 H5 开红包特效完整代码包,面向需要为社交电商、营销活动增加互动趣味的前端开发者和初学者,帮助快速实现点击红包开启动画、随机金额展示等常见玩法。压缩包共 9 个文件,体积仅 130KB,包含 HTML…

作者头像 李华
网站建设 2026/10/20 7:23:51

AI芯片软硬件协同设计:从算力焦虑到性能落地的关键技术与实践

1. 从"算力焦虑"说起:为什么AI芯片的软硬件协同设计成了必答题这两年跟做芯片和做算法的朋友聊天,绕不开的一个词就是"算力焦虑"。模型参数从亿级飙到万亿级,训练集群的规模从几百张卡扩到几万张卡,可大家发现…

作者头像 李华
网站建设 2026/10/20 7:23:17

SPDC-YOLO:无人机航拍小目标检测的YOLOv8改造实践

做无人机航拍检测的朋友,十有八九都经历过这种场面:一张几千乘几千的航拍大图里,车只有十几个像素,人要眯着眼才看得见,模型却连一个都框不出来。YOLOv8本身已经很快,但拿到这种小目标密集的场景下&#xf…

作者头像 李华
网站建设 2026/10/20 7:23:11

如何用Ovis-Image在ComfyUI中实现文生图:官方工作流保姆级教程

【免费下载链接】Ovis-Image 项目地址: https://ai.gitcode.com/hf_mirrors/Comfy-Org/Ovis-Image 点击查看 免费下载 想在 ComfyUI 里跑 Ovis-Image 文生图,却不知道怎么放模型、配节点?本文基于 Ovis-Image 官方仓库,手把手带你…

作者头像 李华