ibatis 支持枚举类型
很多应用中,数据库表结构都会存在一些状态字段。在关系性数据库中,一般会用VARCHAR类型。使用ibatis的应用,传统做法,往往会使用 String的属性,与之对应。例如一张member表,结构设计如下:

其中status为状态字段。
ibatis中,使用class MemberPO 与之mapping,设计往往如下:
private Integer id;
private String loginId;
private String password;
private String name;
private String profile;
private Date gmtCreated;
private Date gmtModified;
private String status;
// getter/setters

}
缺点:
1)不直观,没人会知道status具体有哪些值。在缺乏文档,并且历史悠久的系统中,只能使用“select distinct(status) from member”,才能得到想要的数据。如果是在千万级数据中,代价太大了;
2)类型不安全,如果有人不小心拼写错误,将会导致错误状态。假设上面列子中,status只允许ENABLED/DISABLED,如果一不小 心,memberPO.setStatus("ENABLEDD"),那么将会造成脏数据。
既然jdk5之后,引入了enum,是否可以让ibatis支持enum类型呢?事实上,最新的ibatis版本,已经支持enum类型(本文使用的是 2.3.4.726版本--mvn repsitory上最新的版本)。
以上代码可以修改成:
1)Status类:
/** enabled */
ENABLED,
/** disabled */
DISABLED;
}
private Integer id;
private String loginId;
private String password;
private String name;
private String profile;
private Date gmtCreated;
private Date gmtModified;
private Status status;
// getter/setters

}
除此之外,其他均无需改动。
为什么呢?ibatis如何知道VARCHAR/Enum的mapping呢?
看过ibatis源码的同学,知道,ibatis是通过jdbcType/javaType得到对应的TypeHandler做mapping处理的。 ibatis有基本类型的TypeHandler,比如StringTypeHandler,IntegerTypeHandler等等。在最新版本中, 为了支持enum,增加了一个EnumTypeHandler。
并且在TypeHandlerFactory中,加了对enum类型的判断,请看:
Map jdbcHandlerMap = (Map) typeHandlerMap.get(type);
TypeHandler handler = null ;
if (jdbcHandlerMap != null ) {
handler = (TypeHandler) jdbcHandlerMap.get(jdbcType);
if (handler == null ) {
handler = (TypeHandler) jdbcHandlerMap.get( null );
}
}
if (handler == null && type != null && Enum. class .isAssignableFrom(type)) {
handler = new EnumTypeHandler(type);
}
return handler;
}
为什么说它取巧,原因是早期ibatis设计过程中,自定义的接口无法得到具体的java class type。故早期的ibatis中,要实现对enmu的支持,非常苦难。而新版本中,为了达到这个功能,作者直接修改了 TypeHandlerFactory的实现,打了一个补丁,如下:
handler = new EnumTypeHandler(type);
}
当然,新版本ibatis能支持enum,已经是一件开心的事情了。
Status枚举类除了描述状态,就足够了吗?回想起很多应用,我是做web开发的,在view层(velocity,jsp,等),见多了类似这样的代 码:
< select >
< option value ="ENABLED" #if($member.getStatus() ==Status.ENABLED) selected ="selected" #end > 开通 </ option >
< option value ="DISABLED" #if($member.getStatus() ==Status.DISABLED) selected ="selected" #end > 关闭 </ option >
</ select >
而事实上,关于状态的信息描述,按照职责分,就应该由枚举类来维护:
1)制定一个接口, EnumDescription.java
public String getDescription();
}
private ResourceBundle resourceBundle;
public ResourceBundleUtil(String resource) {
this .resourceBundle = ResourceBundle.getBundle(resource);
}
public ResourceBundleUtil(String resource, Locale locale) {
this .resourceBundle = ResourceBundle.getBundle(resource, locale);
}
public String getProperty(String key) {
return resourceBundle.getString(key);
}
}
/** enabled */
ENABLED,
/** disabled */
DISABLED;
private static ResourceBundleUtil util = new ResourceBundleUtil(Status. class .getName());
public String getDescription() {
return util.getProperty( this .toString());
}
}
这样,有什么好处:
1)通过Properties文件,支持国际化。
2)描述信息统一由自己来维护,方便维护,并且显示层逻辑简化,如:
< select >
#foreach($status in $Status.values())
< option value ="$status" #if($member.getStatus() ==$status)selected="selected"#end > $status.getDescription() </ option >
#end
</ select >
##############################################################################
那么使用老版本ibatis的客户怎么办呢?就像我们公司使用ibatis 2.3.0,难道只能眼馋着?解决方案:
1)升级到最新版本。 :)
2)ibatis提供了TypeHandler/TypeHandlerCallback接口,针对每种枚举类型,写相应的TypeHandler /TypeHandlerCallback的接口实现即可--工作量大,重复的劳动力。
主要是早期ibatis TypeHandler无法得到javaType类型,无法从jdbc value转成对应的枚举。在我看来,TypeHandler是作mapping用的,它至少有权知道javaType。
3)实现伪枚举类型(允许继承)来实现状态类型安全,而抛弃jdk5的方式--不方便日后升级。
不知道大家是否还有更好的方案?
本文涉及演示代码如下:
演示代码
workspace file encoding:utf-8
build tool: maven
repository:spring/2.5.5;ibatis/2.3.4.726
posted @ 2008-11-05 23:08 stone2083 阅读(1405) | 评论 (0) | 编 辑 收藏
=======================另外一篇============================
ibatis中使用Enum
这个实现给每个Enum对象一个数字值,所以可以把这个数字值存入到数据库中,或者从数据库中取出这个数字值然后恢复这个Enum对象。
1、定义一个所以 Enum的interface
package com.demo;
public interface HasValueInterface {
public Integer getValue();
}
2、定义一个Enum类型
package com.demo;
public enum Status implements HasValueInterface{
ALL(0),
GOOD(1),
BAD(2);
private final int status;
Status(int status) {
this.status = status;
}
public Integer getValue() {
return status;
}
}
3、定义一个抽象的typehandler
package com.demo;
import java.sql.SQLException;
import java.sql.Types;
import com.ibatis.sqlmap.client.extensions.ParameterSetter;
import com.ibatis.sqlmap.client.extensions.ResultGetter;
import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;
import com.demo.HasValueInterface;
public abstract class HasValueTypeHandler implements TypeHandlerCallback {
public abstract HasValueInterface[] getEnums();
public void setParameter(ParameterSetter setter, Object parameter) throws SQLException {
if (parameter == null) {
setter.setNull(Types.INTEGER);
} else {
HasValueInterface status = (HasValueInterface) parameter;
setter.setInt(status.getValue());
}
}
public Object getResult(ResultGetter getter) throws SQLException {
/*千万要注意!!!
* When calling getter.getNull before actually getting the column's value (getter.getInt(),
* getNull() will always return true, since no value has been fetched yet.
* The check for null has to be done after getting the value.
*/
int value = getter.getInt();
if (getter.wasNull())
return null;
for (HasValueInterface status : getEnums()) {
if (status.getValue().equals(value)) {
return status;
}
}
throw new UnsupportedOperationException("No such status");
}
public Object valueOf(String s) {
return s;
}
}
4、给Status实现一个typehandler
package com.demo;
import com.demo.Status;
import com.demo.HasValueInterface;
public class StatusTypeHandler extends HasValueTypeHandler {
public HasValueInterface[] getEnums() {
return Status.values();
}
}