目录

  • 前言
  • 运行环境
  • 创建表
  • 导入依赖
  • 创建实体
  • 建立TypeHandler
  • @MappedJdbcTypes和@MappedTypes干什么用的
  • BaseTypeHandler要实现的方法是干什么的
  • 写好的TypeHandler怎么使用
  • 创建dao
  • 控制层


前言

mybatis作为一款相当优秀的一款orm框架,不仅本身就提供了很多类型映射,还支持自定义类型映射(javaType与jdbcType之间的关系),通过TypeHandler我们可以自己在数据库类型与实体类型之间任意转换。
比如说,我现在需要讲前端传来的手机号进行脱敏入库处理,而用户查的时候,看到的得是正常的!这就涉及加密解密处理了,当然我们可以在service里处理,这里我们演示下如何通过自定义类型映射,进行处理。

运行环境

java8, springboot2.5.0,mysql8,maven3.3.9

创建表

CREATE TABLE `t_user` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `phone` varchar(255) DEFAULT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

导入依赖

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

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.4</version>
		</dependency>

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.6.5</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

创建实体

import java.util.List;

public class User {
    private Integer id;

    private String name;

    private MyEncrypt phone;

    private List<String> remark;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public MyEncrypt getPhone() {
        return phone;
    }

    public void setPhone(MyEncrypt phone) {
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getRemark() {
        return remark;
    }

    public void setRemark(List<String> remark) {
        this.remark = remark;
    }
}
public class MyEncrypt {

    private String realPhone;

    public MyEncrypt(String realPhone) {
        this.realPhone = realPhone;
    }

    public String getRealPhone() {
        return realPhone;
    }

    public void setRealPhone(String realPhone) {
        this.realPhone = realPhone;
    }
}

数据库手机号设置的varchar类型,而我定义的是一个MyEncrypt实体类,所以我需要将他与jdbcType关联起来,并且完成加密解密的过程!

建立TypeHandler

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.jcl.mybatistypehandler.model.MyEncrypt;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(MyEncrypt.class)
public class MyEncryptTypeHandler extends BaseTypeHandler<MyEncrypt> {

    private static final byte[] KEYS = "12345678abcdefgh".getBytes(StandardCharsets.UTF_8);

    /**
     * 设置参数,默认从前端传来是源手机号,加密处理后,又重新塞回
     * preparedStatement: 预执行句柄
     */
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, MyEncrypt myEncrypt, JdbcType jdbcType) throws SQLException {
        if (myEncrypt == null || myEncrypt.getRealPhone() == null) {
            preparedStatement.setString(i, null);
            return;
        }
        String phone = myEncrypt.getRealPhone();
        AES aes = SecureUtil.aes(KEYS);
        String encrypt = aes.encryptHex(phone);
        preparedStatement.setString(i, encrypt);
    }

    @Override
    public MyEncrypt getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return decrypt(resultSet.getString(s));
    }

    @Override
    public MyEncrypt getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return decrypt(resultSet.getString(i));
    }

    @Override
    public MyEncrypt getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return decrypt(callableStatement.getString(i));
    }

    /**
     * 解密
     *
     * @param value 手机号
     * @return 返回解密对象
     */
    public MyEncrypt decrypt(String value) {
        if (null == value) {
            return null;
        }
        return new MyEncrypt(SecureUtil.aes(KEYS).decryptStr(value));
    }
}

以上就是全文核心,看的就是这里!

@MappedJdbcTypes和@MappedTypes干什么用的

我认为表面上是对jdbcType和javaType做了个绑定,更像是做了个筛选。比如我上面

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(MyEncrypt.class)

就是将MyEncryptvarchar建立了联系,你的参数里有MyEncrypt这个类型且是要转成varchar的,他才会起作用!

BaseTypeHandler要实现的方法是干什么的

他要实现一个set方法和三个get方法。
setNonNullParameter是塞值,我理解成把javaType转成jdbcType的过程(通俗点是把让你把传的对象转成你能放进数据库类型)
getNullableResult被重载了两次,但是在我眼里他就是和上面反过来,是把jdbcType转成javaType的过程,也就是把数据库里的数据转成你约定的对象。

写好的TypeHandler怎么使用

个人建议全局配在配置文件里,写在xml里,不仅麻烦,还报各种错误,麻烦死了。而且一般写这个,大概率是为了全局使用的!

# 包名
mybatis.type-handlers-package=com.jcl.mybatistypehandler.handler

写在xml里的话,那可就麻烦了

高度建议全局配

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jcl.mybatistypehandler.dao.UserDao">

    <resultMap id="BaseResultMapper" type="com.jcl.mybatistypehandler.model.User">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="phone" property="phone" typeHandler="com.jcl.mybatistypehandler.handler.MyEncryptTypeHandler"/>
        <result column="remark" property="remark" typeHandler="com.jcl.mybatistypehandler.handler.ListTypeHandler"/>
    </resultMap>

    <insert id="add">
        insert into t_user(name, phone, remark)
        values (#{name}, #{phone,typeHandler=com.jcl.mybatistypehandler.handler.MyEncryptTypeHandler},
                #{remark,typeHandler=com.jcl.mybatistypehandler.handler.ListTypeHandler})
    </insert>

    <select id="findByPhone" resultMap="BaseResultMapper">
        select *
        from t_user
        where phone = #{phone,typeHandler=com.jcl.mybatistypehandler.handler.MyEncryptTypeHandler}
    </select>
</mapper>

创建dao

import com.jcl.mybatistypehandler.model.MyEncrypt;
import com.jcl.mybatistypehandler.model.User;
import org.apache.ibatis.annotations.Param;

public interface UserDao {

    int add(@Param("name") String name,@Param("phone") MyEncrypt phone, @Param("remark") String remark);

    User findByPhone(@Param("phone") MyEncrypt phone);
}

xml上面已给!

ListTypeHandler是我设定的一个字符串转List的一个转换器!
可以不写,所以xml里面也就别配了!

package com.jcl.mybatistypehandler.handler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.util.StringUtils;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class ListTypeHandler extends BaseTypeHandler<List<String>> {
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        if (null == parameter || parameter.isEmpty()) {
            ps.setString(i, null);
            return;
        }
        ps.setString(i, String.join(",", parameter));
    }
    
    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        final String value = rs.getString(columnName);
        if (StringUtils.hasText(value)) {
            return Arrays.asList(value.split(","));
        }
        return null;
    }
    
    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        final String value = rs.getString(columnIndex);
        if (StringUtils.hasText(value)) {
            return Arrays.asList(value.split(","));
        }
        return null;
    }
    
    @Override
    public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        final String value = cs.getString(columnIndex);
        if (StringUtils.hasText(value)) {
            return Arrays.asList(value.split(","));
        }
        return null;
    }
}

控制层

import com.jcl.mybatistypehandler.dao.UserDao;
import com.jcl.mybatistypehandler.model.MyEncrypt;
import com.jcl.mybatistypehandler.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserDao userDao;

    @GetMapping("add")
    public String addUser(@RequestParam("name") String name,
                          @RequestParam("phone") String phone,
                          @RequestParam("remark") String remark) {
        MyEncrypt myEncrypt = new MyEncrypt(phone);
        int result = userDao.add(name, myEncrypt, remark);
        return "添加结果: " + result;
    }

    @GetMapping("findByPhone")
    public User findCustomer(@RequestParam("phone") String phone) {
        return userDao.findByPhone(new MyEncrypt(phone));
    }
}