美文网首页
MyBatis 类型处理器

MyBatis 类型处理器

作者: zhengaoly | 来源:发表于2022-06-09 13:41 被阅读0次

    类型处理器的注册
    1.全局注册
    <typeHandlers>
    <package name="com.imooc.mybatis.handler"/>
    </typeHandlers>
    或者
    <typeHandlers>
    <typeHandler handler="com.imooc.mybatis.handler.JsonArrayTypeHandler"/>
    </typeHandlers>
    全局注册的类型处理器会自动被 MyBatis 用来处理所有符合类型的参数。如 JsonArrayTypeHandler 通过 MappedJdbcTypes 注解表明了自己将会处理 JdbcType.VARCHAR 类型,MyBatis 会自动将字符串类型的参数交给 JsonArrayTypeHandler 来进行处理。
    2.局部注册
    由于全局注册会对其它类型产生歧义和污染,因此我们选择更加精准的局部注册。在 BlogMapper 中,我们来注册和使用类型处理器。
    但是,这样显然有问题,因为 JsonObjectTypeHandler 注册的类型也是 JdbcType.VARCHAR 类型,所以全局注册是不推荐的,除非你需要对所有参数都做类型转换
    <resultMap id="blogMap" type="com.imooc.mybatis.model.Blog">
    <result column="id" property="id"/>
    <result column="info" property="info" typeHandler="com.imooc.mybatis.handler.JsonObjectTypeHandler"/>
    <result column="tags" property="tags" typeHandler="com.imooc.mybatis.handler.JsonArrayTypeHandler"/>
    </resultMap>
    <select id="selectById" resultMap="blogMap">
    SELECT * FROM blog WHERE id = #{id}
    </select>
    对于注解方式,可采用@results(@result(typehandler=....))注解的方式,进行映射

    MyBatis 类型处理器

    1. 前言
      MyBatis 提供了诸多类型处理器,但是相较于丰富的数据库类型仍然略显不足,比如 MyBatis 只能将 JSON 数据类型当成普通的字符串处理。因此 MyBatis 提供了类型处理器接口,让开发者可以根据具体的业务需求来自定义适合的类型处理器。

    本小节,我们将以 JSON 类型处理器作为落脚点,来介绍类型处理器,并自定义 JSON 类型处理器。

    1. JSON 数据类型
      首先,我们需要为 MyBatis 内置类型处理器增加一个它无法处理的数据类型,这里我们选择 MySQL5.7 中新增的 JSON 数据类型,这也是大家普遍使用的一个数据类型。在可用的数据库环境中,我们运行如下脚本:
    DROP TABLE IF EXISTS blog;
    CREATE TABLE blog
    (
      id   int(11) unsigned primary key auto_increment,
      info json,
      tags json
    );
    INSERT INTO blog(info, tags)
    VALUES ('{"title": "世界更大", "content": "世界更大的内容", "rank": 1}', '["世界观"]'),
           ('{"title": "人生更短", "content": "人生更短的内容", "rank": 2}', '["人文"]');
    

    在这个脚本中,我们新建了一个 blog 数据表,blog 数据表除 id 外有 info 和 tags 两个字段,这两个字段都是 JSON 类型,并通过 insert 语句添加了两条记录。

    1. 类型处理器
      MyBatis 默认是无法很好处理 info 和 tags 这两个字段的,只能将它们当成字符串类型来处理,但显然这不是我们想要的效果。我们希望新增 json 类型处理器来处理好这两个字段。

    MyBatis 提供了 TypeHandler 接口,自定义类型处理器需要实现了这个接口才能工作。考虑到很多开发者不够熟练,MyBatis 还提供了一个 BaseTypeHandler 抽象类来帮助我们做自定义类型处理器,只需继承这个基类,然后实现它的方法即可。

    3.1 JsonObject 处理器
    JSON 可分为 object 和 array 两大类,分别对应 info 和 tags 字段,这两类需要分别实现类型处理器。由于需要对 JSON 进行处理,我们在 pom.xml 文件中添加上对应的依赖。

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.60</version>
    </dependency>
    

    这里,我们使用阿里巴巴开源的 fastjson库。

    在 com.imooc.mybatis 包下新建 handler 包,并向 handler 包中添加上 json object 的类型处理器 JsonObjectTypeHandler。如下:

    package com.imooc.mybatis.handler;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import org.apache.ibatis.type.*;
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    @MappedJdbcTypes(JdbcType.VARCHAR) // 对应jdbc 类型
    @MappedTypes({JSONObject.class}) // 对应处理后类型
    public class JsonObjectTypeHandler extends BaseTypeHandler<JSONObject> {
      // 当为 PreparedStatement 参数时,如何处理对象
      @Override
      public void setNonNullParameter(PreparedStatement preparedStatement, int i, JSONObject o, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, JSON.toJSONString(o));
      }
    
      // 当通过名称从结果中取json字段时如何处理
      @Override
      public JSONObject getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String t = resultSet.getString(s);
        return JSON.parseObject(t);
      }
    
      // 当通过序列号从结果中取json字段时如何处理
      @Override
      public JSONObject getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String t = resultSet.getString(i);
        return JSON.parseObject(t);
      }
    
      // 当通过序列号从 CallableStatement 中取json字段时如何处理
      @Override
      public JSONObject getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String t = callableStatement.getString(i);
        return JSON.parseObject(t);
      }
    }
    

    有了 BaseTypeHandler 作为基础后,实现一个类型处理器就比较简单了,我们只需要为其中 4 个方法添加上对应的实现即可。

    类型处理器有两个作用,第一处理 Java 对象到 JdbcType 类型的转换,对应 setNonNullParameter 方法;第二处理 JdbcType 类型到 Java 类型的转换,对应 getNullableResult 方法,getNullableResult 有 3 个重载方法。下面我们依次来说明这四个方法的作用:

    setNonNullParameter:处理 PreparedStatement 中的 JSONObject 参数,当调用 PreparedStatement 执行 SQL 语句时,调用该处理 JSONObject 类型的参数,这里我们通过 fastjson 的JSON.toJSONString(o)函数将 JSONObject 转化为字符串类型即可。
    getNullableResult:从结果集中获取字段,这里 CallableStatement 和 ResultSet 分别对应不同的执行方式,对于 JDBC 而言 JSON 类型也会当做字符串来处理,因此这里我们需要将字符串类型转化为 JSONObject 类型,对应 JSON.parseObject(t)代码。
    3.2 JsonArray 处理器
    与 JsonObjectTypeHandler 一样,在 handler 包下新建 JsonArrayTypeHandler 类,继承 BaseTypeHandler 类,并将具体方法的实现从 JSON.parseObject 改变为 JSON.parseArray,如下:

    @MappedJdbcTypes(JdbcType.VARCHAR)
    @MappedTypes({JSONArray.class})
    public class JsonArrayTypeHandler extends BaseTypeHandler<JSONArray> {
      @Override
      public void setNonNullParameter(PreparedStatement preparedStatement, int i, JSONArray o, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, JSON.toJSONString(o));
      }
    
      @Override
      public JSONArray getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String t = resultSet.getString(s);
        // // 变成了 parseArray
        return JSON.parseArray(t);
      }
    
      @Override
      public JSONArray getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String t = resultSet.getString(i);
        // // 变成了 parseArray
        return JSON.parseArray(t);
      }
    
      @Override
      public JSONArray getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String t = callableStatement.getString(i);
        // 变成了 parseArray
        return JSON.parseArray(t);
      }
    }
    
    1. 注册类型处理器
      自定义类型处理器无法直接被 MyBatis 加载,我们需要增加相关的配置告诉 MyBatis 加载类型处理器。

    4.1 全局注册
    在全局配置配置文件中可通过 typeHandlers 属性来注册类型处理器。如下:

    <typeHandlers>
      <package name="com.imooc.mybatis.handler"/>
    </typeHandlers>
    

    通过 package 项来指定类型处理器所在的包路径,这样 handler 包中的所有类型处理器都会注册到全局。

    当然如果你的类型处理器分散在其它地方,也可以通过如下方式来注册。

    <typeHandlers>
      <typeHandler handler="com.imooc.mybatis.handler.JsonArrayTypeHandler"/>
    </typeHandlers>
    

    全局注册的类型处理器会自动被 MyBatis 用来处理所有符合类型的参数。如 JsonArrayTypeHandler 通过 MappedJdbcTypes 注解表明了自己将会处理 JdbcType.VARCHAR 类型,MyBatis 会自动将字符串类型的参数交给 JsonArrayTypeHandler 来进行处理。

    但是,这样显然有问题,因为 JsonObjectTypeHandler 注册的类型也是 JdbcType.VARCHAR 类型,所以全局注册是不推荐的,除非你需要对所有参数都做类型转换。

    4.2 局部注册
    由于全局注册会对其它类型产生歧义和污染,因此我们选择更加精准的局部注册。在 BlogMapper 中,我们来注册和使用类型处理器。

    在 BlogMapper.xml 文件中,我们添加上如下配置。

    <resultMap id="blogMap" type="com.imooc.mybatis.model.Blog">
      <result column="id" property="id"/>
      <result column="info" property="info" typeHandler="com.imooc.mybatis.handler.JsonObjectTypeHandler"/>
      <result column="tags" property="tags" typeHandler="com.imooc.mybatis.handler.JsonArrayTypeHandler"/>
    </resultMap>
    
    <select id="selectById" resultMap="blogMap">
      SELECT * FROM blog WHERE id = #{id}
    </select>
    

    我们定义了 名为 blogMap 的 resultMap 和名为 selectById 的查询。在 result 映射中,我们注册了相关的类型处理器,info 字段对应

    JsonObjectTypeHandler 类型处理器,tags 字段对应 JsonArrayTypeHandler 类型处理器。

    这样自定义的类型处理器不会污染到其它数据,blogMap 的类型 com.imooc.mybatis.model.Blog 定义如下:

    package com.imooc.mybatis.model;
    
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    
    public class Blog {
      private Long id;
      private JSONObject info;
      private JSONArray tags;
      // 省略了 getter 和 setter 方法
    }
    

    4.3 处理 JDBC 类型
    在对应的 BlogMapper.java 接口上添加上对应的 selectById 方法:

    package com.imooc.mybatis.mapper;
    
    import com.imooc.mybatis.model.Blog;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface BlogMapper {
      Blog selectById(Integer id);
    }
    我们测试一下 selectById 方法:
    
    BlogMapper blogMapper = session.getMapper(BlogMapper.class);
    Blog blog = blogMapper.selectById(1);
    System.out.println(blog.toString());
    String title = blog.getInfo().getString("title");
    System.out.println(title);
    String tag = blog.getTags().getString(0);
    System.out.println(tag);
    输出结果如下:
    
    Blog{id=1, info={"rank":1,"title":"世界更大","content":".......****............"}, tags=["世界观"]}
    世界更大
    世界观
    

    从结果中可以看出,类型处理器成功的处理了查询的数据,info 和 tags 字段都能够通过 fastjson 的 API 来获取里面的内容。

    4.4 处理 JSON 类型
    在查询可以工作的情况下,那么如何通过 insert 插入 JSON 对象了。

    我们在 BlogMapper 中新增一个 insertBlog 方法,如下:

    <insert id="insertBlog">
      INSERT INTO blog(info,tags)
      VALUES(#{info,typeHandler=com.imooc.mybatis.handler.JsonObjectTypeHandler},
      #{tags,typeHandler=com.imooc.mybatis.handler.JsonArrayTypeHandler})
    </insert>
    public interface BlogMapper {
      int insertBlog(@Param("info") JSONObject info, @Param("tags") JSONArray tags);
    }
    

    这样 MyBatis 就可以处理 JSON 类型的参数了,我们再次测试一下:

    JSONObject info = new JSONObject().fluentPut("title", "测试案例").fluentPut("rank", 1);
    JSONArray tags = new JSONArray().fluentAdd("测试");
    int rows = blogMapper.insertBlog(info, tags);
    System.out.println(rows);
    输出结果:1
    

    可以看到类型处理器成为了 Java JSON 类型和 JDBC 类型转换桥梁,在查询的时候主动将数据库类型转化为了可用的 JSON 类型,而在插入的时候将 JSON 类型又转化为了数据库可识别的字符串类型。

    1. 小结
      自定义类型处理器并不难,MyBatis 已经帮我们做好了大多数工作,我们只需在适当的位置适当的配置就可以了。
      数据库 JSON 类型的用处会越来越广泛,在 MyBatis 官方未内置处理器之前,我们也可以通过本小节的方式来提早的使用它。

    相关文章

      网友评论

          本文标题:MyBatis 类型处理器

          本文链接:https://www.haomeiwen.com/subject/ofrmmrtx.html