Grails探索之访问存储过程及其事务控制 .

       Grails探索之访问存储过程及其事务控制 .

给Grails项目配置好了数据源以后,就可以访问数据库了。Grails开发平台已经为我们配置好了Hibernate,我们可以使用GORM通过Hibernate访问数据库。这些在官方文档上有了一个初步的、详细的描述,这里就不再重述。
在很多的时候,我们会使用存储过程访问数据库,而不是使用ORM,因为存储过程对于复杂的数据库访问比起ORM有强大的优势。这就提出了一个问题,如何在Grails平台访问数据库的存储过程?
据说Hibernate为用户开放了访问存储过程的接口,但一方面我以前没有使用过Hibernate,对它不是很熟悉;另外一方面,我对GSQL的存储过程接口很感兴趣,感觉它比我以前使用的Spring的存储过程接口要好。所以,下面我将使用GSQL的存储过程接口在Grails平台来访问数据库。
我们先从简单的存储过程说起,假设我们有一个叫“PA_PPRM_PivotCar.P_Tran”的存储过程,有三个输入参数,没有输出参数。大家可以试着写一个存储过程,有包没包都没关系,这个存储过程有多个输入参数,作用是简单的insert一个table。
下面我们来访问这个存储过程。
class LoginService {
   
        def dataSource
   
    def check(User u) {

我们对存储过程的访问都是在service层进行的,注意声明dataSource,这样Grails平台会将dataSource对象通过Spring注入进来,我们就可以在服务层使用数据源了。
我们对存储过程的访问在check(User u)方法里,下面是代码:
        Sql sql = new Sql(dataSource)
       
       
上面的代码是初始化Sql对象,记得引入该类,“import groovy.sql.Sql”,然后就可以访问存储过程了:
        sql.call("call PA_PPRM_PivotCar.P_Tran(?,?,?)",['3','cheng','m'])
}

很简单,使用sql对象的call方法,第一个参数是一个字符串“call PA_PPRM_PivotCar.P_Tran(?,?,?)”,使用过Jdbc访问过存储过程的人都知道这个字符串的意思,这里不再多说。第二个参数是存储过程的输入参数,以List对象组织,按存储过程的输入顺序作为List对象元素的顺序。
就这样就完成了对存储过程的访问,是不是很简单?
下面就再进一步,我们把上面的“PA_PPRM_PivotCar.P_Tran”进行改造,给这个存储过程一个返回值,存储过程的功能还是insert一个table,因为这个table有一个主键,所以如果输入的记录主键重复的话,insert就会失败,这个存储过程的返回值就是insert成功还是失败,如果成功返回0,失败返回-1。
在这种情况下,在调用call方法之前的步骤还是和以前一样,我在这里不再重述。下面是call方法的代码:
def retn
      
       sql.call("call PA_PPRM_PivotCar.P_Tran(?,?,?,?)",['3','cheng','m', Sql.VARCHAR]){
           retn = it
    }

首先定义一个变量获取返回值,即“retn”变量。可以看到,存储过程名后面跟着四个“?”,其中三个代表存储过程的输入参数,一个代表存储过程的输出参数。
call方法的第二个参数仍然是一个List对象,先按顺序给定存储过程的输入值,完了以后再按顺序给定存储过程的返回值的类型代表值,如String类型的返回值的代表值是“Sql.VARCHAR”等等。
最后通过一个闭包来获取存储过程的返回值,即:
{
           retn = it
    }

这样,我们获取返回值的存储过程调用就完成了。
你可能要问,如果有两个返回值怎么办?
def retn1
def retn2
      
       sql.call("call PA_PPRM_PivotCar.P_Tran(?,?,?,?,?)",['3','cheng','m', Sql.VARCHAR, Sql.VARCHAR]){
       value1,value2 ->
           retn1 = value1
           retn2 = value2
    }

上面演示了对一个返回两个String类型值的存储过程的调用情况,可以看到,有多少个返回值,我们的闭包就有多少个输入参数,每一个参数按顺序对应一个返回值。

下面,我们再把存储过程的输出参数难道加大,我们现在有这样一个存储过程“PA_PPRM_PivotCar.P_PivotCar_GetList”,它有一个输入参数,还有一个输出参数,但这个输出参数游标。大家可以做一个简单的存储过程,用来获取一个table的所有记录,把它放在游标里返回。
下面是访问这个存储过程的代码:
        sql.call("call PA_PPRM_PivotCar.P_PivotCar_GetList(?,?)",['2008',Sql.resultSet(OracleTypes.CURSOR)]){
        carModel ->
             carModel.eachRow(){
                x ->
                 println"car style: ${x.carStyle},car series: ${x.carseries},car type: ${x.cartype}"
             }
        }

先看call方法的输入参数,第一个参数和上面的没有什么不同,第二个参数不同在于输出参数类型的代表值,为“Sql.resultSet(OracleTypes.CURSOR)”,如果是访问的是Oracle数据库的话,游标的代表值固定就是它。
最后来看闭包对返回值的处理:闭包的输入参数是一个resultset类型对象,如“   carModel”,所以要对它进行eachRow的处理,在eachRow的闭包里,我们就可以对它的输入参数x进行操作了。
在我的“PA_PPRM_PivotCar.P_PivotCar_GetList”存储过程里,游标里存的是这样一些参数:carStyle、carseries、cartype等。所以我们就可以使用“x.carStyle”、“ x.carseries”和“x.cartype”来获取它们的值了。
上面代码的输出结果为逐行打印游标每一条记录里三个参数carStyle、carseries、cartype的值。

有了上面的基础,大家可以试着做一些练习:比如存储过程返回一个String类型值和一个游标,存储过程返回两个游标等等。

简单的存储过程调用到这里就可以告一段落,现在的问题是,如果我需要调用上面的存储过程,如“PA_PPRM_PivotCar.P_Tran”多次,即我一次对一个table进行多次insert操作,怎么办?
你肯定会说,这很简单啊,对上面的call语句进行多次调用就行了,比如把上面的call语句放在一个for循环里头,不就可以了。方法是对了,但这里有一个问题,聪明的你一定想到了,“事务”。
对了,怎么控制事务呢?
下面就是一个例子:
       def conn = dataSource.getConnection()

要对事务进行控制,首先要获取数据源的Connection对象,不能直接控制dataSource对象的autoCommit方法,我还没有搞清楚为什么。
然后是设置Connection对象的autoCommit为false,即由我们自己来控制事务。
       conn.setAutoCommit(false)


使用Connection对象来初始化Sql对象。
       Sql sql = new Sql(conn)

然后是我们属性的事务控制方法,try…catch语句。
       try
       {

然后调用多个存储过程。
               sql.call("call PA_PPRM_PivotCar.P_Tran(?,?,?)",['3','cheng','m'])
              
              sql.call("call PA_PPRM_PivotCar.P_Tran(?,?,?)",['1','feng','m'])

commit事务。
              conn.commit()
       }
       catch(java.sql.SQLException e)
       {

如果失败,则回滚。
           conn.rollback()
           throw e;
       }
       finally
       {

最后,记得关闭Connection对象,因为dataSource对象会为你自动关闭Connection对象,但是你从dataSource对象中获取了Connection对象的话,就需要你自己关闭Connection对象。
           conn.close()
    }


以上就是一个简单的存储过程事务控制。大家可以写一个存储过程试试看。

你可能感兴趣的:(grails)