很多应用中,数据库表结构都会存在一些状态字段。在关系性数据库中,一般会用VARCHAR类型。使用ibatis的应用,传统做法,往往会使用String的属性,与之对应。
例如一张member表,结构设计如下: ![]() 其中status为状态字段。 ibatis中,使用class MemberPO 与之mapping,设计往往如下: public class MemberPO implements Serializable {
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类: public enum Status {
2)MemberPO类:/** enabled */ ENABLED, /** disabled */ DISABLED; } public class MemberPO implements Serializable {
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类型的判断,请看: public TypeHandler getTypeHandler(Class type, String jdbcType) {
ibatis使用了取巧的方法,当取不到基本类型的handler时候,判断javaType是否是Enum类型--Enum.class.isAssignableFrom(type),如果是,则使用 EnumTypeHandler进行mapping处理。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的实现,打了一个补丁,如下: if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
这个设计有悖于和早前的设计思想。早期,TypeHandler都是通过public void register(Class type, String jdbcType, TypeHandler handler)方式事先注册到factory中的,而这次,是在运行期,通过new方法动态得到EnumTypeHandler。handler = new EnumTypeHandler(type); } 当然,新版本ibatis能支持enum,已经是一件开心的事情了。 Status枚举类除了描述状态,就足够了吗?回想起很多应用,我是做web开发的,在view层(velocity,jsp,等),见多了类似这样的代码: #if($member.getStatus()==Status.ENABLED)开通#elseif($member.getStatus()==Status.DISABLED)关闭#end
web层需要多少个页面,就需要维护多少份这样的代码;以后每添加/删除一种状态,多个地方都需要修改,还要担心逻辑不一致。<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 interface EnumDescription {
2)写一个ResourceBundleUtil.java,通过Properties文件得到描述信息:public String getDescription(); } public class ResourceBundleUtil {
3)Status等枚举类实现EnumDescription: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); } } public enum Status implements EnumDescription {
/** enabled */ ENABLED, /** disabled */ DISABLED; private static ResourceBundleUtil util = new ResourceBundleUtil(Status.class.getName()); public String getDescription() { return util.getProperty(this.toString()); } } 这样,有什么好处: 1)通过Properties文件,支持国际化。 2)描述信息统一由自己来维护,方便维护,并且显示层逻辑简化,如: $member.getStatus().getDescription()
<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
|
|