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的原理。

Vue2,ElementUI,SpringBoot,MybatisMySQL8.0是一组优秀的前后端技术组合。Vue2是一款流行的前端框架,具有响应式的数据绑定和组件化开发等特点,适合开发动态交互性强的单页面应用;ElementUI是一款基于Vue2的UI库,提供了一系列美观实用的组件,可快速搭建现代化的Web应用;SpringBoot是一款基于Spring框架的轻量级应用开发框架,使用简单,能够快速集成其他框架;Mybatis是一个优秀的ORM框架,能够极大地提高Java开发与SQL交互的效率,避免手写SQL语句的麻烦;MySQL8.0则是一个高性能、稳定性强的关系型数据库,使用广泛。 结合这些技术进行登陆注册系统的开发,可以使用Vue2和ElementUI实现前端页面的效果,使用SpringBoot作为后端框架,利用MybatisMySQL8.0数据库进行访问。具体开发过程可以分为以下几步: 1. 后端开发:使用SpringBoot框架搭建RESTful风格API接口,使用Mybatis框架访问MySQL8.0数据库,并实现用户登陆、注册以及对用户信息的增删改查等功能。 2. 前端开发:使用Vue2和ElementUI完成前端页面的搭建,包括登陆、注册、用户信息管理等页面,并使用Axios等技术与后端进行数据传输。可以使用Vuex实现数据的状态管理和共享。 3. 接口实现:在前后端开发完成后,需要将后端的API接口与前端进行对接,实现数据的交互。可以使用Postman等工具测试和调试接口。 4. 系统上线:在完成开发后,需要对系统进行测试和调试,确保系统能够稳定运行并满足用户需求后,再进行部署上线。 综上,登陆注册Vue2 ElementUI SpringBoot Mybatis MySQL8.0的开发过程相对复杂,需要前后端开发人员精细的协作和技术储备,但使用这些优秀的技术组合可以有效提高开发效率和用户体验,是一种切实可行的开发方式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值