IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> MyBatis+MySQL8.0存取Json字段之TypeHandler -> 正文阅读

[大数据]MyBatis+MySQL8.0存取Json字段之TypeHandler

一丶背景

在业务开发过程中,为了实现一个在线编辑器功能,存取了一些CSS样式在MySQL里面,就像这样:

"css": {
                "id": "3",
                "width": 11,
                "height": 12,
                "left": 13,
                "top": 14,
                "createtTime": "2021-10-14 10:40:11",
                "updateTime": "2021-10-14 10:40:11",
                "creatorId": "1111"
            },

之前的MySQL存JSON字段都是用BLOB类型,在MySQL 5.7后添加了JSON类型作为对JSON字段的存储。但是MyBatis并未支持,因此有了这篇文章要解决的问题。

二丶解决方案

1.自定义转换

也就是查询的时候,将用fastjson这类工具包,JSON串转为对象;在存储时,手动将对象转为json串,然后存入类型为VARCHAR的字段里。就像这样:

@Data
public class Element {
    private String id;
    private String css;
    private Objetc cssObj;
    private Datetime createTime;
}

@Data
public class Css{
    private String id;
    private int width;
    private int height;
    private int left;
    private int top;
    private Date createTime;
    private Date updateTime;
    private String creatorId;
}

自定义转换ServiceImpl:

@Override
public Element getById(String id) {
    Element element = elementDao.selectByPrimaryKey(id);
    String css= element .getCss();
    Css cccObj = JsonUtils.fromJson(css, Css.class);
    element .setCssObj(cccObj );
    return element ;
}

@Override
public boolean save(Element element) {
    Css cssObj = element.getCssObj();
    element.setCss(JsonUtil.toJson(cssObj ));
    return elementDao.insert(element) > 0;
}

这样的解决方案存在两个问题:
1.需要在model类上面增加冗余字段:cssObj。
2.每一个业务逻辑里面都需要增加上述转换方法,代码冗余了些。
3.不够优雅
这时候,阅读框架源码的作用体现出来了,Mybatis本身作为一个ORM框架,自己是实现了类型转换的,可不可以参考Mybatis的实现,来自己设计一个转换器呢?Mybatis预定义的基础类型转换是通过实现TypeHandler接口或者继承抽象类BaseTypeHandler来实现,其默认的转换类型如图:在这里插入图片描述
本文采用的方式是继承BaseTypeHandler的方式,来实现对JSON数据类型的转换。Mybatis的BaseTypeHandler具体代码如图:
BaseTypeHandler:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {  
   
  protected Configuration configuration;  
   
  public void setConfiguration(Configuration c) {  
    this.configuration = c;  
  }  
   
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {  
    if (parameter == null) {  
      if (jdbcType == null) {  
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");  
      }  
      try {  
        ps.setNull(i, jdbcType.TYPE_CODE);  
      } catch (SQLException e) {  
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +  
             "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +  
             "Cause: " + e, e);  
      }  
    } else {  
      setNonNullParameter(ps, i, parameter, jdbcType);  
    }  
  }  
   
  public T getResult(ResultSet rs, String columnName) throws SQLException {  
    T result = getNullableResult(rs, columnName);  
    if (rs.wasNull()) {  
      return null;  
    } else {  
      return result;  
    }  
  }  
   
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {  
    T result = getNullableResult(rs, columnIndex);  
    if (rs.wasNull()) {  
      return null;  
    } else {  
      return result;  
    }  
  }  
   
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {  
    T result = getNullableResult(cs, columnIndex);  
    if (cs.wasNull()) {  
      return null;  
    } else {  
      return result;  
    }  
  }  
   
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;  
   
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;  
   
  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;  
   
  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;  
   
}  

2.继承BaseTypeHandler实现对JSON类型的转换

  1. 第一步,定义一个abstract class,继承于org.apache.ibatis.type.BaseTypeHandler,作为Object类型的转换基类,所有想varcharObject的互转,只需要继承此基类即可,无需重复写第一个方法那些自定义转换的步骤。
package com.eqxiu.chart.handler;

import com.eqxiu.chart.util.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.BaseTypeHandler;

import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * ClassName: AbsractObjectTypeHandler
 * Description: Model对象json转换抽象类 解决mybatis插入json数据报错问题
 * Author: lizhiyu
 * Date: 2021/10/14 11:05
 * Version: mvp
 **/
public abstract class AbstractObjectTypeHandler<T> extends BaseTypeHandler<T> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter,
                                    JdbcType jdbcType) throws SQLException {
        ps.setString(i, JsonUtils.toJson(parameter));
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        String data = rs.getString(columnName);
        return StringUtils.isBlank(data) ? null : JsonUtils.fromJson(data, (Class<T>) getRawType());
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String data = rs.getString(columnIndex);
        return StringUtils.isBlank(data) ? null : JsonUtils.fromJson(data, (Class<T>) getRawType());
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        String data = cs.getString(columnIndex);
        return StringUtils.isBlank(data) ? null : JsonUtils.fromJson(data, (Class<T>) getRawType());
    }
}

  1. 第二步,定义具体的实现类,继承步骤一中的AbstractObjectTypeHandler,要转什么类型的Java对象,转什么对象。
public class CssTypeHandler extends AbstractObjectTypeHandler<Css> {
}
  1. 第三步,修改原有Element类的数据结构,去除String类型,如图:
@Data
public class Element {
    private String id;
    private Object css;
    private Datetime createTime;
}
  1. 第四步,配置类型处理器包扫描路径,在application.properties里面配置:
#mybatis typeHandler扫描
mybatis.typeHandlersPackage=com.eqxiu.chart.handler.impl
  1. 第五步,修改对应XML文件,将对应属性使用自定义的转换器:
<resultMap id="BaseResultMap" type="com.eqxiu.chart.model.Element">
    <id column="id" jdbcType="VARCHAR" property="id" />
    <result column="css" jdbcType="OTHER" property="css" typeHandler="com.eqxiu.chart.handler.Impl.CssTypeHandler" />
    <result column="compType" jdbcType="VARCHAR" property="comptype" />
    <result column="contentIds" jdbcType="VARCHAR" property="contentIds" typeHandler="com.eqxiu.chart.handler.Impl.ListToVarcharTypeHandler" />
    <result column="chartData" jdbcType="VARCHAR" property="chartData" />
    <result column="createTime" jdbcType="TIMESTAMP" property="createTime" />
    <result column="updateTime" jdbcType="TIMESTAMP" property="updateTime" />
    <result column="creatorId" jdbcType="VARCHAR" property="creatorId" />
  </resultMap>
  1. 第六步,这里有个小坑,在MySQL8.0后,插入JSON对象还得用uff8mb8编码才行,不然会类型错误。所以在insert或者update时,需要加上CONVERT函数,作编码转换。
<!-- foreach批量插入 -->
  <insert id="insertBatch">
    INSERT element (id, css, compType, contentIds, chartData, createTime, updateTime, creatorId)
    VALUES
    <foreach collection ="elementList" item="element" separator =",">
      (#{element.id,jdbcType=VARCHAR}
      , CONVERT(#{element.css,jdbcType=OTHER,typeHandler=com.eqxiu.chart.handler.Impl.CssTypeHandler} using utf8mb4)
      , #{element.comptype,jdbcType=VARCHAR}
      , #{element.contentIds,jdbcType=VARCHAR,typeHandler=com.eqxiu.chart.handler.Impl.ListToVarcharTypeHandler}
      , #{element.chartData,jdbcType=VARCHAR},#{element.createTime,jdbcType=TIMESTAMP},#{element.updateTime,jdbcType=TIMESTAMP}
      , #{element.creatorId,jdbcType=VARCHAR})
    </foreach >
  </insert>

最后,业务代码就变得不再冗杂了,也便于扩展。

@Override
public Element getById(String id) {
    return elementDao.selectByPrimaryKey(id);
}

@Override
public boolean save(Element element) {
    return elementDao.insert(element) > 0;
}

三丶反思,继续扩张认知边界

这篇文章只是从业务实现层面去介绍了如何用TypeHandler的思路,优雅的解决Mybatis+Mysql实现对json数据类型字段的存取。但是对于,MyBatis中TypeHandler如何具体执行的以及设计思路未作探讨,下一篇文章会继续写Mybatis中TypeHandler的原理。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-10-19 11:57:30  更:2021-10-19 11:57:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:04:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码