本章内容包括
- iBATIS API的更多内容
- 插入数据
- 更新和删除数据
- 使用存储过程
5.3 更新和删除数据
至此,我们已经学习了如何向数据库插入数据以及获取相应记录的键值,再来看看如何更新和删除数据。
Insert方法返回的是object类型的值,而Update和Delete方法则返回int类型的值,该值指示了更新或删除语句所影响的记录数。
iBATIS框架允许使用单条语句操作一条或多条记录。这是它与大多数ORM工具不同的地方之一,后者一般只能修改单条记录。
5.3.1 并发更新处理
iBATIS目前尚未实现的一个功能是锁定记录以管理对相同数据的并发修改。有几种技术可用来处理并发更新,如在数据行上使用时间戳或者版本号。比如,有如下一个account表,定义为:accountid serial NOT NULL,
username varchar(10),
passwd varchar(10),
firstname varchar(30),
lastname varchar(30),
address1 varchar(30),
address2 varchar(30),
city varchar(30),
state varchar(5),
postalcode varchar(10),
country varchar(5),
version int8,
CONSTRAINT account_pkey PRIMARY KEY (accountid)
)
我们可以在更新数据的时候增加version列,在update语句的where子句使用accountId和version两个字段。在执行更新操作时,如果要更新的记录未被其它用户修改,那么更新会得以成功地执行,因为版本号未被改变,映射语句也可返回期望的记录数。如果返回的是0,而且未抛出任何异常,那么您就该知道,其他人已经修改了记录。下一步如何处理就由您来决定了。
5.3.2 更新和删除子项记录
在一个对象模型系统中,不难找到一些包含子对象的组件(component)。例如,一个Order对象可能会包含一个OrderItem类型的列表(list)或数组(array),这些子对象表示订单的细项。
因为iBATIS主要是一个SQL映射工具,它不能在更新数据库时去管理这种关系。这样,这种功能就必须由您程序的数据访问层而不是iBATIS来处理了。而其实现代码相当简单:
{
if (null == order.OrderId)
{
sqlMapper.Insert("Order.insert", order);
}
else
{
sqlMapper.Update("Order.update", order);
}
sqlMapper.Delete("Order.deleteDetails", order);
for(int i = 0; i < order.OrderItems.Count; i++)
{
OrderItem oi = order.OrderItems[i];
sqlMapper.Insert("OrderItem.insert", oi);
}
}
这段代码能够实现功能,但它没有提供任何事务机制,因此如果最后一个OrderItem失败,那么所有的其它数据则处于不一致的状态,因为事务只能处理每一次插入和更新操作。此外,性能也是个问题,因为每条的执行都会立即提交。在下节中,您将了解如何使用批量更新技术来解决上述的两个问题(即事务和性能)。
5.4 执行批量更新
译注:在iBATIS.NET中并未提供与此等价的功能,不过园子里的Zealic提供了一种方法,可以借鉴一下。
5.5 使用存储过程
存储过程是在数据库服务器进程中执行的代码块。大部分存储过程都是以特定于数据库的语言(而这些语言一般基于SQL),一些数据库生产商还允许使用其它语言开发(例如,Oracle中可用Java,SQL Server 2005中可用C#,而PostgreSQL允许几乎任何语言)。
5.5.1 三思而行
存储过程往往被Java开发人员视为“敌人”,因为它们是平台相关的(数据库平台相关的, 不一定是操作系统相关的),而这是一些Java开发人员所不接受的。
相比于特定的解决方案,我们更注重于解决问题,我们发现存储过程在优化过程中值得考虑,它还可以解决那些复杂的以数据为中心的问题。
不要作极端主义者
在对存储过程的讨论中存在两个极端的观点。一方面,一些纯Java主义者认为永远不要使用存储过程(更有甚者,有人认为SQL本身也不能使用)。另一方面,一些纯数据库主义者认为与数据库间的任何交互都要通过存储过程来进行。
实际上,那则古老的谚语:“纯粹主义者都是错误的。”在这里也是适用的,两个极端的观点都是错误的。存储过程是一个工具,仅仅如此。考虑一个类似的情景:一位木匠,他有一把锤子,一个卷尺和一张锯。虽然他能够使用锤子来测量木板,但使用卷尺显然更好(使用锯来钉钉子的情况也是如此)。每一项工作都有最合适的工具,每个工具也都是为某种工作设计的。如果选择了错误的工具,它就不会发挥太好的作用。
选择正确的工具
我们可以选择的工具有SQL,存储过程和应用程序代码。一些操作用SQL实现起来不错,另一些可以选择存储过程,其它的则可以使用应用程序代码。
例如,考虑这种情况,我们要从几张表中查询以生成报表。使用应用程序代码,先从几张表中抓取所有数据,然后再进行过滤、连接,没什么意义。为一个简单的查询创建存储过程也会增加复杂性,却不会带来大的好处。将SQL放在iBATIS的映射语句中则是一种快速、简单、高效的解决方案。
现在,有一个更复杂的报表,需要进行多个子查询和左连接从百万级的记录中收集数据,存储过程会更好。使用存储过程,可以有更多选项进行优化。
在应用程序中,如果需要使用动态SQL,映射语句也非常有用。在第8章,我们会看到分别使用C#代码,存储过程和映射语句来实现动态SQL。我们不想吊您的胃口,如果您等不及了,直接跳到8.5节看一下吧。
在使用存储过程时需要考虑的一点是,它们可用来更新并返回数据,如果此时调用存储过程的方法不需要提交(比如仅用作读取数据),因而事务并未提交,那么就可能出现问题。在这种情况下,事务管理器需要完成提交,即使是读操作,或者您手工管理这些事务,就像下面的例子:{
sqlMapper.BeginTransaction();
sqlMapper.QueryForObject("Account.insertAndReturn", a);
sqlMapper.CommitTransaction();
}
catch(Exception)
{
sqlMapper.RollBackTransaction();
}
关于事务的更多内容在第7章,因此要获得更多信息,去那里看看吧。
5.5.2 IN,OUT,INOUT参数
目前为止,我们看到的参数都是输入参数——将它们传给iBATIS,传入的数据保持不变(除了<selectKey>元素)。使用存储过程时,可以使用三种参数:IN,OUT和INOUT。
在iBATIS中使用存储过程时,IN参数用法很简单。将参数传给存储过程跟传给映射语句是一样的。这里是一个简单的存储过程,它接受两个IN参数并返回一个值:(a float4, b float4)
RETURNS float4 AS
$BODY$
BEGIN
if (a > b) then
return a;
else
return b;
end if;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
下面是相应的参数映射,映射语句以及C#代码:
<parameter property="a" column="a" />
<parameter property="b" column="b" />
</parameterMap>
<procedure id="in_example" parameterMap="pm_in_example" resultClass="int" >
max_in_example
</procedure>
condition.Add("a", 7);
condition.Add("b", 5);
return sqlMapper.QueryForObject<int>("Account.in_example", condition);
INOUT参数是指那些可以传给存储过程,可被存储过程修改的参数,就像在下面的例子中,存储过程接受两个数字,交换它们的值。看存储过程的代码(使用MS SQL的T-SQL):
@a INT OUTPUT,
@b INT OUTPUT
AS
DECLARE @temp INT
SET @temp = @a
SET @a = @b
SET @b = @temp
下面是相应的参数映射、映射语句和C#代码:
<parameter property="a" column="a" direction="InputOutput" />
<parameter property="b" column="b" direction="InputOutput" />
</parameterMap>
<procedure id="swap_numbers" parameterMap="para_swap_numbers">
Swap
</procedure>
condition.Add("a", 7);
condition.Add("b", 5);
sqlMapper.Insert("swap_numbers", condition);
OUT参数较为少用。它类似于结果(就像resultMap中的那样),却像参数那样传入。传入的值会被忽略,然后在存储过程中被返回值替换掉。OUT参数可返回任意的单个值(见我们的下个例子)。 下面例子中的存储过程很简单,使用两个IN参数和一个OUT参数(MS SQL的T-SQL):
@a INT,
@b INT,
@c INT OUTPUT
AS
IF @a > @b
SET @c = @a
ELSE
SET @c = @b
这个存储过程接收三个参数并返回void。但是,第三个参数仅用作输出,因此取决于另外两个参数的大小关系,它将被替换为较大的一个。要在iBATIS中使用它,需要如下代码:
Max
</procedure>
<parameterMap id="para_max_number" class="hashtable">
<parameter property="a" column="a" direction="Input" />
<parameter property="b" column="b" direction="Input" />
<parameter property="c" column="c" direction="Output" dbType="int" />
</parameterMap>
condition.Add("a", a);
condition.Add("b", b);
ExecuteInsert("max_number", condition);
//Convert.ToInt32(condition["c"]) is the maximum.
译注:参数c对应的parameter节点中有一个dbType特性,此处不能省略。如果省略,则会出现“String[2]: the Size property has an invalid size of 0.”的异常,原因时参数c被解析为varchar类型,此时OUT参数需要设定长度,故而引发上述异常。将dbType加上就不会有问题了。这个问题也说明parameter Map要尽量写得详细。
存储过程也可以返回多行记录。在对大量数据进行复杂的查询时,如果传统的SQL优化技术没有效果,使用存储过程可能会显著地改善性能。一些更为困难的情况是外连接(outer join)和需要计算的过滤。如果在这些情景中,您决定要使用存储过程,就需要确保对系统的瓶颈有良好的理解,否则不仅不能改善性能,还可能会变得更糟。
5.6 小结
在本章中,我们查看了使用iBATIS修改数据库数据的绝大多数选项。在读完第4章和本章后,您已经了解了使用iBATIS维护数据的所有信息。
在第6章中,我们将继续进行扩展。通过了解更为高级的查询技术,您能够更好地利用已掌握的数据库技能和平台。上一篇:iBATIS In Action:执行非查询语句(一);
下一篇:iBATIS In Action:使用高级查询技术(一);