POJO & Code Generation & ...

POJO & Code Generation

1. Code Generation
我所知道的 Code Generation 有几种方式,
JSP (Ant), AspectJ, CGLib (asm), Jboss Java assist.
Sun的JDO Reference Implementation 里面的Enhancer 也可以生成JVM指令。

JSP (Ant), AspectJ 两种方法都是操作source code,然后调用Javac编译成Class。
其他的方法是直接操作 Class文件(Byte Code)。这些方法当中,Java assist 值得提一下。
ASM, BCEL都是直接生成JVM指令。Java assist却可以直接填入源码,内置编译器会把源码编译为JVM指令,在放到Class文件中。看了一下它的例子,觉得比较友好。

2. ORM & POJO
ORM中,
JAXOR不用POJO, 自动生成Entity, DAO等代码。
iBatis使用纯粹的POJO,不用enhance。
Hibernate需要动态enhance class。
JDO(1.0) 的Sun Reference Implementation 需要静态enhance class.
JDO( 1.0) 的Open Source实现有
1.TJDO http://tjdo.sf.net
2.Speedo http://speedo.objectweb.org
3.JORM http://jorm.objectweb.org
4.XORM http://xorm.sourceforge.net
5.JPOX http://jpox.sourceforge.net
6.OJB http://db.apache.org/ojb/
7. Castor ?

好像只有XORM是象Hibernate那样,动态enhance class. 其他的都是静态enhance class。

3. Lightor
我一年多前,就做了一个相当于 iBatis 和 JAXOR 级别的 持久层工具 Lightor。和众多的持久层框架相比,Lightor实在没有太多的新意和附加价值,所以一直没有发布,免得献丑。
随着经验的增加,并把一些关键问题帖到Javaeye上和大家讨论,增长了不少心得,Lightor也进行了几次大改动。
Lightor的初始版本,很象JAXOR,不支持POJO。随着改进,就越来越向iBatis靠拢,支持POJO。
iBatis支持POJO,基本上是用reflection。
Lightor支持POJO,用的是Code Generation。类似于静态enhance class的方法,但Lightor并不enhance bean class, 而是根据bean class的信息生成一个对应的Mapper类的Source Code。然后编译成Class, 打包成jar。这个jar添加到class path,程序就可以运行。
例如,这样一段程序
import test.lightor.Person;
Person person = new Person();
person.setName(…); // … setters here
persister.insert(person);

Persister首先根据person.getClass() = test.lightor.Person。
然后根据这个类名,寻找对应的 Mapper.test.lightor.PersonMapper类。
这个Mapper.test.lightor.PersonMapper类就是静态生成的。里面包含了table name, key names, column names 等信息,可以把Person映射为一个关系表纪录。
这个Mapper类就相当于iBatis的reflection部分,只是这里静态化了。

4. Source Generation
为什么要采用source code generation的思路呢?
最重要的原因就是简单、直观。Byte Code Generation,即使如Java Assist, 也不是很容易掌握的。
还有就是 坦诚,没有藏一手,容易debug。在IDE中,把Mapper的mapper.source.jar 源代码包 attach到 mapper.jar 运行库包,如果需要,用户可以看到所有的source, 能够很明白这些事情是怎么做的,也可以debug到任何一步真正的source对应的指令。
Source Code Generation,我首先想到的是Velocity,而不是我自己的template工具fastm。
因为fastm使用{}作为变量标志,而java code中有大量的{},velocity用$, ${}标志变量,更明确一些。
再其次,fastm使用 xml comment作为loop block标志,适合xml, html等,而velocity使用 #, 适用范围更广。
但我在使用velocity的过程中,遇到了困难。
我的想法是,把标准code template和code generator class放在一起,即使打成jar,也能够顺利找到code template。
我就采用了,CodeGenerator.class.getClassLoader().getResourceAsStream( … code tempate path)
这个时候,CodeGenerator的class loader会正确寻找到同一个jar里面的template文件。
但是,我试了velocity的很多方法,都没有办法让velocity解析这个stream。Template类只接受 filename 参数。Velocity类的evaluate方法确实接受stream参数,但是试了半天,里面总是抛出Null pointer,也很难debug。
没有办法,我还是转向了fastm。其实fastm也能够很容易地支持java code generation,因为当时为了支持java script code generation。也支持了code comment style。比如。

public void populateBean(Person b, ResultSet rs) throws SQLException{
int index = 0;
// BEGIN DYNAMIC: keyColumns
index = metaIndice[{index}]; if(index > 0) b.{setter}(rs.get{resultSetType}(index));
// END DYNAMIC: keyColumns
// BEGIN DYNAMIC: nonKeyColumns
index = metaIndice[{index}]; if(index > 0) b.{setter}(rs.get{resultSetType}(index));
// END DYNAMIC: nonKeyColumns
}


5. Update changed columns
为什么重量级的O/R (如Hibernate, JDO等)都要enhance POJO class, 而不是像iBatis那样使用reflection without enhance?
原因之一,就是为了监控bean property的变化,这样update的时候,就可以只更新变化的字段。
iBatis的update SQL都是要自己写的,你想更新哪些字段,就写在sql里面好了,控制完全在你。非常简单,直观。当然不需要为了这个目的enhance POJO class。
Lightor以前很象JAXOR,自动生成Entity Bean,所以能够监听bean property的变化。但这种方式侵入性太强,阻碍了用户使用Domain Object的途径。所以,Lightor后面逐渐转向iBatis的方向。
这也就造成了,Lightor这一点上,就两头不讨好。Lightor虽然基本和iBatis的效果差不多,但是还是有一些不同(如果没有这些不同,那Lightor根本就没有一点存在的必要了)

Lightor的SQL由用户传入,而不是事先写在Config文件里面。
Lightor对SQL没有一点处理,变量替换也没有。
Lightor对Join的支持非常好,可以把result set动态智能地匹配到一组POJO里面。
Lightor的update, insert是自动生成的,不需要用户自己指定。正是因为这个,update几个特定字段的时候,才不如iBatis方便。
于是,Lightor在提供了 update(object ) 方法之外,还提供了一个 update(object, changeInfo)方法。这个相当于iBatis的update(SQL_ID, object)。
当然,iBatis的update(SQL_ID, object) 通用而直观,不仅update, 还能insert,而且整个SQL可以明确地指定。Lightor不这么做,是为了避免分析SQL, 和reflection。

6. Back and Forth Value Block
前面讲到,Lightor提供了一个 update(object, changeInfo)方法,对应iBatis的update(SQL_ID, object)。

比如,我们有一个person.
// DAO part
static String[] ageColumns = {“age“, “updatedDate“};
static UpdateInfoBlock updateAge = new UpdateInfoBlock(ageColumns);

// business part
Person person = ...
person.setAge( age);
person.setUpdatedDate(new Date());

// DAO part
persister.update(person, updateAge);

这段程序表示只更新person的age和updatedDate两个字段。如果只是update(person),那么表示更新所有的字段。

这里用到了一个updateAge参数,是一个UpdateInfoBlock结构。为什么不把ageColumns这个字段名数组直接作为参数传进去呢。
主要是为了效率。因为内部程序需要根据 ageColumns 的定义搜索PersonMapper的关于column 的定义,并生成对应的sql,比如,
Update t_person set age = ?, updatedDate = ? where id = ?
而我们知道,对于一个特定的DAO操作,这个sql每次都是一样的,不需要每次都重新生成一遍。我们可以把这个sql缓存起来。
一种方法是以“字段名数组”作为key, 放到一个Map当中(比如,那个ReadWriteMap帖子)。每次都可以取出来。这也可以用AOP来实现,比如用一个Cache Interceptor.但这里我不想造成Cache Everywhere的情况。一是如果Map数据比较多的时候,find by key的效率会受到影响。二还要考虑是否给用户一个配置选项,cache on or off?
我采用了另一种侵入比较强的方法。UpdateInfoBlock里面不仅包括了一个String[]的“字段名数组”,还包括了一个 “Update SQL” 字段。Lightor首先判断Update SQL是否为空,如果不为空,那么直接使用这个Update SQL,如果为空,那么根据“字段名数组”生成这个Update SQL。
如果UpdateInfoBlock声明为static,那么整个运行过程中,Update SQL只需要生成一次,相当于Cache起来(由于只有 = 赋值操作,不存在并发同步的问题,参见Read Write Map 帖子,此处不再赘述)。运行效率非常高,没有find by key的over head。
如果声明为局部变量,那么Update SQL每次都为空,都需要重新生成一次,相当于不使用Cache。
这种模式,我称之为Back and Forth Value Block。其特点在于另外开辟了一个隐含的空间段,用来存放可以缓存的信息(Update SQL等信息)。
我并不特别推荐这种方法,因为侵入性非常强,需要在API Doc中特别说明。不过还好,这毕竟还属于DAO内的方法,没有侵入到POJO。

下面讲一下,select的思路。Lightor的select过程是这样。
1. 根据 POJO Class 的定义,找到对应的Mapper。有可能是一组POJO Class,对应一组Mapper,比如多个table join的情况。
2. Lightor根据ResultSetMetaInfo和Mapper组,确定出ResultSet的那几个字段属于哪个Mapper。
这个运算过程也还是需要一点时间的,最好也可以缓存起来。同样,我没有采用Cache Interceptor的AOP方法,而是采用了Back and Forth Value Block。

8. Summary.
以上是Lightor的关键改进思路。很多是根据javaeye的讨论,收益很大。
我着重提出了这些思路中比较别扭的地方(Code Generation, Update Changed Colums, Back and Forth Value Block等)。
一个是因为个人视野能力的限制,一个是因为头脑恰好还没有转过那个弯。
希望借大家的慧眼,指出我的盲点。:-)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值