使用自定义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.建表
{"age": 12, "sex": 0, "name": "张三"}
[{"age": 12, "sex": 0, "name": "张三"}, {"age": 12, "sex": 0, "name": "张三"}]
{"age": 26, "sex": 1, "name": "李四"}
2.创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String id;
private String sno;
private DetailInfo detail_info;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DetailInfo {
private String name;
private Integer age;
private Integer sex;
}
3.创建自定义的TypeHandler
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({DetailInfo.class})
public class JsonTypeHandle implements TypeHandler<DetailInfo> {
@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"/>
</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调试工具测试
发现达到了我们需要的需求了,成功解决!
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;
}
这样就相当于与实现了一个可复用的自定义TypeHandler,使用者只需关注使用的实体类和对应的mapper文件就行了,如果是springboot项目的话,只需把封装对应的实体类用Object表示即可,两种方式都可使用,这样想封装什么就封装什么,比如要封装成List<实体类>形式的,JSON.parseObject会根据原始Json格式([{“age”: 12, “sex”: 0, “name”: “张三”}, {“age”: 12, “sex”: 0, “name”: “张三”}])解析成相应的对象。
总结:
1.使用Mybatis-TypeHandler 我们可以自定义一个我们需要封装到指定对象的一个TypeHandler类.
2.将自定义的Mybatis-TypeHandler 使用Object以及反射特性可以做到Typehandler可复用,可直接将JSON数据封装到实体类对象、Map对象、List<实体类>对象等.
3.推荐使用xml方式自己在resulMap 中使用Typehandler属性去添加typefHandler类,这样原来的开发方式保持不变,我们只需要在要封装的字段上添加自定义的TypeHandler就行了.
4.我们也可以在插入数据库的时候实现自动转json插入.
|