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

