前言
在上一篇 文章 中已經介紹了 枚舉 類型字段的使用,本文接著介紹 JSON 類型字段的使用。
關于 JSON 類型 ( 參考 )
Mysql 5.7 版本 起增加了對 JSON 類型的支持,表現形式類似于加了 JSON 格式校驗的 longtext 。有了這個類型我們就可以存儲一些非固定的數據結構來靈活應對多變的業務。
使用
拿訂單業務舉例,一個訂單允許購買多件商品,通常會定義兩張表,一張 訂單表 和一張 訂單商品表 ,然后進行關聯查詢。
-
如果用了 JSON 類型就只需一張表了:
DROP TABLE IF EXISTS `order`; CREATE TABLE `order` ( id int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', orderNo varchar(40) NOT NULL COMMENT '訂單號', status tinyint(1) NOT NULL COMMENT '訂單狀態', address json NOT NULL COMMENT '收貨地址', orderGoods json NOT NULL COMMENT '訂單商品', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
注意: 請結合實際情況設計表結構,此處是為了演示效果。
-
對應實體類 :
@Data public class Order implements Serializable { /** * 訂單號 */ private String orderNo; /** * 訂單狀態 */ private Status status; /** * 收貨地址 */ private Address address; /** * 訂單商品 */ private List<OrderGoods> orderGoods; /** * 序列化時顯示狀態描述 * * @return */ public String getStatusDesc() { return status == null ? null : status.desc(); } }
-
地址類:
@Data public class Address implements Serializable { /** * 手機 */ private String mobile; /** * 收貨人姓名 */ private String receiver; /** * 詳細地址 */ private String adr; }
DAO 層處理
由于使用 Mybatis 作為 ORM 框架,這里使用 Mybatis 提供的 TypeHandler 實現 枚舉類型 的 序列化 和 反序列化 。
-
實現一個自定義的通用的 TypeHandler
/** * JSON 類型-類型轉換器 * <p> * 由于 Mybatis 對泛型嵌套做了處理,T 為泛型類型時會被轉為 rawType,所以要注意以下兩點: * <p> * 1. 不要使用父類的 rawType 屬性,使用本類的 type 屬性來做類型轉換 * 2. 需要做類型轉換的字段要指定 TypeHandler,而不能由 Mybatis 自動查找 * * @author anyesu * @see org.apache.ibatis.type.TypeReference#getSuperclassTypeParameter */ @Slf4j public class JsonTypeHandler<T> extends BaseTypeHandler<T> { private final Type type; /** * 只能由子類調用 */ protected JsonTypeHandler() { type = GenericsUtils.getSuperClassGenericType(getClass()); } /** * 由 Mybatis 根據類型動態生成實例 * * @param type * @see org.apache.ibatis.type.TypeHandlerRegistry#getInstance(Class, Class) */ public JsonTypeHandler(Class<T> rawClass) { this.type = rawClass; } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, this.toJson(parameter)); } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { return this.toObject(rs.getString(columnName)); } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return this.toObject(rs.getString(columnIndex)); } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return this.toObject(cs.getString(columnIndex)); } /** * 序列化 * * @param object * @return */ private String toJson(T object) { return JSON.toJSONString(object); } /** * 反序列化 * * @param content * @return */ private T toObject(String content) { T object = null; if (content != null && content.length() > 0) { try { object = JSON.parseObject(content, getType()); } catch (Exception e) { log.error("", e); } } return object; } public Type getType() { return type; } }
-
注冊 JsonTypeHandler
@Configuration public class MybatisTypeHandlerConfiguration { /** * 注冊 Mybatis 類型轉換器 */ @Autowired public void registerTypeHandlers() { jsonTypes().forEach(this::registerJsonTypeHandler); } /** * 注冊 JSON 類型的類型轉換器 * * @param javaTypeClass Java 類型 */ private void registerJsonTypeHandler(Class<?> javaTypeClass) { register(javaTypeClass, JsonTypeHandler.class); } /** * 注冊類型轉換器 * * @param javaTypeClass Java 類型 * @param typeHandlerClass 類型轉換器類型 */ private void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) { this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } /** * 簡單 JSON 類型 * * @return */ private List<Class<?>> jsonTypes() { // TODO 這里為了方便就硬編碼記錄類型,自行替換掃描的方式 return Arrays.asList(Address.class); } }
上面的方式只是注冊了 Address 類的類型轉換器,對于 List<OrderGoods> 這種 泛型類型 則無法使用這種方式注冊,需要使用下面的方法:
-
實現 JsonTypeHandler 的子類
public class OrderGoodsListTypeHandler extends JsonTypeHandler<List<OrderGoods>> { }
-
然后修改 application.yml 讓 Mybatis 去掃描注冊自定義的 TypeHandler :
mybatis: type-handlers-package: com.github.anyesu.common.typehandler
-
然后在 Mapper 中顯式指定 typeHandler
<update id="updateByPrimaryKey"> update `order` set orderNo = #{orderNo}, status = #{status}, address = #{address}, orderGoods = #{orderGoods, typeHandler=com.github.anyesu.common.typehandler.OrderGoodsListTypeHandler} where id = #{id} </update>
注意: 如果 orderGoods 字段不指定 typeHandler 會被識別為 Object 類型并使用 ObjectTypeHandler 來轉換,因此會導致錯誤。
另外,如果使用了 set 標簽,orderGoods 能被為 List 類型,而 OrderGoodsListTypeHandler 被注冊為 List 類型的類型轉換器,所以能正常執行。( 最好還是顯式指定 typeHandler )
<update id="updateByPrimaryKeySelective"> update `order` <set> <if test="orderNo != null"> orderNo = #{orderNo}, </if> <if test="status != null"> status = #{status}, </if> <if test="address != null"> address = #{address}, </if> <!-- 包裹在 set 標簽內被正確識別為 List 類型 --> <!-- 最好還是顯式指定類型轉換器 --> <if test="orderGoods != null"> orderGoods = #{orderGoods} </if> </set> where id = #{id} </update>
源碼
篇幅有限,上面代碼并不完整,點擊 這里 查看完整代碼。