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的属性中.