赞
踩
前言:最近在做项目的时候,遇到一个问题,就是数据量特别大的情况下,除了分表分库,索引层面优化,以及加缓存以外,还可以对数据进行进一步处理,比如你要查明细数据,你可以将这些明细信息以Json的形式存储在数据库中,另外保留一些基础信息,这样就能把大量的数据缩减,提高查询效率,我们知道Mybatis默认的JdbcType 是没有Json类型的,那么如何把查询得到的json结果封装成我们需要的对象呢,Mybatis 给我们提供了强大的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类型。
现在有这样一个需求,一个学生表,里面有非常多的学生信息,而学生的具体信息是非常多的,我又不想建那么多字段,我想使用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.创建实体类
//实体类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调试工具测试
发现达到了我们需要的需求了,成功解决!
这里,我们封装一个实体类对象的话,就需要写一个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插入.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。