如何在当前的Hibernate Session中写sql，并受事务控制?
李小翔
2013-10-18 09:46

内容列表
问 	 # 
答 	 # 
适用于 	 # 

问
    Hibernate虽然好用，但是也有很多不足，比如不支持from的子查询、大批量数据插入时的性能问题，这些时候，我们通常借助于原生的sql来处理。
    在控制器、service或者domain类中，我们通常可以通过获得grails自动注册的dataSource或dataSourceUnproxied来获得jdbc数据库连接（Connection）来写sql，如
    def dataSource
   
   void test() { 
        java.sql.Connection conn = dataSource.getConnection();    // 获得一个新的数据库连接。也可以使用API直接获取： DatabaseUtils.getConnection()
        try { 
           ...
       }  finnaly { 
            conn?.close()        // 关闭连接
        } 
   } 
   
   void test2() { 
        groovy.sql.Sql sqlInst = new Sql( dataSource );            // 获得一个groovy的sql实例。也可以使用API直接获取： DatabaseUtils.getSql()
        try { 
           ...
       }  finally { 
            sqlInst?.close()    // 关闭连接
        } 
   } 
   
    但是通过这种方式获得的jdbc连接和grails自动注入的连接并不是同一个，从而导致无法受到当前事务控制，无法和其他hibernate的数据库操作一块回滚。如下面的代码所示，在事务回滚后，张三的插入操作将会被回滚，而李四却被删除了，这显然不是我们希望的结果。
    void test3() { 
        Employee.withTransaction{  status ->
           // 创建员工张三
            new Employee(name: ”张三”).save();        
           // 删除员工李四
            groovy.sql.Sql sqlInst = new Sql( dataSource );
           try { 
                sqlInst.executeUpdate(”delete from brofwk_osm_employee where name=’李四’”);
           }  finally { 
                sqlInst?.close();
           } 
           // 回滚事务
            status.setRollbackOnly();
       } 
   } 

答
    还好 Hibernate 通过 sessionFactory 提供了获得当前session的方法，通过下面的代码即可获得当前session的数据库连接，将其替换到上面的示例代码test3中，就可以统一控制事务回滚：
    def sessionFactory
  
   void test4() { 
        groovy.sql.Sql sqlInst = new Sql( sessionFactory.currentSession.connection() )        // 也可以使用API直接获取： DatabaseUtils.getSqlCurrent()
        ....
   } 
   
    此外，这样获得的数据库连接，使用完成后，可以不用加trycatch来close链接，session结束的时候，会自动释放相关资源。
    不过保持一个close的好习惯，也是不错的；）

适用于
    DatabaseUtils 是 BroToolkit 提供的一组数据库相关的API。