Mybatis和Jpa添加数据时自动加解密


前言

在日常开发中我们可能会处理一些敏感信息,例如存储个人信息。这时候我们便需要对数据进行加解密。一般的流程是在存入数据库前对数据进行加密,在查询数据后对数据进行解密然后传给前端。但是如果每一次存取操作都要加解密都要手动操作的话就太过麻烦了,因此我们需要编写代码让ROM框架可以自动处理这些过程。


提示:以下是本篇文章正文内容,下面案例可供参考

一、编写加解密工具类

加解密的方法有很多,我这里只是举一个例子,自己平时喜欢用哪种就用哪种就可以了。
示例:我自己使用的是DES。这里是定义了一个加解密的接口,然后不同加解密的算法实现接口即可。

public interface CryptoHandler {
    String encrypt(String data) throws Exception;

    String decrypt(String encryptedData) throws Exception;
}
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;

/**
 * @author Namego
 * @date 2023/6/23 13:55
 */
public class DESedeCryptHandler implements CryptoHandler {
    private static final String ENCRYPTION_ALGORITHM = "DESede"; // 使用TripleDES算法

    private static final String KEY = "yourSecretKey";

    @Override
    public String encrypt(String data) throws Exception {
        if(data == null){
            return data;
        }
        SecretKeySpec secretKey = generateSecretKey(KEY);
        Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    @Override
    public String decrypt(String encryptedData) throws Exception {
        if(encryptedData == null){
            return encryptedData;
        }
        SecretKeySpec secretKey = generateSecretKey(KEY);
        Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }

    private static SecretKeySpec generateSecretKey(String key) throws Exception {
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hashedKey = md.digest(keyBytes);
        byte[] truncatedKey = new byte[24];
        System.arraycopy(hashedKey, 0, truncatedKey, 0, truncatedKey.length);
        return new SecretKeySpec(truncatedKey, ENCRYPTION_ALGORITHM);
    }

}

二、使用Mybatis

1.引入库

库就是mybatis的那些依赖,这里就不一一列举了。

2.编写Handler继承Mybatis的BaseTypeHandler

这里我是使用String类型,因为加解密的数据都是字符串,你可以尝试使用Object去处理。注意代码里的无参构造函数
代码如下(示例):

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
 * @author Namego
 * @date 2023/6/27 15:31
 */
public class EncryptionTypeHandler extends BaseTypeHandler<String> {
    private static CryptoHandler deSedeCryptHandler = new DESedeCryptoHandler();
	
	/**
	* 这里无参构造函数我写出来是因为要强调这里必须要有无参构造函数,不然mybatis框架会报错。如果你自己定义了有参数的构造函数,一定要声明无参构造
	*/
    public EncryptionTypeHandler() {
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        // 在写入数据库之前对字段进行加密
        String encryptedValue = null;
        try {
            encryptedValue = deSedeCryptHandler.encrypt(parameter);
        } catch (Exception e) {
            e.printStackTrace();
        }
        ps.setString(i, encryptedValue);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 在从数据库读取字段值后对字段进行解密
        String encryptedValue = rs.getString(columnName);
        if (encryptedValue == null) {
            return null;
        }
        String decryptedValue = null;
        try {
            decryptedValue = deSedeCryptHandler.decrypt(encryptedValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedValue;
    }

    @Override
    public String getNullableResult(ResultSet resultSet, int i) throws SQLException {
        // 实现解密逻辑
        String encryptedValue = resultSet.getString(i);
        if (encryptedValue == null) {
            return null;
        }
        String decryptedValue = null;
        try {
            decryptedValue = deSedeCryptHandler.decrypt(encryptedValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedValue;
    }

    @Override
    public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        // 实现解密逻辑
        String encryptedValue = callableStatement.getString(i);
        if (encryptedValue == null) {
            return null;
        }
        String decryptedValue = null;
        try {
            decryptedValue = deSedeCryptHandler.decrypt(encryptedValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedValue;
    }
}

3、在你需要加解密的字段上添加注解

在字段上添加 @TableField(typeHandler = EncryptionTypeHandler.class)注解
示例代码如下:

/**
 * @author Namego
 * @date 2023/6/22 16:25
 */
@Data
@Accessors(chain = true)
@TableName("stu_user")
public class StuUserEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    @TableId
    private Long userId;
    
    /**
     * 学号
     */
    @NotBlank(message = "学号不能为空",groups = UpdateGroup.class)
    @TableField(typeHandler = EncryptionTypeHandler.class)
    private String schoolNumber;

    /**
     * 姓名
     */
    @NotBlank(message = "姓名不能为空",groups = UpdateGroup.class)
    @Size(message = "姓名长度不得小于2",min = 2)
    @TableField(typeHandler = EncryptionTypeHandler.class)
    private String name;

    /**
     * QQ号
     */
    @NotBlank(message = "QQ号不能为空",groups = UpdateGroup.class)
    @Size(message = "QQ长度不能超过13",max = 13)
    @TableField(typeHandler = EncryptionTypeHandler.class)
    private String qq;
}

这样即可在存取数据的时候进行加密了。但查询的时候mybatis不会自动走这个加解密的handler,我们需要自己在xml上写好

4、查询时也使用自定义的handler

在你需要解密的字段后添加typeHandler的内容,注意这里的handler包路径要改成你自己的路径。
示例配置如下:

<resultMap id="BaseResultMap" type="com.namego.model.stu.StuUserEntity">
		<result property="schoolNumber" column="school_number" typeHandler="com.namego.handler.EncryptionTypeHandler" />
		<result property="name" column="name" typeHandler="com.namego.handler.EncryptionTypeHandler" />
		<result property="qq" column="qq" typeHandler="com.namego.handler.EncryptionTypeHandler" />
	</resultMap>

三、使用SpringJpa

1、引入库

就导入jpa操作的依赖,

  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>

2、编写Converter实现AttributeConverter方法

在jpa中使用的是Converter,跟上面Mybatis里的Handler道理一样。
示例代码如下:

@Converter
public class CryptoConverter implements AttributeConverter<String, String> { //这里是字符串转字符串,如果你是Integer转字符串就是<Integer,String>
    private static CryptoHandler deSedeCryptHandler = new DESedeCryptoHandler();

	 /**
     * 存入数据库前进行的转化操作
     * @param attribute
     * @return
     */
    @Override
    public String convertToDatabaseColumn(String attribute) {
        String encryptedValue = null;
        try {
            encryptedValue = deSedeCryptHandler.encrypt(attribute);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedValue;
    }
	 /**
     * 取出数据库后进行的转化操作
     * @param dbData
     * @return
     */
    @Override
    public String convertToEntityAttribute(String dbData) {
        String decryptedValue = null;
        try {
            decryptedValue = deSedeCryptHandler.decrypt(dbData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedValue;
    }
}

3、在你需要加解密的字段上添加注解

在字段上添加 @TableField(typeHandler = EncryptionTypeHandler.class)注解
示例代码如下:

/**
 * @author Namego
 * @date 
 */
@Data
@Accessors(chain = true)
@Entity
public class StuUserEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    @Id
    private Long userId;
    
    /**
     * 学号
     */
    @NotBlank(message = "学号不能为空",groups = UpdateGroup.class)
    @Convert(converter = CryptoConverter.class)
    private String schoolNumber;

    /**
     * 姓名
     */
    @NotBlank(message = "姓名不能为空",groups = UpdateGroup.class)
    @Size(message = "姓名长度不得小于2",min = 2)
    @Convert(converter = CryptoConverter.class)
    private String name;

    /**
     * QQ号
     */
    @NotBlank(message = "QQ号不能为空",groups = UpdateGroup.class)
    @Size(message = "QQ长度不能超过13",max = 13)
    @Convert(converter = CryptoConverter.class)
    private String qq;
}

jpa的话在添加数据和存储数据都会自动走这个Converter,就不用跟Mybatis的xml里添加了。


总结

在我自己实际开发中,我使用mybatis-plus自带的selectById方法他是没法走Handler的,在测试了很多方法都不行,最后只能自己写查询方法,如果哪位大佬知道如何解决还希望提一提。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatisJPA都是持久层框架,用于简化数据库操作。它们各自有优势和适用场景。 MyBatis是一种基于SQL的持久层框架,它提供了强大的灵活性和可定制性。MyBatis允许开发人员直接编写SQL语句,并且可以更好地控制SQL的执行过程。此外,MyBatis还提供了丰富的功能和扩展插件,使得开发过程更加便捷。 JPA是一种Java持久化API,它是JavaEE规范的一部分。JPA提供了一种对象关系映射的方式,通过注解或XML配置来实现对象与数据库表之间的映射关系。相比于MyBatisJPA更加面向对象,提供了更高层次的抽象。JPA还提供了一些方便的特性,例如自动生成数据库表结构、查询语言JPQL等。 选择使用哪个框架要根据具体的业务需求和团队技术栈来决定。如果你对SQL的控制和灵活性要求较高,或者已经有一套成熟的SQL语句库,那么MyBatis可能更适合你。如果你更注重对象与数据库之间的映射以及JavaEE规范的一致性,或者希望使用更高级的特性,那么JPA可能更适合你。 当然,这两个框架的选择并不是互斥的,有候也可以结合使用。比如,可以在项目中使用JPA进行简单的CRUD操作,而对于复杂的SQL查询,可以使用MyBatis来实现。这样既能享受到JPA的便捷性,又能发挥出MyBatis的灵活性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [jpamybatis](https://blog.csdn.net/weixin_57393819/article/details/125515358)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [浅谈mybatisjpa的区别](https://blog.csdn.net/zhangzhanbin/article/details/115562266)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值