J-Hi 开发日记(二)
最近在做J-Hi融合SpringJDBC时遇到一个棘手的问题,那就是在insert一条记录时如何取回记录主键值的?问题主要让我纠结在对跨数据库SpringJDBC的处理上,大家都知道象SQLServer或MyServer主键的值是以自增的方式,而象Oracle主建的值通过序列生成并通过insert将值直接插入到表中的。为此SpringJDBC提供了两种机制,
1、主键自增的解决方案
KeyHolder keyHolder
=
new
GeneratedKeyHolder();
this
.getJdbcTemplate().update(
new
PreparedStatementCreator(){
public
PreparedStatement createPreparedStatement(Connection con)
throws
SQLException {
PreparedStatement ps
=
con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
return
ps;
}
}, keyHolder);
return
keyHolder.getKey().intValue(); keyHolder
是数据库自增主键值的持有者,它监听
PreparedStatement的返回的值
Statement.RETURN_GENERATED_KEYS获取主键值,并存放在自己的池中(实际上是一个list)一般来说,一个keyHolder实例只绑定一个
PreparedStatement的执行,当然最好也只是插入一条数据库记录,这样才能保证池中只有一个主键值。
当keyHolder获得主键值后,您可以在任何时候通过访问keyHolder对象得到这个主键值,也就是说只要它的生命期存在,这个主键的值就一直不会丢失。
总结:1)、在执行 PreparedStatement之前创建自增主键的持有者对象keyHolder
2)、在创建 PreparedStatement对象时一定要声明返回主键值,列如
con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS)
3)、只要keyHolder的生命期存在,那么主键的值在任何时候与位置你都可以取得到
2、检索数据库序列生成的主键的解决方案
OracleSequenceMaxValueIncrementer incr =
new
OracleSequenceMaxValueIncrementer(dataSource,
"
SIMPLE_SEQUENCE
"
);
return
incr.nextIntValue();
象Oracle这样的数据库SpringJDBC的解决方案一目了解,通过给定数据源dataSource与序列名"SIMPLE_SEQUENCE"就可以这个序列的最大值。当然还可以通过这个类设计缓冲区大小通过setCacheSize方法,该方法可以一次性取出多个值以减少与数据库的访问次数(数据库的交互是很耗时与耗费资源的)
J-Hi的问题与解决方法
因为J-Hi要实现跨数据库跨多个ORM框架因此对于SpringJDBC这两种方案必须要融合到一起,并且在总体设计上还要与其它的ORM框架(目前J-Hi已融合的ORM框架有hibernate、ibatis2、ibatis3)的接口声明相兼容,因此在对SpringJDBC集成的总体设计上我借鉴了hibernate的方言思想,通过方言将SpringJDBC两种方案融合在J-Hi之中以实现对不同类型数据库主键管理的差异性。
KeyHolder keyHolder =
new
GeneratedKeyHolder();
this
.getJdbcTemplate().update(
new
PreparedStatementCreator(){
public
PreparedStatement createPreparedStatement(Connection con)
throws
SQLException {
ISpringJDBCHiDialect dialect =
sessionFactory.getDialect();
PreparedStatement ps =
con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
if
(stepFlage
==
primaryKeyIndex
&&
valueClass.getPropertyName().equals(primaryKeyName)){
Number _id =
dialect.getSelectKey(entity.getEntityName(), getJdbcTemplate().getDataSource());
return
ps;
}
}, keyHolder);
if
(obj.getPrimarykey()
==
null
)
BeanUtil.setPropertyValue(obj, "
id
"
, keyHolder.getKey().intValue());
从原生的SQL语句上讲Oracle在insert时要插入主键值,而SQLServer恰好相反必须不能插入主键的值,我在设计上就是抓住这一特性,再结合方言,实现了跨数据库的SpringJDBC, dialect.getSelectKey()方法,对应不同的数据库方言,如果是oracle就会生成主键的值,而如果是SQLServer这个方法不会返回任何值,代码如下
Oracle的方言方法:
public Number getSelectKey(String entityName, DataSource dataSource) {
OracleSequenceMaxValueIncrementer incr =
new
OracleSequenceMaxValueIncrementer(dataSource,
"
HIBERNATE_SEQUENCE
"
);
return incr.nextIntValue();
}
SQLServer的方言方法:
public Number getSelectKey(String entityName, DataSource dataSource) {
// 自增主键不用实现该方法
return
null
;
}
通过返回主键值是否为null,还判断在拼写sql时是否插入主键字段的值
最后通过
if (obj.getPrimarykey()
==
null
)
BeanUtil.setPropertyValue(obj, " id
"
, keyHolder.getKey().intValue());
Pojo对象是否主键值(如果没有就说明是自增型的如SQLServer,如果有就说明是序列生成的如Oracle),来将其赋值到POJO的属性中.