打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
mybatis自定义typeHandler映射对象为JSON

mybatis自定义typeHandler映射对象为JSON  

2012-02-14 17:12:43|  分类: web|字号 订阅

技术背景: 
一个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的类型如下图:
          extDO字段是Text类型。 UserExtDO对象存储到一个text类型的字段上了,这是如何实现的呢? 这就要靠mybatis提供的自定义类型处理器功能了。
          MyBatis 在预处理语句中设置一个参数,或从结果集中获取一个值时,会使用类型处理器typeHandler将获取的值以合适的方式转换成 Java 类型。数据库中的基本类型之所以能被mybatis转换成Java类型,是因为mybatis已经内置了这些类型的处理器,如下图是mybatis内置的部分处理器: 
        除内置的类型处理器外,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配置及映射文件无需做任何变化,数据库也无需做任何变化。 减少字段也可以很方便的进行。业务开发人员无需在做任何字段的解析及拼接的操作, 极大增强了程序的扩展性,易读性,及维护性。   

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

 
       当然你也可以将Java对象存储成其它形式,比如直接采用Java内置的序列化进行处理,存储成二进制的形式。  选用json主要出于通用性考虑,可视化的存储结果,便于对存储结果进行操作及扩展。

相关资源: 
 mybatis参考                            :  http://www.mybatis.org/ 
序列化JSON采用的库FastJSON  :  http://code.alibabatech.com/wiki/display/FastJSON/Inside+Fastjson  
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Json.net的高级用法
Mybatis查询oracle分页
mybatis代理
Ext与后台数据库交互
SSH(Struts,Spring,Hibernate)和SSM(SpringMVC,Spring,MyBatis)的区别
mybatis3 空字段null字段不返回
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服