场景

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

示例

数据库表

实体类

@Datapublic 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"    }]

实现

依赖

<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>    <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 对应 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@NoArgsConstructorpublic 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 对应 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 来实现。这样的话,往后扩展其他的类型处理器就不用再重写处理过程了,直接重写提供类型的方法即可。