最近在做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的属性中.