亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

MyBatis 類型處理器

1. 前言

MyBatis 提供了諸多類型處理器,但是相較于豐富的數據庫類型仍然略顯不足,比如 MyBatis 只能將 JSON 數據類型當成普通的字符串處理。因此 MyBatis 提供了類型處理器接口,讓開發者可以根據具體的業務需求來自定義適合的類型處理器。

本小節,我們將以 JSON 類型處理器作為落腳點,來介紹類型處理器,并自定義 JSON 類型處理器。

2. 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 語句添加了兩條記錄。

3. 類型處理器

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);
  }
}

4. 注冊類型處理器

自定義類型處理器無法直接被 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 類型又轉化為了數據庫可識別的字符串類型。

5. 小結

  • 自定義類型處理器并不難,MyBatis 已經幫我們做好了大多數工作,我們只需在適當的位置適當的配置就可以了。
  • 數據庫 JSON 類型的用處會越來越廣泛,在 MyBatis 官方未內置處理器之前,我們也可以通過本小節的方式來提早的使用它。