使用MyBatis TypeHandler實(shí)現(xiàn)數(shù)據(jù)加密與解密的具體方案
在我們?nèi)粘5拈_發(fā)工作中,經(jīng)常會遇到一些敏感數(shù)據(jù)需要存儲,比如用戶的手機(jī)號、身份證號、銀行卡號等。為了保障數(shù)據(jù)安全,我們通常會對這些敏感信息進(jìn)行加密后再存入數(shù)據(jù)庫。
如果每次在存入數(shù)據(jù)庫之前都手動(dòng)調(diào)用加密方法,在查詢出來之后再手動(dòng)調(diào)用解密方法,不僅繁瑣,而且容易出錯(cuò),也不利于代碼的維護(hù)。
有一種更優(yōu)雅、更通用的方式來解決這個(gè)問題,答案是:使用MyBatis的TypeHandler。
我們通過對用戶手機(jī)號進(jìn)行加密存儲和解密查詢來詳細(xì)講解如何自定義和使用 MyBatis的TypeHandler
1. 核心概念:什么是 TypeHandler?
TypeHandler,顧名思義,就是類型處理器。它在 MyBatis 中扮演著一個(gè)非常關(guān)鍵的角色:在 Java 對象和數(shù)據(jù)庫 JDBC 類型之間進(jìn)行轉(zhuǎn)換。
- 寫入數(shù)據(jù)庫時(shí):MyBatis 會使用 TypeHandler 將你的 Java 對象(比如一個(gè)String)轉(zhuǎn)換成數(shù)據(jù)庫可以理解的 JDBC 類型(比如VARCHAR)。
- 從數(shù)據(jù)庫讀取時(shí):MyBatis 會使用 TypeHandler 將從數(shù)據(jù)庫中查詢到的 JDBC 類型(比如VARCHAR)轉(zhuǎn)換回你的 Java 對象(比如一個(gè)String)。
MyBatis 內(nèi)置了大量的 TypeHandler 來處理常見的 Java 類型和 JDBC 類型之間的轉(zhuǎn)換。但當(dāng)我們有特殊需求時(shí),比如需要對數(shù)據(jù)進(jìn)行加密解密,就需要自定義 TypeHandler。
2. 實(shí)戰(zhàn)場景
- 加密:當(dāng)我們將一個(gè) Java 對象(比如User)中的手機(jī)號(String類型)存入數(shù)據(jù)庫時(shí),MyBatis 能自動(dòng)將其加密成一個(gè)密文String后再存儲。
- 解密:當(dāng)我們從數(shù)據(jù)庫查詢出這個(gè)密文String時(shí),MyBatis 能自動(dòng)將其解密成原始的手機(jī)號String后,再設(shè)置到 Java 對象中。
我們可以讓TypeHandler來處理指定的java類型將其轉(zhuǎn)換成jdbc中的類型,
而為了避免將所有的String類型都通過TypeHandler來進(jìn)行轉(zhuǎn)換(因?yàn)槲覀冎幌朕D(zhuǎn)換存儲手機(jī)號的String)也是為了讓這個(gè)過程更清晰,我們引入一個(gè)自定義的 Java 類型Encrypt,用它來標(biāo)記需要進(jìn)行加密解密的字段。
3. 代碼實(shí)現(xiàn)步驟
- 定義一個(gè)標(biāo)記接口 / 類:創(chuàng)建Encrypt類。
- 實(shí)現(xiàn)自定義 TypeHandler:創(chuàng)建EncryptTypeHandler類,實(shí)現(xiàn)加密和解密邏輯。
- 配置 TypeHandler:告訴 MyBatis 我們的 TypeHandler 存在。
- 在代碼中使用:修改Mapper接口,享受自動(dòng)加密解密的便利。
步驟 1:定義 Encrypt 類
這個(gè)類的主要作用是作為一個(gè) “標(biāo)記”,告訴 MyBatis:“凡是用這個(gè)Encrypt類型聲明的字段,都需要使用對應(yīng)的EncryptTypeHandler來處理”。
它可以非常簡單,甚至可以是空的。
package com.dao.dataobject;
import lombok.Data;
/**
* 一個(gè)標(biāo)記接口,用于標(biāo)識需要加密/解密的字段
*/
@Data
public class Encrypt {
private String value;
public Encrypt(byte[] decrypt) {
}
public Encrypt(String value) {
this.value = value;
}
}步驟 2:實(shí)現(xiàn) EncryptTypeHandler
這是整個(gè)功能的核心。我們需要實(shí)現(xiàn) MyBatis 的TypeHandler接口,并在其中編寫 AES 加密和解密的邏輯。
package com.dao.handler;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.bit.lotterySystem.dao.dataobject.Encrypt;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 自定義的 TypeHandler,用于處理 Encrypt 類型與 VARCHAR 類型之間的轉(zhuǎn)換(包含加密解密)
*/
@MappedTypes(Encrypt.class)//告訴 MyBatis,這個(gè) Handler 是處理 Java 中的 Encrypt 類型的。
@MappedJdbcTypes(JdbcType.VARCHAR)//處理后的jdbc類型
public class EncryptTypeHandler implements TypeHandler<Encrypt> {
// 注意:在生產(chǎn)環(huán)境中,密鑰絕不能硬編碼在代碼里!應(yīng)從配置文件或環(huán)境變量中讀取。
private final byte[] key = "123456789abcdefg".getBytes(StandardCharsets.UTF_8); // 16位密鑰,AES-128
/**
* 設(shè)置參數(shù)
* 將 Encrypt 對象中的明文加密后,以 String 形式存入數(shù)據(jù)庫。
*
* @param ps SQL預(yù)編譯對象
* @param i 需要賦值的索引位置
* @param parameter 原本位置i需要賦的值
* @param jdbcType jdbc的類型
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement ps, int i, Encrypt parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null || parameter.getValue() == null) {
ps.setString(i, null);
return;
}
// 1. 獲取需要加密的明文
String plaintext = parameter.getValue();
// 2. 使用 Hutool 的 SecureUtil 進(jìn)行 AES 加密
AES aes = SecureUtil.aes(key);
String ciphertext = aes.encryptHex(plaintext); // encryptHex 返回十六進(jìn)制字符串
// 3. 將加密后的密文設(shè)置到 PreparedStatement
ps.setString(i, ciphertext);
}
/**
* 獲取值
* 將從數(shù)據(jù)庫讀出的密文 String 解密后,封裝成 Encrypt 對象。
*
* @param rs 結(jié)果集
* @param columnName 索引名
* @return
* @throws SQLException
*/
*/
@Override
public Encrypt getResult(ResultSet rs, String columnName) throws SQLException {
String ciphertext = rs.getString(columnName);
return decrypt(ciphertext);
}
/**
* 獲取值,從 ResultSet 中根據(jù)列索引獲取值(從數(shù)據(jù)庫讀取)
* @param rs 結(jié)果集
* @param columnIndex 索引位置
* @return
* @throws SQLException
*/
@Override
public Encrypt getResult(ResultSet rs, int columnIndex) throws SQLException {
String ciphertext = rs.getString(columnIndex);
return decrypt(ciphertext);
}
/**
* 獲取值,從 CallableStatement 中獲取值(存儲過程相關(guān))
* @param cs 結(jié)果集
* @param columnIndex 索引位置
* @return
* @throws SQLException
*/
@Override
public Encrypt getResult(CallableStatement cs, int columnIndex) throws SQLException {
String ciphertext = cs.getString(columnIndex);
return decrypt(ciphertext);
}
/**
* 通用的解密方法
*/
private Encrypt decrypt(String ciphertext) {
if (ciphertext == null) {
return null;
}
// 1. 使用 Hutool 的 SecureUtil 進(jìn)行 AES 解密
AES aes = SecureUtil.aes(key);
String plaintext = aes.decryptStr(ciphertext, StandardCharsets.UTF_8);
// 2. 將解密后的明文封裝成 Encrypt 對象返回
return new Encrypt(plaintext);
}
}代碼解讀:
@MappedTypes和@MappedJdbcTypes:這兩個(gè)注解是關(guān)鍵,它們建立了Encrypt類型和VARCHAR類型之間的映射關(guān)系,讓 MyBatis 能夠自動(dòng)找到并使用這個(gè)TypeHandler。setParameter:在執(zhí)行insert、update時(shí),MyBatis 會調(diào)用此方法。我們在這里實(shí)現(xiàn)加密邏輯。getResult(三個(gè)重載方法):在執(zhí)行select 后,MyBatis 會調(diào)用這些方法來填充結(jié)果對象。我們在這里實(shí)現(xiàn)解密邏輯。- 依賴:代碼中使用了Hutool工具庫來簡化 AES 加密解密的代碼。需要添加依賴:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.41</version>
</dependency>步驟 3:配置 MyBatis
我們需要讓 MyBatis 知道自定義的TypeHandler在哪里 他才會起作用。
有兩種常用方式:
方式一:在 MyBatis 配置文件中注
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeHandlers>
<package name="com.dao.handler"/>
</typeHandlers>
</configuration>這種方式最推薦,它會掃描指定包下所有實(shí)現(xiàn)了TypeHandler接口并帶有@MappedTypes注解的類。
方式二:在 Spring Boot 中通過注解自動(dòng)掃描
如果你使用 Spring Boot,并且在application.yml或application.properties中配置了 mybatis.type-handlers-package,Spring Boot 啟動(dòng)時(shí)會自動(dòng)掃描并注冊該包下的 TypeHandler。
# application.yml mybatis: type-handlers-package: com.dao.handler # 其他配置...
步驟 4:在業(yè)務(wù)代碼中使用
現(xiàn)在,我們的TypeHandler已經(jīng)準(zhǔn)備就緒,可以在Mapper接口中使用了。
假設(shè)我們有一個(gè)UserMapper,需要根據(jù)手機(jī)號查詢是否有用戶。
package com.dao.mapper;
import com.bit.lotterySystem.dao.dataobject.Encrypt;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
/**
* 根據(jù)手機(jī)號統(tǒng)計(jì)用戶數(shù)量
* @param phoneNumber 注意這里的參數(shù)類型是 Encrypt
* @return 用戶數(shù)量
*/
@Select("SELECT count(*) FROM user WHERE phone_number = #{phoneNumber}")
int countPhoneNumber(@Param("phoneNumber") Encrypt phoneNumber);
}使用它:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 校驗(yàn)手機(jī)號是否已被使用
* @param phoneNumber 原始的手機(jī)號明文
* @return 是否被使用
*/
private boolean checkPhoneNumberUsed(String phoneNumber) {
// 我們只需要將明文手機(jī)號封裝進(jìn) Encrypt 對象即可
// MyBatis 會在后臺自動(dòng)調(diào)用 EncryptTypeHandler 進(jìn)行加密
int count = userMapper.countPhoneNumber(new Encrypt(phoneNumber));
return count > 0;
}
// ...
}魔法發(fā)生了:
- 當(dāng)你調(diào)用
userMapper.countPhoneNumber(new Encrypt(phoneNumber))時(shí),MyBatis 發(fā)現(xiàn)參數(shù)類型是Encrypt - MyBatis 找到注冊的EncryptTypeHandler
- MyBatis 調(diào)用EncryptTypeHandler.setParameter()方法。
- setParameter方法將傳入的手機(jī)號加密成密文(例如
a1b2c3d4...) - 最終執(zhí)行的 SQL 變成了
SELECT count(*) FROM user WHERE phone_number = 'a1b2c3d4...'
如果是查詢操作,假設(shè)數(shù)據(jù)庫中 phone_number 字段存儲的是密文,MyBatis 會在獲取結(jié)果時(shí)自動(dòng)調(diào)用 getResult 方法進(jìn)行解密,將密文轉(zhuǎn)換回 Encrypt對象。
4. 總結(jié)與優(yōu)勢
通過以上步驟,我們成功實(shí)現(xiàn)了數(shù)據(jù)庫字段的自動(dòng)加密和解密。
這種方式的優(yōu)勢非常明顯:
- 代碼解耦:加密解密邏輯被封裝在TypeHandler中,業(yè)務(wù)代碼變得非常干凈,無需關(guān)心加密細(xì)節(jié)。
- 通用性強(qiáng):只要在任何需要加密的地方使用 Encrypt類型聲明字段或參數(shù),MyBatis 就會自動(dòng)處理。
- 可維護(hù)性高:如果未來需要更換加密算法或密鑰,只需要修改 EncryptTypeHandler這一個(gè)地方即可,無需改動(dòng)所有業(yè)務(wù)代碼。
- 優(yōu)雅高效:利用了 MyBatis 的擴(kuò)展機(jī)制,是處理這類問題的 “最佳實(shí)踐” 之一。
以上就是使用MyBatis TypeHandler實(shí)現(xiàn)數(shù)據(jù)加密與解密的具體方案的詳細(xì)內(nèi)容,更多關(guān)于MyBatis TypeHandler數(shù)據(jù)加解密的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MyBatis-Plus主鍵生成策略的實(shí)現(xiàn)方法
MyBatis-Plus提供了多種主鍵生成策略,包括自增、UUID、Redis和雪花算法,本文就來介紹一下,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
解決idea services窗口不見的一種特殊情況(小白采坑系列)
這篇文章主要介紹了解決idea services窗口不見的一種特殊情況(小白采坑系列),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java實(shí)現(xiàn)樹形List與扁平List互轉(zhuǎn)的示例代碼
在平時(shí)的開發(fā)中,我們時(shí)常會遇到需要將"樹形List"與"扁平List"互轉(zhuǎn)的情況,本文為大家整理了Java實(shí)現(xiàn)樹形List與扁平List互轉(zhuǎn)的示例代碼,希望對大家有所幫助2023-05-05
Spring?Boot?MQTT?Too?many?publishes?in?progress錯(cuò)誤的解決方
本文介紹Spring?Boot?MQTT?Too?many?publishes?in?progress錯(cuò)誤的解決方案,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-07-07

