在JavaEE应用中,使用ORM操作数据库虽然简单快捷(参考“高效使用JavaEE
ORM”),但是毕竟是对JDBC的封装,很多时候,ORM还是不能满足我们的需求,主要是两个问题:
1. 速度不如JDBC,毕竟是封装JDBC,有额外的开销;
2. ORM提供的xQL很多时候无法满足需求,还需要数据库相关的SQL,这时,必须使用JDBC。
使用JDBC虽然麻烦点,但是,按照软件设计的思想,一步一步封装必要的代码,还是可以做到性能与开发效率并存。
首先,要坚决避免的就是不断重复编写try... catch... finally...。对于查询、更新、插入和删除操作,每一种操作只允许编写一次try...
catch... finally...。如何实现?有两个方法。
第一个办法是找一个现成的封装了这些JDBC操作的框架,最好的方案当然就是Spring的JDBC框架了,顺便可以参考JdbcTemplate的源码,以便提升自己的JavaEE功力。
如果不使用Spring,那就采用第二个办法,自己造轮子,封装一个JDBC框架。
很多人反对自己造轮子,原因不外乎费事。不过很多时候,造轮子并不麻烦,而且可以满足特定的需求。今天要造的轮子就是一个封装JDBC的框架:Express-Persist。
Express-Persist是ExpressMe的持久化子项目,目标是封装JDBC并提供简单的数据库操作接口。
为什么不使用Spring JDBC呢?主要原因只有一个:Spring的JDBC目前还是1.4兼容的,不支持1.5的泛型。Express-Persist要提供的接口除了基本的数据库操作外,还要实现:
1. 简单的ORM映射,注意是简单的,没有Hibernate那样完整而强悍,本质上就是把ResultSet的每一条记录变成一个JavaBean,可以参考Spring
JDBC的RowMapper,实现非常容易;
2. 充分利用Java 5泛型支持,都是类型安全的参数和返回值,不用做强制转化;
3. 利用Java 5的注解(Annotation)把SQL标记在接口方法上,比如:
1. @Query ( "select
* from User where u.id=:id" )
2.
public
User get(String id);
4. 最后,最重要的,只编写接口,没有实现类!
没有实现类,那JDBC代码写在哪?当然由Express-Persist框架自动生成了。如何自动生成?运行一个命令自动生成Java类?在“高效使用JavaEE
ORM”一文中我们已经对JDO的这种静态增强方式表示了强烈的鄙视和唾弃,因此绝不可重蹈覆辙。Express-Persist会在启动时根据接口动态创建出类,不过我们不采用Hibernate使用的CGLIB库,而是直接通过JDK的动态代理功能实现动态类。
如何绑定SQL参数
DAO接口的方法参数要自动绑定到SQL参数中,由于方法参数的顺序与SQL参数的顺序可能不一致,因此,只能使用命名参数来绑定,即:SQL参数定义为:xxx,对应的方法参数用@Param("xxx")标记。
当SQL参数很多的时候(尤其是INSERT语句),方法参数也非常多,调用起来非常不方便,比如:
1. @Update ( "insert
into User values(:id, :email, :password, :name)" )
2. public
int create( @Param ( "id" )
String id, @Param ( "email" )
String email, @Param ( "password" )
String password, @Param ( "name" )
String name);
而且都是String类型,调用起来容易出错。
因此,Express-Persist允许使用JavaBean绑定,把上述代码变为:
1. @Update ( "insert
into User values(:u.id, :u.email, :u.password, :u.name)" )
2. public
int create( @Param ( "u" )
User u);
这样,调用起来只需要传入一个User对象即可,简单且不易出错。
如何分页查询
绝大多数数据库支持分页查询,但语法各不相同。如果让开发者自己写分页SQL语句,难度较大,而且不易复用。因此,Express-Persist仿照Hibernate的做法,为每一种数据库定义一个Dialect,处理分页,这样,无需考虑数据库的特定分页语法,只需额外添加@FirstResult和@MaxResults这两个注解,以便传入分页参数:
1. @Query ( "select
* from User u order by id" )
2. List<User>
queryAll( @FirstResult
int first,
@MaxResults
int max);
Express-Persist已经内置HSQLDB、MySQL和Oracle的Dialect支持,也可以编写其他数据库的Dialect,只需实现Dialect接口即可。
如何把ResultSet映射为Java对象
要把ResultSet映射为Java对象,我们采用Spring JDBC使用的RowMapper方案,改进之处在于采用了泛型,并且,提供一个BeanRowMapper,实现ResultSet到JavaBean的转换,因为大部分的转换都是到JavaBean。
利用Java 5的泛型支持,可以非常容易地生成一个BeanRowMapper,而无需编写任何方法:
1. public
class UserRowMapper
extends BeanRowMapper<User>
{}
@MappedBy用于告诉Express-Persist如何映射ResultSet:
1. @MappedBy (UserRowMapper. class )
2. @Query ( "select
* from User u order by id" )
3. List<User>
queryAll( @FirstResult
int first,
@MaxResults
int max);
如果返回结果仅有一个,例如根据主键查询,则必须加上一个@Unique注解,这样,Express-Persist将自动检查返回的记录数,如果不为1,则抛出异常:
1. @Unique
2. @MappedBy (UserRowMapper. class )
3. @Query ( "select
* from User u where id=:id" )
4. User
queryById( @Param ( "id" )
String id);
如果返回结果允许多个,则返回值应该定义为泛型List,如List<User>。
Batch支持
批量插入或修改时,使用和不使用JDBC Batch,其性能将有数量级的差距。Express-Persist提供Batch支持,通过继承BatchSupport接口:
1. public
class UserDao
extends BatchSupport
{
2. @Update ( "update
User set name=:name where id=:id" )
3. void
updateUserName( @Param ( "id" )
String id, @Param ( "name" )
String name);
4. }
Batch操作的代码稍微复杂一点,必须用try... finally执行,以便正确释放资源:
01. try
{
02. dao.prepareBatch();
03. //
now the batch prepared:
04. dao.updateUserName( "id-1" ,
"change A's name" );
05. dao.updateUserName( "id-2" ,
"change B's name" );
06. dao.updateUserName( "id-3" ,
"change C's name" );
07. //
execute:
08. int []
results = dao.executeBatch();
09. }
10. finally
{
11. dao.closeBatch();
12. }
事务控制
Express-Persist仅支持JDBC事务,因此无法远程传播事务。事务代码通常写在Web应用程序的Filter或Interceptor中,只需编写一次:
01. TransactionManager
txManager = ...;
02. Transaction
tx = txManager.beginTransaction();
03. try
{
04. //
TODO: DAO operations here...
05. tx.commit();
06. }
07. catch
(Exception e) {
08. tx.rollback();
09. }
如果你想体验一下Express-Persist带来的全新Java持久化方案,可以从http://express-me.googlecode.com/files/express-persist.jar下载Jar包(含源代码)。完整的文档请参考http://code.google.com/p/express-me/wiki/ExpressPersist。 |