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()
}
以上就是一个简单的存储过程事务控制。大家可以写一个存储过程试试看。