如何使用自定义Mybatis-TypeHandler直接将查询到的JSON数据封装到指定对象,达到可复用?

2年前 (2022) 程序员胖胖胖虎阿
248 0 0

使用自定义Mybatis-TypeHandler直接将查询到的JSON数据封装到指定对象,达到可复用!!

​ 前言:最近在做项目的时候,遇到一个问题,就是数据量特别大的情况下,除了分表分库,索引层面优化,以及加缓存以外,还可以对数据进行进一步处理,比如你要查明细数据,你可以将这些明细信息以Json的形式存储在数据库中,另外保留一些基础信息,这样就能把大量的数据缩减,提高查询效率,我们知道Mybatis默认的JdbcType 是没有Json类型的,那么如何把查询得到的json结果封装成我们需要的对象呢,Mybatis 给我们提供了强大的TypeHandler,利用它,我们可以做到自动将一些特殊类型封装到对应的对象。

1.TypeHandler概念

​ TypeHandler类型转换器,在mybatis中用于实现java类型和JDBC类型的相互转换。mybatis使用prepareStatement来进行参数设置的时候,需要通过typeHandler将传入的java参数设置成合适的jdbc类型参数,这个过程实际上是通过调用PrepareStatement不同的set方法实现的;在获取结果返回之后,也需要将返回的结果转换成我们需要的java类型,这时候是通过调用ResultSet对象不同类型的get方法时间的;所以不同类型的typeHandler其实就是调用PrepareStatement和ResultSet的不同方法来进行类型的转换,有些时候会在调用PrepareStatement和ResultSet的相关方法之前,可以对传入的参数进行一定的处理。
当我们没有指定typeHandler的时候mybatis会根据传入参数的类型和返回值的类型调用默认的typeHandler进行处理.对于一个typeHandler需要配置java类型(javaType)和JDBC类型(jdbcType),typeHandler的作用就是实现这两种类型的转换,在传入的参数为指定的Java类型时,将其转换为指定的JDBC类型,当返回值为指定JDBC类型时将其转换为配置的Java类型。

2.如何使用TypeHandler

​ 现在有这样一个需求,一个学生表,里面有非常多的学生信息,而学生的具体信息是非常多的,我又不想建那么多字段,我想使用Json来存储除了基本的一些信息外的其他信息,这里,为了好演示,我只定义了三个字段,一个id字段用来当主键,一个sno表示学号,一个detail_info来表示学生的其他信息,不同业务规则不同,下面我就以这样一个例子来介绍一下如何使用TypeHandler。

1.建表

如何使用自定义Mybatis-TypeHandler直接将查询到的JSON数据封装到指定对象,达到可复用?

/*测试数据,实际可能还会有更多的具体信息*/
{"age": 12, "sex": 0, "name": "张三"}
[{"age": 12, "sex": 0, "name": "张三"}, {"age": 12, "sex": 0, "name": "张三"}]
{"age": 26, "sex": 1, "name": "李四"}

2.创建实体类

//实体类Student
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String sno;
    private DetailInfo detail_info;
}

//实体类DetailInfo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DetailInfo {
    private  String name;
    private  Integer age;
    private  Integer sex;
}

3.创建自定义的TypeHandler

@MappedJdbcTypes(JdbcType.VARCHAR)//这里对应着数据库中对应的类型,数据库中存的json格式,可看作json串,使用varchar去对应
@MappedTypes({DetailInfo.class})//这里map表示你最终要封装到的对象,这两个注解一定要有并且类型要指定好。
public class JsonTypeHandle implements TypeHandler<DetailInfo> {
    //下面四个方法是需要实现的,可根据需要在方法中进行处理,这里我使用fastjson将json串转为对应的对象
     @Override
    public void setParameter(PreparedStatement preparedStatement, int i, DetailInfo detailInfo, JdbcType jdbcType) throws SQLException {
        Object o = JSON.toJSON(detailInfo);
        preparedStatement.setObject(i,o);
    }

    @Override
    public DetailInfo getResult(ResultSet resultSet, String s) throws SQLException {
        DetailInfo detailInfo = JSON.parseObject(resultSet.getString(s), DetailInfo.class);
        return detailInfo;
    }

    @Override
    public DetailInfo getResult(ResultSet resultSet, int i) throws SQLException {
        DetailInfo detailInfo = JSON.parseObject(resultSet.getString(i), DetailInfo.class);
        return detailInfo;
    }

    @Override
    public DetailInfo getResult(CallableStatement callableStatement, int i) throws SQLException {
        DetailInfo detailInfo = JSON.parseObject(callableStatement.getString(i), DetailInfo.class);
        return detailInfo;
    }
}

4.在mapper文件中使用

这里如果是一般项目,需要自己指定字段对应的typeHandler,如果是springboot项目,可在配置文件中配置,xml文件中则不用配置TypeHandler

普通项目

   <resultMap id="queryMap" type="com.xlape.demo.domain.Student">
        <result column="id" property="sno" jdbcType="VARCHAR"/>
        <result column="detail_info"  property="detail_info" typeHandler="com.xlape.demo.typehandler.JsonTypeHandle"/>
    </resultMap>
    <select id="query" resultMap="queryMap">
        select
        *
        from
        student
        <where>
            <if test="id !=null and id!=''">
                and  id = #{id}
            </if>
        </where>
    </select>
<insert id="insert">
    insert into student(id,sno,detail_info) values(#{id,jdbcType=VARCHAR},#{sno,jdbcType=VARCHAR},#{detailInfo,typeHandler=com.xlape.demo.typehandler.JsonTypeHandleComm})
</insert>

springboot项目

.propertis 配置文件中

mybatis.type-handlers-package=com.xlape.demo.typehandler
   <resultMap id="queryMap" type="com.xlape.demo.domain.Student">
        <result column="id" property="sno" jdbcType="VARCHAR"/>
        <result column="detail_info"  property="detail_info"/> <!--这里就不用指定typehandler了 -->
    </resultMap>
    <select id="query" resultMap="queryMap">
        select
        *
        from
        student
        <where>
            <if test="id !=null and id!=''">
                and  id = #{id}
            </if>
        </where>
    </select>
<insert id="insert">
    insert into student(id,sno,detail_info) values(#{id,jdbcType=VARCHAR},#{sno,jdbcType=VARCHAR},#{detailInfo})
</insert>

5.创建对应的mapper,service,controller接口进行测试

这里代码就不贴了,和平常一样,该咋样传值就咋样传,使用api调试工具测试
如何使用自定义Mybatis-TypeHandler直接将查询到的JSON数据封装到指定对象,达到可复用?

发现达到了我们需要的需求了,成功解决!

3.优化,实现可复用

这里,我们封装一个实体类对象的话,就需要写一个typeHandler,那么我们怎么样可以封装一个可复用的typeHandler,使得不同的实体类自动封装上去呢,下面

高级一点的玩法来了!

将JsonTypeHandle进行优化,使用Object代替对应的对象类型,利用反射原理进行处理,根据原始Json格式转化为相应的Java类型

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({Object.class})
public class JsonTypeHandleComm implements TypeHandler<Object> {
    private Class<Object> clazz;//
    //自动封装对应的字节码
    public JsonTypeHandleComm(Class<Object> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.clazz = clazz;
    }
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {
        Object o1 = JSON.toJSON(o);
        preparedStatement.setObject(i,o1);
    }

    @Override
    public Object getResult(ResultSet resultSet, String s) throws SQLException {
        Object o = JSON.parseObject(resultSet.getString(s), clazz);
        return o;
    }

    @Override
    public Object getResult(ResultSet resultSet, int i) throws SQLException {
        Object o = JSON.parseObject(resultSet.getString(i), clazz);
        return null;
    }

    @Override
    public Object getResult(CallableStatement callableStatement, int i) throws SQLException {
        Object o = JSON.parseObject(callableStatement.getString(i), clazz);
        return null;
    }

注意如果是springboot项目,如果在配置文件中配置后,需要将实体类中的detail_info改成Object类型,不然会报错

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String sno;
    private Object detail_info;
}

如果使用xml配置,则在对应的resultMap中使用TypeHandler参数定义

<resultMap id="queryMap" type="com.xlape.demo.domain.Student">
    <result column="id" property="sno" jdbcType="VARCHAR"/>
    <result column="detail_info"  property="detail_info" typeHandler="com.xlape.demo.typehandler.JsonTypeHandleComm"/>
</resultMap>

某些mybatis版本还需要加上 javaType参数指定对应的实体类, 不然封装不上

<resultMap id="queryMap" type="com.xlape.demo.domain.Student">
    <result column="id" property="sno" jdbcType="VARCHAR"/>
    <result column="detail_info"  property="detail_info" javaType="com.xlape.demo.domain.DetailInfo" typeHandler="com.xlape.demo.typehandler.JsonTypeHandleComm"/>
</resultMap>

但是如果使用springboot方式配置可复用typeHandler的话,实体类DetailInfo就没用了,无法做到json字段映射到对应的实体类字段,我们使用实体类映射的话,可以使用@JsonFiled注解很好的将Json字段和实体类字段对应起来,所以推荐使用实体类作为要封装的基础对象,可读性强而且灵活性好。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DetailInfo {
    @JSONField(name = "name")
    private  String name;
    @JSONField(name = "age")
    private  Integer age;
    @JsonIgnore  //可以根据需要除去某个字段
    private  Integer sex;
}

如何使用自定义Mybatis-TypeHandler直接将查询到的JSON数据封装到指定对象,达到可复用?

这样就相当于与实现了一个可复用的自定义TypeHandler,使用者只需关注使用的实体类和对应的mapper文件就行了,如果是springboot项目的话,只需把封装对应的实体类用Object表示即可,两种方式都可使用,这样想封装什么就封装什么,比如要封装成List<实体类>形式的,JSON.parseObject会根据原始Json格式([{“age”: 12, “sex”: 0, “name”: “张三”}, {“age”: 12, “sex”: 0, “name”: “张三”}])解析成相应的对象。
如何使用自定义Mybatis-TypeHandler直接将查询到的JSON数据封装到指定对象,达到可复用?

总结:

1.使用Mybatis-TypeHandler 我们可以自定义一个我们需要封装到指定对象的一个TypeHandler类.

2.将自定义的Mybatis-TypeHandler 使用Object以及反射特性可以做到Typehandler可复用,可直接将JSON数据封装到实体类对象、Map对象、List<实体类>对象等.

3.推荐使用xml方式自己在resulMap 中使用Typehandler属性去添加typefHandler类,这样原来的开发方式保持不变,我们只需要在要封装的字段上添加自定义的TypeHandler就行了.

4.我们也可以在插入数据库的时候实现自动转json插入.

相关文章

暂无评论

暂无评论...