mybatis自定义typeHandler映射对象为JSON

来源:互联网 发布:linux 漏洞扫描软件 编辑:程序博客网 时间:2024/06/05 15:58
技术背景: 
一个domain对象不可避免的会出现List、Map类型的字段,或者将多个字段拼装到一个字段的情况。
前者存在是业务及设计的需求,后者出现是当初设计数据库没有考虑那么多字段,业务快速发展时需要增加字段,数据库数据量大时,添加一个字段非常耗时,有可能中断服务。为保证服务的可用性,及减少数据订正麻烦,应对频繁的业务变更,会把几个字段拼接到一个字段中。
 这些字段在domain对象中往往采用下面的方式进行处理:解析字段成需要的类型,拼接多个字段填充成一个字段。操作过程类似下面的代码:

public class User implements Serializable{ private static final long serialVersionUID = -3120416800441648924L; private List<String> hobbys;

//存储hobbys private String hobbyExt; private String ext1; private String ext2; private String ext3; //存储ext1 、 ext2、 ext3 的信息 private String extInfo; public String getExtInfo() { return extInfo; } public void setExtInfo(String extInfo) { this.extInfo = extInfo; } public String getExt1() { return ext1; } public void setExt1(String ext1) { this.ext1 = ext1; } public String getExt2() { return ext2; } public void setExt2(String ext2) { this.ext2 = ext2; } public String getExt3() { return ext3; } public void setExt3(String ext3) { this.ext3 = ext3; } public List<String> getHobbys(){ this.parseInfo(); return hobbys; } public void parseInfo() { //TODO 解析字段hobbyExt 成hobbys, 将extInfo解析成ext1、ext2、ext3 } public void fillExtendInfo(){ //TODO 将List hobbys拼装成一个字符串,ext1、ext2、ext3拼接成一个字符串 } public String getHobbyExt() { return hobbyExt; } public void setHobbyExt(String hobbyExt) { this.hobbyExt = hobbyExt; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; }}

这样处理操作繁琐,程序进行存取时要注意解析及填充操作,如果一旦忽略或误用了某个操作,会导致数据库中某个信息没被填充进去,或者产生一些脏数据,极易引入潜在的bug。
       领一种解决解决方法是将解析及填充操作放到DO对象的set及get方法中,类似下面的代码:

public List<String> getHobbys(){ this.parseInfo(); return hobbys; }

        这种处理方式会导致每次存取该字段时都要进行解析操作,增加了不必要的性能开销。不同程序员对不同domain对象定义难以想用,这种解析操作无法共用。如果要增加附加字段,需修改原有的解析方法,又增加了引入bug的风险。这种拼接,解析操作也减低了程序的易读性,导致可维护性降低。

如何避免这种拼接及解析的操作,创建一个干净的domain对象呢?

下面是一个干净的domain对象及其对应的ibatis的的映射文件配置:
UserDO 对象:

//UserDO 对象

public class UserDO implements Serializable { private static final long serialVersionUID = 377875304139361819L; private int id; private String name; private UserExtDO extDO; private List<String> hobbys ; private Map<String,String> votes; public Map<String, String> getVotes() { return votes; } public void setVotes(Map<String, String> votes) { this.votes = votes; } public void addHobby(String hobby){ if(hobbys == null){ hobbys = new ArrayList<String>(); } hobbys.add(hobby); } public List<String> getHobbys() { return hobbys; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public UserExtDO getExtDO() { return extDO; } public void setExtDO(UserExtDO extDO) { this.extDO = extDO; }}

//其扩展字段的对象

public class UserExtDO implements Serializable{ private static final long serialVersionUID = -6065939028174333314L; private String school; private List<String> loves;  

public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } public List<String> getLoves() { return loves; } public void setLoves(List<String> loves) { this.loves = loves; } public void addLove(String love){ if(loves == null){ loves = new ArrayList<String>(); } loves.add(love); }}

 UserDO 的ORM映射文件:

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="UserDO"> <resultMap type="UserDO" id="UserDO"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="extDO" column="extDO" /> </resultMap> <parameterMap type="UserDO" id="UserDO"> <parameter property="id"/> <parameter property="name"/> <parameter property="extDO"/> </parameterMap> <select id="selectAllUsers" resultType="UserDO"> select * from user </select><insert id="insertUser" parameterType="UserDO"> insert into user(id, name, extDO, hobbys, votes) values(#{id}, #{name}, #{extDO}, #{hobbys}, #{votes} );</insert> <select id="mapTest" resultType="UserDO"> select * from user where votes like #{keyword} </select></mapper>

     查看UserDO映射文件,extDO字段是一个对象,程序中没有任何拼接代码,怎么能存到数据库中呢?数据库中该字段时什么类型呢?数据库中extDO的类型如下图:
自定义typeHandler映射JSON与对象增强mybatis功能 - gubaojian - 为世界之光
          extDO字段是Text类型。 UserExtDO对象存储到一个text类型的字段上了,这是如何实现的呢? 这就要靠mybatis提供的自定义类型处理器功能了。
          MyBatis 在预处理语句中设置一个参数,或从结果集中获取一个值时,会使用类型处理器typeHandler将获取的值以合适的方式转换成 Java 类型。数据库中的基本类型之所以能被mybatis转换成Java类型,是因为mybatis已经内置了这些类型的处理器,如下图是mybatis内置的部分处理器: 
自定义typeHandler映射JSON与对象增强mybatis功能 - gubaojian - 为世界之光
        除内置的类型处理器外,mybatis同时提供了类型处理器的扩展功能,允许程序自定类型处理器,或者替换内置的类型处理器。 自定义的类型处理器只需继承TypeHandler接口,然后在xml配置文件配置一下,或者用程序注册类型处理器,即可实现自定义处理器的功能。 
        UseExtDO及List、 Map字段只所以能直接存储到数据库中,就是采用自定义的类型处理器实现的。 在存储时,利用类型处理器将对象序列化成JSON字符串,存储到数据库的一个字段中,在取出时类型处理器将json数据进行解析,反序列化成对象。类型处理器代码如下:

import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import org.apache.ibatis.type.JdbcType;import org.apache.ibatis.type.TypeHandler;import com.alibaba.fastjson.JSON;

public class JSONHandler implements TypeHandler<Object> { /** * json数据和类名的分隔符号 * */ private static final char SPLIT = '/'; public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { if(parameter == null){ ps.setString(i, null); return; } String json = JSON.toJSONString(parameter); json = json + SPLIT + parameter.getClass().getName(); ps.setString(i, json); } public Object getResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); return jsonToObject(json); } public Object getResult(CallableStatement cs, int columnIndex) throws SQLException { String json = cs.getString(columnIndex); return jsonToObject(json); } /** * json 转换成对象 * */ private Object jsonToObject(String json){ if(json == null){ return null; } int index = json.lastIndexOf(SPLIT); if(index < 0){ return null; } String key = json.substring(index + 1, json.length()); json = json.substring(0, index); Class<?> cls = null; try { cls = Class.forName(key); } catch (ClassNotFoundException e) { throw new RuntimeException("序列化成json时找不到指定的类", e); } Object ob = JSON.parseObject(json, cls); return ob; }}

在ibatis.xml 文件增加如下配置: 

<typeHandlers> <typeHandler javaType="com.jianbai.learn.ibatis.domain.UserExtDO" jdbcType="TEXT" handler="com.jianbai.learn.ibatis.handler.JSONHandler"/> <typeHandler javaType="java.util.Map" jdbcType="TEXT" handler="com.jianbai.learn.ibatis.handler.JSONHandler"/> <typeHandler javaType="java.util.List" jdbcType="TEXT" handler="com.jianbai.learn.ibatis.handler.JSONHandler"/> </typeHandlers>

或者通过程序注册类型处理器: 

Reader rd = Resources.getResourceAsReader("ibatis.xml"); SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(rd); Configuration cfg = sf.getConfiguration(); TypeHandlerRegistry tr = cfg.getTypeHandlerRegistry(); //程序注册,不然不起作用 tr.register(UserExtDO.class, new JSONHandler()); tr.register(List.class, new JSONHandler()); tr.register(Map.class, new JSONHandler());

         这样既可把List、Map及对象当做基本类型进行存储。  如果需要在UserExtDO 添加字段,则直接添加属性,设置普通的set和get方法既可,无需做拼接解析操作,并且mybatis配置及映射文件无需做任何变化,数据库也无需做任何变化。 减少字段也可以很方便的进行。业务开发人员无需在做任何字段的解析及拼接的操作, 极大增强了程序的扩展性,易读性,及维护性。   

实际数据库中存储形式如下:

自定义typeHandler映射JSON与对象增强mybatis功能 - gubaojian - 为世界之光
 
       当然你也可以将Java对象存储成其它形式,比如直接采用Java内置的序列化进行处理,存储成二进制的形式。  选用json主要出于通用性考虑,可视化的存储结果,便于对存储结果进行操作及扩展。

相关资源: 
 mybatis参考                            :  http://www.mybatis.org/ 
序列化JSON采用的库FastJSON  :  http://code.alibabatech.com/wiki/display/FastJSON/Inside+Fastjson
0 0
原创粉丝点击