Mybatis Plus 自定义类型处理器实现自定义字段映射

场景

假如说我有一个数据库表字段的数据类型为json。java对应实体类的属性类型为List集合类型。
问:我应该怎么把数据查出来映射给实体类属性?又应该怎么把实体类数据映射后存入数据库?

示例

数据库表

在这里插入图片描述

实体类

@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //部门
    private List<DepartmentInfo> department;
}

json数据格式

[
    {
        "departmentId": "string", 
        "departmentName": "string"
    }
]

实现

依赖

<!--因为存的json数据,所以要用到fastjson转换-->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.10.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.10.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.10.0</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.68</version>
</dependency>
<dependency>
<!--mybatis plus-->
<!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1</version>
    </dependency>

定义集合类型的处理类

@MappedJdbcTypes(JdbcType.VARBINARY)
@MappedTypes({List.class})
//继承BaseTypeHandler类,这个类是mybatis plus提供的基础类型处理类
public abstract class ListTypeHandler<T> extends BaseTypeHandler<List<T>> {

    /**
	* 保存数据前,会调用这个方法
	* 
	* ps 就是我们jdbc的 ps
	* i 对应参数保存sql中的位置,比如说我们以前用jdbc的时候,会写setString(i,value) ? -> value
	* List<T> 对应 BaseTypeHandler 上的泛型,parameter 对应实体类属性名 
	  其实这个参数就是获取到我们的实体类配了该处理器的属性的值,这里 parameter 为 department
	* jdbcType 如其名
	*/
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
        //如果参数有值,则用json转换器转换成json字符串,再把值set给ps
        //相当于在ps执行sql前,将值进行转换处理再放回sql里面去
        String content = parameter == null ? null : JSON.toJSONString(parameter);
        ps.setString(i, content);
    }

    /**
	* 查询数据后, 会调用这个方法
	*
	* rs 返回结果集,你懂jdbc的话应该不会陌生
	* columnName 加了转换器的属性对应的列名
	* rs.getString(columnName) 获取结果集中该列的值
	*/
    @Override
    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //用JSON类的方法把json字符串转换成集合对象
        return this.getListByJsonArrayString(rs.getString(columnName));
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnIndex));
    }

    @Override
    public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(cs.getString(columnIndex));
    }

	//自定义的一个方法,用于处理查询结果中加了转换器的值
    private List<T> getListByJsonArrayString(String content) {
        return content == null ? null : JSON.parseObject(content, this.specificType());
    }

    /**
     * 具体类型,由子类提供
     * @return 具体类型
     */
    protected abstract TypeReference<List<T>> specificType();
}

定义提供具体类型的处理类

//具体类型为
public class DepartmentListTypeHandler extends ListTypeHandler<DepartmentInfo> {
    @Override
    protected TypeReference<List<DepartmentInfo>> specificType() {
        return new TypeReference<List<DepartmentInfo>>() {
        };
    }
}

具体类型

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DepartmentInfo {
    @JsonSerialize(using = ToStringSerializer.class)
    private Long departmentId;//部门id
    private String departmentName;//部门名称

    public DepartmentInfo(DeptPO deptPO) {
        this.departmentId = deptPO.getDeptId();
        this.departmentName = deptPO.getDeptName();
    }
}

使用

不用xml的查询与插入
	@Data
    public class User {
        @TableId(type = IdType.AUTO)
        private Long id;
        private String name;
        private Integer age;
        private String email;
        
        //添加了自定义的类型处理器
        @TableField(value = "department", typeHandler = DepartmentListTypeHandler.class)
        private List<DepartmentInfo> department;
    }

查询与插入的时候,因为有这个注解,所以会在查询和插入的sql执行前后,调用类型处理器对数据先做处理,再执行sql。

使用xml的查询与插入

xml文件

<resultMap id="xxx" type="xxx.xxx.xxx.xxx">
  <result column="department" jdbcType="VARCHAR" property="department" typeHandler="xxx.xxx.xxx.DepartmentListTypeHandler"/>
</resultMap>
<select id="xxx" resultType="xxx" resultMap="xxx">
  ...

在resultMap标签里面指定一下即可

小结

类比JDBC查询

JDBC代码

           ArrayList<User> us=new ArrayList<User>();//存储从数据库中取出来的数据
		   Connection conn=BaseConnection.getConnection();//获取数据库连接
		   //sql执行器对象
		   PreparedStatement ps=null;
		   //结果集对象
		   ResultSet rs=null;//查询出来的数据先放到rs中
		   try{
			     String sql="select * from user";
			     ps=conn.prepareStatement(sql);
			     rs=ps.executeQuery();//执行数据库查询的方法,放到rs中
			     while(rs.next()){//rs对象相当于一个指针,指向数据库的一横行数据
                     User user =new User();//封装数据
                     user.setId(rs.getLong("id"));//rs指针指向id一行获取id一行数据,存储到ne中
                     user.setTitle(rs.getString("name"));//rs指针指向title一行获取id一行数据,存储到ne中
                     //... 逐个字段封装数据

                     //轮到部门字段的时候做一定的处理
                     String departmentStr = rs.getString("department");
                     List<DepartmentInfo> department = JSON.parseObject(department, new TypeReference<List<DepartmentInfo>>());
                     
                     us.add(user);    	 
			     }
               //...

ListTypeHandler代码

 /**
	* 查询数据后, 会调用这个方法
	*
	* rs 返回结果集,你懂jdbc的话应该不会陌生
	* columnName 加了转换器的属性对应的列名
	* rs.getString(columnName) 获取结果集中该列的值
	*/
    @Override
    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //用JSON类的方法把json字符串转换成集合对象
        //rs.getString("department");
        return this.getListByJsonArrayString(rs.getString(columnName));
    }

//...
//自定义的一个方法,用于处理查询结果中加了转换器的值
    private List<T> getListByJsonArrayString(String content) {
        return content == null ? null : JSON.parseObject(content, this.specificType());
    }

类比JDBC插入

JDBC代码

         Connection conn=BaseConnection .getConnection();
		 PreparedStatement ps=null;
		 String sql="insert into user(id,name,age,email,department) values(?,?,?,?,?)"; //占位符
            //...
		 ps= conn.prepareStatement(sql);//把写好的sql语句传递到数据库,让数据库知道我们要干什么
         ps.setLong(1,id);
		 ps.setString(2,name);
		 ps.setInt(3,age);
		 ps.setString(4,email);

         //处理部门字段
    	 String departmentStr = JSON.toJSONString(department);
         ps.setString(5,departmentStr);
		 int a=ps.executeUpdate();//这个方法用于改变数据库数据,a代表改变数据库的条数
        	//...

ListTypeHandler代码

    /**
	* 保存数据前,会调用这个方法
	* 
	* ps 就是我们jdbc的 ps
	* i 对应参数保存sql中的位置,比如说我们以前用jdbc的时候,会写setString(i,value) ? -> value
	* List<T> 对应 BaseTypeHandler 上的泛型,parameter 对应实体类属性名 
	  其实这个参数就是获取到我们的实体类配了该处理器的属性的值,这里 parameter 为 department
	* jdbcType 如其名
	*/
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
        //如果参数有值,则用json转换器转换成json字符串,再把值set给ps
        //相当于在ps执行sql前,将值进行转换处理再放回sql里面去
        String content = parameter == null ? null : JSON.toJSONString(parameter);
        ps.setString(i, content);
    }

从上面不难看出,类型处理器的处理逻辑相当于是在执行sql之前做了一层拦截,把加了处理器的属性进行相应的处理后再放入sql或者转成具体类型封装给实体类。
不妨猜测mp在执行sql前,对实体类每个字段进行判断,如果有 TableField注解,且配置了相应的处理器,则处理后再返回结果。

DepartmentListTypeHandler

提供具体类型的处理类。
其实这里用了设计模式里面的模板方法。具体对字段属性要做什么处理,交给抽象类 ListTypeHandler 即可,具体要处理什么类型,由其子类 DepartmentListTypeHandler 来实现。这样的话,往后扩展其他的类型处理器就不用再重写处理过程了,直接重写提供类型的方法即可。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值