iBATIS系统学习笔记二

时间:2021-11-08 09:38:06

目录:
学习笔记零 - 起源
学习笔记一 - 概念与入门
学习笔记三 - 高级技巧与进阶
学习笔记四 - 技巧与实践


iBATIS基础知识

安装和配置iBATIS

内容
- 获取iBATIS
- iBATIS和JDBC
- iBATIS配置基础

1 iBATIS的依赖性

iBATIS的一些特效需要依赖包才能使用。

1.1针对延迟加载的字节码增强

字节码增强是一种可以根据你所定义的配置活其他规则在运行时修改代码的技术。
iBATIS允许将某些SQL查询和其他一些SQL查询关联起来。这点很像O/RM工具,iBATIS的优势在于更加灵活,能够达到多个数据库查询的效果。
如果你有1000个客户,每个客户都有1000个订单,同时每个订单都有25个订单项,那么合并后将需要25,000,000个对象,显然这个订单项的数据太大了,根本不可能同时把它们全部加载到内存中。
我们可以重新配置SQL映射以便加载那些关联列表。这样一来,当用户查看客户列表时,只有这1000个客户对象会保存在内容中,加载其他列表所需的信息始终可用,但是直到实际需要这些数据时菜真正加载。也就是说,订单信息不是一开始就被加载,只有等到用户单击某个客户查看订单时才加载。同理,当用户单击某个订单时,选中的25个订单才会加载。
只需要更改配置,不需要修改代码的情况下,就能完成这个修改。

1.2 Jakarta Commons数据库连接池

iBATIS是一个用于简化应用程序与数据库交互的工具,所以很明显应用程序必须连接到某个数据库。按需创建新的连接是非常耗时的,因此iBATIS不使用这个方法,而是使用一个连接池,连接池始终处于连接状态并且可以为应用程序的所有用户共享。
很多软件开发商所提供的驱动程序都有支持链接池化版本,但是存在一个问题,即这些连接池的特性和配置同它们的实现一样各不相同。
而Jakarta Commons Database Connection Pool(DBCP)项目正是用于解决以上问题,他是一个包装器(wrapper),可以使我们轻松地使用任意的JDBC驱动程序作为连接池的一部分。

1.3 分布式高速缓存

iBATIS提供了一个使用OpenSymphony cache(OSCache)的告诉缓存实现方案。OSCache可被配置为夸多服务器集群以提供可伸缩性和错误恢复支持。


2 将iBATIS添加到应用程序中

首先需要将iBATIS的配置文件加入应用程序的编译时和运行时的类路径中。

2.1 加入小程序中

java -cp ibatis-sqlmap-2.jar: ibatis-common-2.jar:. MyMainClass

2.2 在Web应用程序中使用iBATIS

放入lib中。
这些显然不够现在的需求,待补充


3 iBATIS和JDBC

Sun公司的JDBC API是Java编程语言中的数据库连接标准。在没有使用iBATIS的情况下,使用JDBC需要注意以下几点

3.1 释放JDBC资源

3.2 SQL注入

使用JDBC的PreparedStatement能有效的防止SQL注入攻击。
在使用iBATIS的时候,当使用显式SQL字符串来替换语法的语句具有这样风险,比如:

SELECT * FROM $TABLE_NAME$ WHERE $ COLUMN_NAME$ = #value#

3.3 降低复杂度

使用JDBC时那些繁琐的步骤可以通过iBATIS去除


4 配置iBATIS

iBATIS的配置概念图
iBATIS系统学习笔记二

SqlMapConfig位于最上方,我们在此文件定义那些全局配置选项以及对各个独立SqlMap文件的引用。
SqlMap文件则用于定义那些已映射语句,这些已映射语句结合应用程序提供的输入来完成数据库的交互。

4.1 SQL Map配置文件

即SqlMapConfig.xml文件,是iBATIS配置的核心。所以东西都是通过此文件中的配置提供给框架。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<properties resource="db.properties" />
<!-- 全局配置选项 -->
<settings
useStatementNamespaces="false"
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"/>

<!-- 事务管理器 -->
<transactionManager type="JDBC" >
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${driver}"/>
<property name="JDBC.ConnectionURL" value="${url}"/>
<property name="JDBC.Username" value="${user}"/>
<property name="JDBC.Password" value="${pword}"/>
</dataSource>
</transactionManager>

<!-- 对各个Sql Map文件的引用 -->
<sqlMap resource="org/apache/mapper2/ii15/SqlMap.xml" />
</sqlMapConfig>

4.2 properties元素

<properties>元素允许在主配置文件之外提供一个名/值对列表,从而使得主配置文件更加通用。
可以使用两种方式来指定所用的属性文件,每种方式对应 <properties>元素的一个属性。分别为:
1. resource — 类路径上的一个资源(或文件)。
2. url — 同意资源定位符(uniform resource locator)。

resource属性是由类加载器来读取,Java文档也会以这种方式访问数据时引用这些资源。
url属性是用java.net.URL类来处理,这个属性的取值可以使该类能理解并用于加载数据的任何有效的URL

4.3 settings元素

<settings>元素就好像是装满了配置选项的摸彩袋,可以通过为 <settings>元素添加若干属性来设置这些可选项。iBATIS有许多配置项可以用,并且每个配置项对这个SQL Map实例来说都是全局的。
1. lazyLoadingEnabled — 延迟加载,只加载必要信息而推迟加载其他未明确请求的数据的技术。lazyLoadingEnabled 配置项用于指定当存在相关联的已映射语句时,是否使用延迟加载。有效值是true或false,默认值是true。
2. cacheModelsEnabled — 数据告诉缓存是一种用于提高程序性能的技术,它基于近期使用过的数据往往很快又会被用到这样一种假设,将近期使用过的数据保存在内存中。cacheModelsEnabled 配置项用于指定是否想让iBATIS使用该技术。有效值也是ture和false并且默认值为true。为了充分利用高速缓存技术,还必须为已映射语句配置高速缓存模型。
3. enhancementEnabled — enhancementEnabled配置项用于指定是否使用cglib中那些已优化的类来提高延迟加载的性能,有效值为true或false,默认值为true。(属于增强型框架,除非确实需要,劲量避免使用。)
4. useStatementNamespaces — useStatementNamespaces 配置项用于告诉iBATIS,在引用已映射语句时,是否需要使用限定名。默认false。举例说明,如果有一个名为Account 的SQL映射,它包含名为insert、update、delete以及getAll的已映射语句,当已启用此配置项时,就必须用Account.insert这个名字来调用这条已映射语句。(这样就通过命名空间,不会造成名字冲突)
5. maxRequests、maxSessions、maxTransactions — 这三种以及被废弃,默认即可用,如果一定要进行修改,需要确保maxRequests大于maxSessions,maxSessions大于maxTransactions,并保证比例相同。另需要注意,这个3个配置项没有一个会对连接池中的连接数或应用程序服务器负责管理的那些资源产生实际影响。

4.4 typeAlias元素

元素允许定义别名:

<typeAlias alias = "Account" type = "org.apache.ibatis.jgamestor.domin.Account" />

经过配置后,可以直接使用Account这样的别名来给出iBATIS需要的数据。
iBATIS框架定义了许多别名:
1. 事物管理器别名 — JDBC, JTA, EXTERNAL
2. 数据类型 — string,byte,long,short,int,integer,double,float,boolean,date,decimal,object,map,hashmap,list,arraylist,collection,iterator
3. 数据源工厂类型 — SIMPE, DBCP, JNDI
4. 告诉缓存控制器类型 — FIFO, LRU, MEMORY, OSCACHE
5. XML结果类型 — Dom, domCollection, Xml, XmlCollection
别名可以省去大量的时间简化类名的输入。

4.5 transactionManager元素

iBATIS提供了若干内建事务管理器实现:

名字 描述
JDBC 用于提供简单的基于JDBC的事物管理,大多数情况下,使用这个事物管理器就足够了
JTA 用于在你的应用程序中提供基于容器的事物管理
EXTERNAL 用于提供非事物管理,并假定管理事物的是应用程序,而不是iBATIS

对于事务管理器,另一个可用的设置就是commitRequired属性,该属性默认为false,主要用于那些要求在释放某个连接之前必须commit或者rollback的情况。

对于某些操作(选择或者存储过程调用),通常并不需要事物,因此一般将其忽略。某些数据库驱动程序(例如Sybase)不会轻易释放连接,直至该连接的每个已启动事物都已提交或回退。在这些情况下,就可以使用commitRequired属性来强制释放连接,即使在那些通常需要在一个事物中释放却没有释放的情况。
1. <property>元素 — 每个事物管理器都可以有不同的配置项。因此iBATIS框架允许使用此标签元素来指定任意数目的名值对以提供给相应的事务管理器实现类
2. <dataSource>元素 — 在Java中,使用连接池的标准方法就是通过javax.sql.DataSource对象。事务管理器的 元素有一个类型属性,用来告诉iBATIS它应该为其数据源工厂实例化并使用哪个类。实际上元素并不是用来定义DataSource,而是用来定义DataSourceFactory实现类,然后iBATIS将用这个实现类来创建实际的DataSource。

iBATIS具有3中数据源工厂的实现类,简单描述如下:

名字 描述
SIMPLE 简单的数据源工厂,用于配置内置有简单连接池的数据源,使用iBATIS框架与JDBC实现
DBCP DBCP数据源工厂使用Jakarta Commons数据库连接池实现
JNDI JNDI数据源工厂用于允许iBATIS共享通过JNDI定位的基于容器的数据源

4.6 typeHandler元素

iBATIS使用类型处理器将数据从数据库特定的数据类型转换为应用程序中的数据类型。类型树立起本质上就是一个翻译器,它将数据库返回的结果集合中的列“翻译”为相应的Java Bean中的字段。
例如数据库没有布尔数据类型,因此在数据库往往使用单个字符Y或N来表示真假。为处理这种情况,需要创建两个类:一个自定义类型处理器和一个类型处理器回调类。
大部分情况下(原文甚至用了99%)都不需要编写自定义类型处理器,如果需要的话,可以通过元素并告诉iBATIS它需要在两个类型jdbcTypejavaType之间转换哪些元素。此外,还需要回调类来管理类型处理器。

4.7 SqlMap元素

元素也是使用resource属性或者url属性,来设置sqlMap的位置。


使用已映射语句

内容
- JavaBean
- iBATIS API
- 已映射语句
- 使用参数映射和结果映射


1 从基础开始

iBATIS并不是一个O/RM工具,他只是一种查询映射工具。

1.1 创建JavaBean

bean的构成
本质上而言,JavaBean规范就是一组规则,用于定义java开发可使用的组件。规范可以看做框架开发人员和应用开发人员的一个中间层公共语言。
iBATIS对于应用javaBean的唯一规则是对于特性命名。在JavaBean中,特性名由一对方法定义,这对方法即规范中所谓的存取方法(accessor method)比如以下value的特性创建的存取方法:

public void setValue(ValueType new value);
public ValueType getValue();

这一对存取方法定义了以小写v开头的名为value的简单特性。
boolean类型的特性允许获取方法是用isProperty,如果是包装类型Boolean则必须使用get。

特性名 获取方法 设置方法
xcoordinate/Double getXcoordinate() setXcoordinate(Double v)
xCoordinate/Double getxCoordinate() setxCoordinate(Double v)
XCoordinate/Double getXCoordinate() setXCoordinate(Double v)
Xcoordinate/Double Not allowed Not allowed
student/Boolean getStudent() setStudent(Boolean b)
student/boolean getStudent()
isStudent()
setStudent(boolean b)

已索引特性,即代表值数组的特性:

public void setOrderItem(OrderItem[] newArray);
public OrderItem[] getOrderItem();
public void setOrderItem(int index, OrderItem oi);
public OrderItem getOrderItem(int index);

显然,两个重载方法的区别不明显
这里推荐2个iBATIS的bean写法:

public void setOrderItemArray(OrderItem[] newArray);
public OrderItem[] getOrderItemArray();
public void setOrderItem(int index, OrderItem oi);
public OrderItem getOrderItem(int index);
public BeanType setProperty(PropType newValue){
this.property = newValue;
return this;
};
myBean.setProperty(x).setSomeProperty(y);

bean导航

可以通过点记法 引用特性:

Java代码 点记法
anOrder.getAccount(),getUsername() anOrder.account.username
anOrder.getOrderItem().get(0).getProductId anOrder.orderItem[0].producId
anObject.getID() anObject.ID
anObject.getxCoordinae() anObject.xCoordinate

使用Java的Introspector类来获取javabean索引特性:

 public void listPropertyNames(Class c)
throws IntrospectionException {
PropertyDescriptor[] pd;
pd = Introspector.getBeanInfo(c).getPropertyDescriptors();
for(int i=0; i< pd.length; i++) {
System.out.println(pd[i].getName()
+ " (" + pd[i].getPropertyType().getName() + ")");
}
}

>> output:
>class (java.lang.Class)
>classname (java.lang.String)
>count (java.lang.Integer)
>psd (java.lang.String)
>sgn (int)

1.2 SqlMap API

SqlMapClient 的接口具备的方法超过30个,在此简单介绍:

  1. queryForObject()
    queryForObject() 的方法是用于从数据库取出一条记录,并把它放入到一个java对象中。他具备2种签名:
 Object queryForObject(String id, Object parameter) throws SQLException;
Object queryForObject(String id, Object parameter, Object result) throws SQLException;

第二种签名接受一个将被用作返回值的对象 — 运行已映射语句之后,该方法并不会创建一个新的对象,而是会修改这个特性。如果请求的对象不能轻创建那就使用这个方式
需要注意的是,如果返回不止一行,那么这个方法就会抛出异常。

2.queryForList()
queryForList() 从数据库中获取一行或多行。

List queryForList(String id, Object parameter) throws SQLException;
List queryForList(String id, Object parameter, int skip, int max)
throws SQLException;

第二个版本返回其中一部分,他将跳到skip参数指定的位置上,并返回从查询结果开始的max行。比如有8条满足结果的数据,当skip = 5 ,max = 5时,则只会返回最后三条数据(不包括第五条)。

3.queryForMap()
queryForMap() 是从数据库中返回一行或多行组成的Java Map。

Map queryForMap(String id, Object parameter, String key) throws SQLException; Map queryForMap(String id, Object parameter, String key, String value)
throws SQLException;

第一个方法返回一组java对象,其中这些对象的键值由key参数所指定的特性来表示,值为返回对象。
第二个方法返回的对象时value参数所标识的对象的特性
这里有些难以理解,举例说明:
JavaBean:

public class UserAccount {
int userId;
String userName;
String password;
String groupName;
//Set and get
}

SqlMap:

<sqlMap>
<select id="getAll" resultClass="UserAccount">
SELECT * FROM USER_ACCOUNT
</select>
</sqlMap>

运行代码:

Map accountMap = sqlMapClient.queryForMap("getAll", null, "userId");
System.out.println(accountMap);
accountMap = sqlMapClient.queryForMap(
"getAll",
null,
"userId",
"userName");
System.out.println(accountMap);

output:
>>{1=com.alan.myvaadin.pojo.UserAccount@11ddcde, 2=com.alan.myvaadin.pojo.UserAccount@18fb1f7, 3=com.alan.myvaadin.pojo.UserAccount@ed0338, 4=com.alan.myvaadin.pojo.UserAccount@6e70c7, 5=com.alan.myvaadin.pojo.UserAccount@ae506e, 6=com.alan.myvaadin.pojo.UserAccount@228a02, 7=com.alan.myvaadin.pojo.UserAccount@192b996, 8=com.alan.myvaadin.pojo.UserAccount@1d63e39, 9=com.alan.myvaadin.pojo.UserAccount@8f4fb3, 10=com.alan.myvaadin.pojo.UserAccount@b988a6}
{1=ALAN, 2=MILU, 3=MIKE, 4=JESSIE, 5=BOOK, 6=TONY, 7=LARRY, 8=FANCY, 9=ALLEN, 10=DADY}

1.3 已映射语句的类型

已映射语句的类型和相关XML元素:

语句 属性 子元素 用途
<select> id
parameterClass
resuletClass
parameterMap
resultMap
cacheModel
所有动态元素 用于选择数据
<insert> id
parameterClass
parameterMap
所有动态元素
selectKey
用于插入数据
<update>/<delete> id
parameterClass
parameterMap
所有动态元素 用于更新/删除
<procedure> id
parameterClass
resuletClass
parameterMap
resultMap
xmlResultName
所有动态元素 用于调用存储过程
<statement> id
parameterClass
resuletClass
parameterMap
resultMap
xmlResultName
所有动态元素 可用来代表所有语句类型,
几乎可以用来执行所有的操作
<sql> id 所有动态元素 非已映射语句,可用来创建用于已映射语句的组件
<include> refid 用于将使用<sql>类型所创建的组件插入到已映射语句中。

<sql>元素和<include>元素,用来创建已映射语句的元素。联合使用这两个元素能够创建可插入到已映射语句中的组件。

<sql>元素用于创建一个文本片段,这些片段又可以组合起来以创建完整的SQL语句。例如:

<sql id="select-order">
select * from order
</sql>
<sql id="select-count">
select count(*) as value from order
</sql>
<sql id="where-shipped-after-value">
<![CDATA[
where shipDate > #value:DATE#
]]>

</sql>
<select id="getOrderShippedAfter" resultClass="map">
<include refid="select-order" />
<include refid="where-shipped-after-value" />
</select>
<select id="getOrderCountShippedAfter" resultClass="int">
<include refid="select-count" />
<include refid="where-shipped-after-value" />
</select>

2 使用select已映射语句

2.1使用内联参数(用#做占位符)

使用 # 占位符的语句会被iBATIS翻译为 = ?。
比如:

 <select id="getByUserName" resultClass="UserAccount">
SELECT
USERID,
USERNAME,
PASSWORD,
GROUPNAME
FROM
USER_ACCOUNT
WHERE
USERNAME = #userName#
</select>

这段会被iBATIS框架翻译为PrepareStatement:

        SELECT
USERID,
USERNAME,
PASSWORD,
GROUPNAME
FROM
USER_ACCOUNT
WHERE
USERNAME = ?

2.2 使用内联参数(用$做占位符)

使用该方法一定要非常小心,会造成SQL注入以及性能问题

例子:

<select id="getByLikeUserName" resultClass="UserAccount">
SELECT
USERID,
USERNAME,
PASSWORD,
GROUPNAME
FROM
USER_ACCOUNT
WHERE
USERNAME like '%$ value $%'
</select>

iBATIS会把语句转化为

  WHERE
USERNAME like '%value%'

2.3 SQL注入简介

如果在刚刚的例子里,恶意用户传入这样的语句:

 value'; drop talbe Account; --

就会将原来的select语句转化为

    SELECT
USERID,
USERNAME,
PASSWORD,
GROUPNAME
FROM
USER_ACCOUNT
WHERE
USERNAME like '%value';
drop table Account;--%'

这样就会导致Account表被删除,所以要慎用$占位符。

2.3.4 自动结果映射

单列选择(single-column selection)

    <select id="getIDs" resultClass="int">
select
USERID as value
from
USER_ACCOUNT
</select>

List list = sqlMapClient.queryForList("getIDs",null);
System.out.println(list.toString());

output:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

USERID自动映射为int对象保存在list中。

固定多列选择(fixed-column list selection)
将bean的特性名定义为列名,或者作为Map键。
当数据库的列存在,而bean中不存在,iBATIS不会发出任何警告和错误,也得不到这部分的数据。

动态多列选择(dynamic-column list selection)

<select id="getAccountRemapExample" remapResults="true" resultClass="java.util.HashMap" >
select
accountId,
username,
<dynamic>
<isEqual property="includePassword" compareValue="true" >
password,
</isEqual>
</dynamic>
firstName,
lastName
from Account
<dynamic prepend=" where ">
<isNotEmpty property="city">
city like #city#
</isNotEmpty>
<isNotNull property="accountId" prepend=" and ">
accountId = #accountId#
</isNotNull>
</dynamic>
</select>

需要了解的是,每次该动态语句执行时,确定结果映射对性能影响可能会很大,只有在确实需要时才使用该特性。

2.3.5 联结相关数据

iBATIS可以轻松的进行多表联结查询,因为它是讲SQL映射。


3 映射参数

有两种方式可以将参数映射到已映射语句中,内联映射(inline mapping)和外联映射(external mapping)

3.1 外部参数映射

外部参数映射时,(
具体来说,就是在sqlMap文件中定义parameterMap元素,然后在其中定义一条条的parameter子元素。),可以指定多达6个属性(即parameter子元素的属性)。

属性 描述
property 设定一个参数时,property用于指定JavaBean中某个特性的名字,或者指定座位参数传递给已映射语句的Map实例的某个键值对(Map entry)的键值
此名字可以不只使用一次,具体根据它它在语句中需要的次数而定。
javaType 设定一个参数时,javaType属性用来显示指定要设置的参数的java特性类型
jdbcType jdbcType属性用来显示指定参数的数据库类型。
通常,jdbcType属性只有当某列允许被设置为空才是必须的。另外,这个属性可以用来明确某些Java的模糊类型,比如Date对应DATE还是DATATIME。
nullValue nullValue属性可以根据特性被设置为任何有效的值,即将默认空值进行设置。
mode 该属性支持存储过程。
typeHandler 指定类型处理器,通常用来提供自定义的类型处理器。

这里进行概括介绍,在之后的博文中会具体介绍外联映射。

3.2内联参数映射

在上个章节简单的介绍了#$两个内联占位符。除了这些,我们也可以在内联参数映射中提供一些外部映射所允许的特性,例如:jdbcType,nullValue。只要用冒号将参数名、数据库类型和空值占位符分隔开即可。

当数据库包含允许为空的列时,而数据库类型通常是必须设置,这是因为JDBC API使用如下方式把空值发送到数据库:

public void setNull(
int parameterIndex,
int sqlType);

如果没有告诉iBATIS究竟使用哪种数据库类型,那么就会自动使用java.sql.Types.OTHER来作为第二个参数,但是一些数据库(如Oracle)不允许,则会报错。(OTHER这个整形的值为1111,如果报错出现这个信息,那么就是数据库驱动不喜欢这个空值)

如果需要内联数据库类型,这里有个示例:

<select id="getOrderShippedAfter" resultClass="java.util.HashMap">
select *
from order
where shipDate > #value:DATE#
</select>

需注意的是,因为我们使用的是XML,如果需要使用 < 时,就必须使用&lt,也可以使用CDATA。

<select id="getOrderShippedAfter" resultClass="hashmap">
select *
from "order"
where shipDate > #value,jdbcType=DATE#
</select>

这个示例与上个是等价的,

3.3 基本类型参数

基本类型只有被包装为另外的对象中才能被使用。即包装类型。

3.4 JavaBean参数和Map参数

当使用bean创建了一个参数映射 (即定义了一个parameterMap元素,其class属性取值为某个JavaBean的全限定类名),并且试图引用bean中实际上不存在的特性,那么iBATIS会立刻报错。但是如果使用Map进行参数映射,iBATIS就不会发现这个潜在错误。


4. 映射结果

iBATIS的显示结果映射能提供更好的性能、更严格的配置验证,以及更加精确的行为。(与显式参数相对于,显示结果映射就是resultMap元素和result子元素)
result子元素的属性:

属性 描述
property property属性用来指定JavaBean中某个特性的名字,或某个Map示例的键值(在class中选择bean或者map)
column 用于提供ResultSet的列的名字
columnIndex 增强型功能,用于提供列的索引以替代ResultSet的别名
jdbcType 显示指定要设置的特性的java特性类型。如果是Map或者XML无法获取Java特性类型,则会假定类型为Object。
nullValue nullValue能代替数据库的空值。
select select属性用于描述对象之间的关系,这样iBATIS就能够自动地加载复杂的特性类型。
该语句特性的值必须是另外一个已映射语句的名字,该列必须是iBATIS支持的简单数据类型。

4.1 基本类型结果

iBATIS不能获得基本类型,只能获取他们的包装类。
需要注意的是,如果返回为空,那么拆箱时就会报空指针。
如果只需要基本类型,那么需要将他包装在bean或者Map中。

4.2 JavaBean结果和Map结果

方式 优点 缺点
Bean 性能
编译时强类型
编译时名字检查
IDE中的重构支持
更少的类型转换
更多的代码
MAP 反之 反之

执行非查询语句

内容
- iBATIS API详述
- 插入数据
- 更新和删除数据
- 使用存储过程

1. 更新数据的基本方法

1.1 用于非查询SQL语句的SqlMap API

这里介绍基础的方法,即insert、update和delete这3种最常见的方法。
1. insert方法
Object insert(String id,Object parameterObject)throws SQLException;
参数为映射语句的id以及参数对象。返回Object对象

  1. update方法
    int update(String id, Object parameterObject) throws SQLException;
    返回影响记录的数目,其他和insert一样

  2. delete方法
    int delete(String id, Object parameterObject) throws SQLException

1.2 非查询已映射语句

已映射语句类型 属性 子元素 用途
<insert> id
parameterClass
parameterMap
所有动态元素
<selectKey>
插入数据
<update> id
parameterClass
parameterMap
所有动态元素 更新
<delete> id
parameterClass
parameterMap
所有动态元素 删除
<procedure> id
parameterClass
resultClass
parameterMap
resultMap
xmlResultName
所有动态元素 调用存储过程
<sql> id 所有动态元素 非映射语句,组件
<include> refid 组件

2 插入数据

2.1 使用内联参数映射

内联参数映射:

<insert id="insertAccount"> 
insert into user_account(
USERNAME,
PASSWORD,
GROUPNAME
) values(
#userName:VARCHAR#,
#password:VARCHAR#,
#groupName:VARCHAR#
)
</insert>

调用:

UserAccount userAccount = new UserAccount();
userAccount.setUserName("KOBE");
userAccount.setPassword("basketBall");
userAccount.setGroupName("BOSS");
Object obj = sqlMapClient.insert("insertAccount",userAccount);

成功插入后obj为null

2.2 外部参数映射

映射配置:

<parameterMap id="ParameterMap" class="UserAccount">
<parameter property="userName" jdbcType="VARCHAR"/>
<parameter property="password" jdbcType="VARCHAR"/>
<parameter property="groupName" jdbcType="VARCHAR"/>
</parameterMap>

<!-- 使用外部映射insert -->
<insert id="insertWithExternal" parameterMap="ParameterMap">
insert into user_account(
userName,
password,
groupName
) values(
?,?,?
)
</insert>

可见代码比内联精简不少(当有很多语句时),外部参数映射能提高维护的代价以及性能。

2.3 自动生成的键

如果将数据库设计为使用自动生成的主键,就可以用iBATIS的<selectKey>元素来获取自动生成的主键的值并保存在对象中。
有两种获取方式,在插入后获取主键信息(非线程安全),在插入前就保存主键信息。
insert方法返回一个Object对象的原因是,使你能够得到所生成的键值。
第一种方式(该SQL MYSQL不适用):

<insert id="insertWithExternal" parameterMap="ParameterMap">
<selectKey keyProperty="userId" resultClass="int">
<!-- SELECT nextVal('user_account_id_seq') -->
SELECT LAST_INSERT_ID() <!--适用于mysql -->
</selectKey>
insert into user_account(
userName,
password,
groupName
) values(
?,?,?
)
</insert>

2.3.1 处理并发更新

IBATIS目前还没有实现这个功能、可自己使用时间戳,或者版本号来处理并发更新。

2.3.2 更新或删除子记录

如果一个对象包含着另外一个对象作为自己的组件(子对象)。因为iBATIS并不是O/RM框架,从根本上讲只是个SQL映射工具,所以更新时并不管理对象间的这种关系,所以必须在应用程序的数据层处理对象关系,比如:

public void saveOrder(SqlMapClient sqlMapClient, Order order) throws SQLException {
if (null == order.getOrderId()) {
sqlMapClient.insert("Order.insert", order);
} else {
sqlMapClient.update("Order.update", order);
}
sqlMapClient.delete("Order.deleteDetails", order);
for(int i=0;i<order.getOrderItems().size();i++) {
OrderItem oi = (OrderItem) order.getOrderItems().get(i);
oi.setOrderId(order.getOrderId());
sqlMapClient.insert("OrderItem.insert", oi);
}
}

orderItem是Order的子对象,通过这种方式可以完成业务,但是没有任何的事务管理,在下面给出解决方法。

2.4 运行批量更新

批量更新是提高IBATIS性能的一种方法。通过创建一批语句,数据库驱动程序可以以一种压缩方式执行更新任务以提高性能。

需要注意的是,要把批处理语句打包为一个事物,如果没有这么做,每条语句都会开始一个新的事务,性能会受到严重影响。
例如2.3.2的例子,

public void saveOrder(SqlMapClient sqlMapClient, Order order) throws SQLException {
sqlMapClient.startTransaction();
try {
if (null == order.getOrderId()) {
sqlMapClient.insert("Order.insert", order);
} else {
sqlMapClient.update("Order.update", order);
}
sqlMapClient.startBatch();
sqlMapClient.delete("Order.deleteDetails", order);
for (int i=0;i<order.getOrderItems().size();i++) {
OrderItem oi = (OrderItem) order.getOrderItems().get(i);
oi.setOrderId(order.getOrderId());
sqlMapClient.insert("OrderItem.insert", oi);
}
sqlMapClient.executeBatch();
sqlMapClient.commitTransaction();
} finally {
sqlMapClient.endTransaction();
}
}

这个例子也用到了批量执行 startBatch()方法和executeBatch()方法。
需要注意的是,上述的例子把父记录操作完成后才开始批处理。因为当使用一批批处理语句时,只有当调用了executeBatch()方法时,数据库的键才会生成。

同时还需要记住的是,批处理语句中只有当PrepareStatment对象和以前的已映射的语句完全一致时,才复用这些PreparedStatement对象。

2.5 使用存储过程

2.5.1 INOUTINOUT参数

IN
简单存储过程,接受两个IN参数返回一个值:

CREATE OR REPLACE FUNCTION max_in_example
(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;

映射后的Java代码:

<parameterMap id="pm_in_example" class="java.util.Map">
<parameter property="a" />
<parameter property="b" />
</parameterMap>

<procedure id="in_example" parameterMap="pm_in_example" resultClass="int" >
{ call max_in_example(?, ?) }
</procedure>

// Call a max function
Map m = new HashMap(2);
m.put("a", new Integer(7));
m.put("b", new Integer(5));
Integer val = (Integer)sqlMap.queryForObject("Account.in_example", m);

INOUT
INOUT参数是指那些被传递给一个存储过程并且可以被其修改的参数。

存储过程:

create procedure swap(a in out integer, b in out integer) as
temp integer;

begin
temp := a;

a := b;
b := temp;
end;

映射代码:

<parameterMap id="swapProcedureMap" class="java.util.Map">
<parameter property="a" mode="INOUT" />
<parameter property="b" mode="INOUT" />
</parameterMap>
<procedure id="swapProcedure" parameterMap="swapProcedureMap">
{ call swap(?, ?) }
</procedure>

// Call swap function
Map m = new HashMap(2);
m.put("a", new Integer(7));
m.put("b", new Integer(5));
Integer val = (Integer) sqlMap.queryForObject("Account.in_example", m);

OUT
OUT参数和返回结果很相似,但又可以像参数那样传递给存储过程。
被传递给存储过程的值将被忽略,然后被存储程序所返回的值所替代。
OUT参数可以返回任何数据。

2个IN参数和1个OUT参数的存储过程:

create or replace procedure maximum
(a in integer, b in integer, c out integer) as
begin
if (a > b) then c :
= a; end if;
if (b >= a) then c := b; end if;
end;

映射语句:

<parameterMap id="maxOutProcedureMap" class="java.util.Map">
<parameter property="a" mode="IN" />
<parameter property="b" mode="IN" />
<parameter property="c" mode="OUT" />
</parameterMap>
<procedure id="maxOutProcedure" parameterMap="maxOutProcedureMap">
{ call maximum (?, ?, ?) }
</procedure>

// Call maximum function
Map m = new HashMap(2);
m.put("a", new Integer(7));
m.put("b", new Integer(5));
sqlMap.queryForObject("Account.maxOutProcedure", m);
// m.get("c") should be 7 now.

使用高级查询技术

内容
- 使用XML
- 声明关系
- N+1问题解决方案


1. 在iBATIS中使用XML

有时候需要使用XML的数据,iBATIS框架允许使用XML为一个数据库查询传递参数,也可以用XML从查询中返回结果。
在接下来的版本中,这个特性可能会被删除,这里只简单了解。

1.1 XML参数

准备:

<parameter><accountId>3</accountId></parameter> 

映射:

<select id="getByXmlId" resultClass="Account" parameterClass="xml">
select
accountId,
username,
password,
postalCode,
country
from Account
where accountId = #accountId#
</select>

使用:

    String parameter = "<parameter><accountId>3</accountId></parameter>";
Account account = (Account) sqlMapClient.queryForObject("Account.getByXmlId",parameter);

也可以使用DOM对象来向iBATIS传递参数

Document parameterDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element paramElement = parameterDocument.createElement("parameterDocument");
Element accountIdElement = parameterDocument.createElement("accountId");
accountIdElement.setTextContent("3");
paramElement.appendChild(accountIdElement);
parameterDocument.appendChild(paramElement);
Account account = (Account) sqlMapClient.queryForObject("Account.getByXmlId", parameterDocument);

1.2 XML结果

iBATIS框架允许从已映射语句中创建XML格式的结果。当执行一条返回XML的已映射语句时,iBATIS会为每个返回对象返回一份完整的XML文档。

映射:

<select id="getByIdValueXml" resultClass="xml"
xmlResultName="account">
select
accountId,
username,
password
from Account
where accountId = #value#
</select>

String xmlData = (String) sqlMap.queryForObject("Account.getByIdValueXml",new Integer(1));

返回xml:

    <?xml version="1.0" encoding="UTF-8"?>
<account>
<accountid>1</accountid>
<username>lmeadors</username>
<password>blah</password>
</account>

如果获取的记录只有一条,那么这个方法是非常方便的,但如果是多个对象,就没那么简单了。
如果是多个对象,那返回的将是多个XML文档的字符串,而不是一个结果。如果想要只想要一个XML文档包含所有结果,那就必须自己手工拼接。

这里有个解决方案是,先获取JavaBean,然后自行将JavaBean转换成XML。
工具类:

public String toXml(){
StringBuffer returnValue = new StringBuffer("");
returnValue.append("<account>");
returnValue.append("<accountid>" + getAccountId() +"</accountid>");
returnValue.append("<username>" + getUsername() + "</username>");
returnValue.append("<password>" + getPassword() + "</password>");
returnValue.append("</account>");
return returnValue.toString();
}

也可以通过反射来将bean转换成xml

public class XmlReflector {
private Class sourceClass;
private BeanInfo beanInfo;
private String name;

XmlReflector(Class sourceClass, String name) throws Exception {
this.sourceClass = sourceClass;
this.name = name;
beanInfo = Introspector.getBeanInfo(sourceClass);
}

public String convertToXml(Object o) throws Exception {
StringBuffer returnValue = new StringBuffer("");
if (o.getClass().isAssignableFrom(sourceClass)) {
PropertyDescriptor[] pd = beanInfo.getPropertyDescriptors();
if (pd.length > 0){
returnValue.append("<" + name + ">");
for (int i = 0; i < pd.length; i++) {
returnValue.append(getProp(o, pd[i]));}
returnValue.append("</" + name + ">");}
else {
returnValue.append("<" + name + "/>");}
}
else {
throw new ClassCastException("Class " + o.getClass().getName() + " is not compatible with " + sourceClass.getName());
}
return returnValue.toString();
}

private String getProp(Object o, PropertyDescriptor pd) throws Exception {
StringBuffer propValue = new StringBuffer("");
Method m = pd.getReadMethod();
Object ret = m.invoke(o);
if(null == ret){
propValue.append("<" + pd.getName() + "/>");
}else{
propValue.append("<" + pd.getName() + ">");
propValue.append(ret.toString());
propValue.append("</" + pd.getName() + ">");
}
return propValue.toString();
}
}

2 用已映射语句的关联对象

2.1 复杂集合

即多个关联:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Ch7">
<resultMap id="ResultAccountInfoMap" class="org.apache.mapper2.examples.bean.AccountInfo">
<result property="account.accountId" column="accountId" />
<result property="orderList" select="Ch6.getOrderInfoList" column="accountId" />
</resultMap>

<resultMap id="ResultOrderInfoMap" class="org.apache.mapper2.examples.bean.OrderInfo">
<result property="order.orderId" column="orderId" />
<result property="orderItemList" column="orderId" select="Ch6.getOrderItemList" />
</resultMap>

<resultMap id="ResultOrderItemMap" class="org.apache.mapper2.examples.bean.OrderItem">
<result property="orderId" column="orderId" />
<result property="orderItemId" column="orderItemId" />
</resultMap>

<select id="getAccountInfoList" resultMap="ResultAccountInfoMap" >
select accountId
from Account
</select>

<select id="getOrderInfoList" resultMap="ResultOrderInfoMap">
select orderId
from orders
where accountId = #value#
</select>

<select id="getOrderItemList" resultMap="ResultOrderItemMap">
select
orderId,
orderItemId
from orderItem
where orderid = #value#
</select>
</sqlMap>

注意resultMap中的select选项,通过这个选项的配置可以达到关联查询的效果。
但是,通过这种方式会出现2个问题,一是会消耗大量内存。二是由于“N+1查询”问题会导致数据库I/O问题。
1. 数据库I/O
数据库I/O可以很好地度量你的数据库的使用情况,同时也是影响数据库性能的主要原因之一。当从数据库读写数据时,数据必须从磁盘与内存之间互相转移,这种操作非常耗时。

  1. N+1查询问题
    N+1查询问题是由于试图加载和父记录列表相关的子记录而造成的。如果执行了一个查询语句并获取N条父记录,那么为了获得所有这些父记录的子记录,那么就必须再执行N个查询,即”N+1查询”。

  2. 这些问题的解决方案
    延迟加载可以通过将加载过程分割成更小也更易管理的小过程,来减轻内存的负担。但是这并没有解决数据库I/O问题,下面这个章节来详述这个问题的解决方法。

2.2 延迟加载

想要使用延迟加载,需要在sqlmap-config中设置:

    <settings lazyLoadingEnabled="true" />

如果使用cglib增强版,就需要下载其插件,并在setting中开启enhancementEnabled。
这是一个全局设置,如果启用了这些特性,SQL映射中所有的已映射语句都将会使用延迟加载。

2.3 避免N+1查询问题

有两种方式避免这个问题,一种方式是通过使用iBATIS提供的groupBy属性,另一种是通过使用一个称为RowHandler的自定义组件。

groupBy和延迟加载技术实现起来非常接近,但是由于使用了联结查询(join query),在数据库服务器上只执行一条SQL语句。使用groupBy的例子:

<resultMap id="ResultAccountInfoNMap" class="AccountInfo" groupBy="account.accountId" >
<result property="account.accountId" column="accountId" />
<result property="orderList" resultMap="Ch6.ResultOrderInfoNMap" />
</resultMap>
<resultMap id="ResultOrderInfoNMap" class="OrderInfo" groupBy="order.orderId" >
<result property="order.orderId" column="orderId" />
<result property="orderItemList" resultMap="Ch6.ResultOrderItemNMap" />
</resultMap>
<resultMap id="ResultOrderItemNMap" class="OrderItem">
<result property="orderId" column="orderId" />
<result property="orderItemId" column="orderItemId" />
</resultMap>

<select id="getAccountInfoListN" resultMap="ResultAccountInfoNMap">
select
account.accountId as accountid,
orders.orderid as orderid,
orderitem.orderitemid as orderitemid
from account
join orders on account.accountId = orders.accountId
join orderitem on orders.orderId = orderitem.orderId
order by accountId, orderid, orderitemid
</select>

使用这个方法在速度上会有提升,因为数据库只执行一条SQL语句,但是内存的消耗以及是一样的。这里比较下延迟加载和groupBy两个解决方案:

延迟加载 N+1查询方案
此方式适用于获取那些虽然较为大型但并非每条记录都会被用到的数据集 此方式适用于小型数据集或者所以数据肯定会被使用的数据集
前期性能可以得到提高,但是之后可能会为之付出一定的代价 整体性能得到提高

3 继承

继承是面向对象编程中的一个基本概念,有以下意义:
代码复用、功能的增强和特殊化、公共接口。

映射继承

iBATIS 通过使用一个特殊的被称为鉴别器(discriminator)的结果映射来支持继承体系。

<resultMap id="document" class="testdomain.Document">
<result property="id" column="DOCUMENT_ID"/>
<result property="title" column="TITLE"/>
<result property="type" column="TYPE"/>
<discriminator column="TYPE" javaType="string" >
<subMap value="Book" resultMap="book"/>
<subMap value="Newspaper" resultMap="news"/>
</discriminator>
</resultMap>

如果TYPE取值为Book,那么就使用book作为结果映射。如果TYPE取值为Newspaper,就使用news作为结果映射,在这种情况下父映射都不会被应用,除非以下情况。

<resultMap id="book" class="testdomain.Book" extends="document">
<result property="pages" column="DOCUMENT_PAGENUMBER"/>
</resultMap>

4 其他用途

当无法使用其他类型的已映射语句时,可以使用<statement>。

4.1 使用语句类型和DDL

PostgreSQL数据库允许使用statement语句创建和删除数据。

<statement id="dropTable">
DROP TABLE Account CASCADE;
</statement>

sqlMap.update("Account.dropTable", null);

4.2 处理超大型数据集

RowHandler接口用来处理大型数据集的问题。

RowHandler接口是一个非常简单的接口,它允许你在某个已映射语句的结果集的处理中插入自己的动作。

public interface RowHandler{
void handleRow(Object valueObject);
}

对于已映射语句的返回结果集中的每一条记录,iBATIS都会调用一次handleRow方法。

示例:

public class AccountXmlRowHandler implements RowHandler {
private StringBuffer xmlDocument = new StringBuffer("<AccountList>");
private String returnValue = null;
public void handleRow(Object valueObject) {
Account account = (Account) valueObject;
xmlDocument.append("<account>");
xmlDocument.append("<accountId>");
xmlDocument.append(account.getAccountId());
xmlDocument.append("</accountId>");
xmlDocument.append("<username>");
xmlDocument.append(account.getUsername());
xmlDocument.append("</username>");
xmlDocument.append("<password>");
xmlDocument.append(account.getPassword());
xmlDocument.append("</password>");
xmlDocument.append("</account>");
}

public String getAccountListXml(){
if (null == returnValue){
xmlDocument.append("</AccountList>");
returnValue = xmlDocument.toString();
}
return returnValue;
}
}

调用:

AccountXmlRowHandler rh = new AccountXmlRowHandler();
sqlMapClient.queryWithRowHandler("Account.getAll", null, rh);
String xmlData = rh.getAccountListXml();

事物

内容
- 事务介绍
- 自动事务、局部事务和全局事务
- 自定义事务
- 事务划界

1 事务是什么

事务就是一项通常包含若干步骤的工作单元,这些步骤必须作为整体来执行,无论成功还是失败。如果一个事务中的任何一个步骤失败了,那么所有其他步骤就必须回滚,以保证数据仍处于一致的状态。

iBATIS支持的4种范围的事务:
- 自动事务
- 局部事务
- 全局事务
- 定制事务

1.1 理解事务的特性

支持事务的数据库需要具备的特性通常被称为ACID,即原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

原子性 用于保证事务中的所有步骤作为一个整体,或者全部成功,或者全部失败。

一致性 好的数据库模式往往会定义很多约束以保证数据完整性和数据一致性。事务在开始前和结束后,数据库都处于一致状态 — 是指所有的约束都得到满足的状态。

隔离性 数据库需要预防多个用户同时使用共享资源。数据库支持不同的隔离级别。隔离级别往往需要与性能作权衡。
- 读未提交的数据(read uncommitted):最低的隔离级别,在此隔离级别下,可以随意地从数据库中读取数据,即使它是一个未完成事务的结果。
- 读已提交数据(read committed):这个级别的隔离能够保证不返回未提交的数据。但这个级别不能保证一个事务在开始时和结束时读取的数据一定是相同的。
- 可重复性 (repeatable read):这个级别除了保证只读取已提交的数据外,还会获取数据库表中被请求记录的只读锁以保证它们在当前事务提交前不会被其他用户修改。
- 串行化 (serializable):*别隔离,本质上让所有事务串行执行,不可能造成冲突但是性能损失巨大。

持久性 当事务完成后保证数据安全。

2 自动事务

iBATIS自动将API调用所涉及的SQL语句划定为一个事务。

3 局部事务

局部事务在iBATIS的SQL Map配置文件(XML格式)中被配置为一个JDBC类型的事务管理器。

<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property …/>
<property …/>
<property …/>
</dataSource>
</transactionManager>
public void runStatementsUsingLocalTransactions() {
SqlMapClient sqlMapClient = SqlMapClientConfig.getSqlMapClient();
try {
sqlMapClient.startTransaction();
Person p = (Person)sqlMapClient.queryForObject("getPerson", new Integer(9));
p.setLastName("Smith");
sqlMapClient.update("updatePerson", p);
Department d = (Department)sqlMapClient.queryForObject("getDept", new Integer(3));
p.setDepartment(d);
sqlMapClient.update("updatePersonDept", p);
sqlMapClient.commitTransaction();
} finally {
sqlMapClient.endTransaction();
}
}

4 全局事务

全局事务定义了一个比局部事务所定义的事务范围大得多的事务范围。它可以包括其他的数据库,包括消息队列,甚至包括其他的应用程序。

4.1 使用主动或被动事务

iBATIS用两种方式之一来参与全局事务:主动参与或被动参与。

当配置为主动参与时,iBATIS会查找全局事务上下文,然后试图以某种恰当的方式管理它。

当配置为被动参与一个全局事务时,iBATIS会忽略当前应用程序中所有关于开始事务、提交事务以及结束事务的指令代码。

主动参与:

<transactionManager type="JTA">
<property name="UserTransaction" value="java:/ctx/con/someUserTransaction"/>

<dataSource type="JNDI">
<property name="DataSource" value="java:comp/env/jdbc/someDataSource"/>
</dataSource>
</transactionManager>

被动参与:

<transactionManager type="EXTERNAL">
<dataSource type="JNDI">
<property name="DataSource" value="java:comp/env/jdbc/someDataSource"/>
</dataSource>
</transactionManager>

4.2 开始、提交以及结束事务

使用方法和局部事务没有区别,start,commit和end。

4.3 是否需要全局事务

大多数情况下,使用全局事务给应用程序带来更多的额外负担。全局事务往往是分布的,这就需要比局部事务更多的网络带宽和状态管理。


5 定制事务

第一种方式,iBATIS可以通过实现某些接口,自己写一个事务管理器,然后把它插入到SQL Map配置文件中。
第二种方式由iBATIS传递一个要使用JDBC Connection实例,从而循序对连接和事务的完全控制权。

获取con来实现事务控制:

public void runStatementsUsingSetUserConnection() {
SqlMapClient sqlMapClient = SqlMapClientConfig.getSqlMapClient();
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
sqlMapClient.setUserConnection(conn);
Person p = (Person)sqlMapClient.queryForObject("getPerson", new Integer(9));
p.setLastName("Smith");
sqlMapClient.update("updatePerson", p);
Department d = (Department)sqlMapClient.queryForObject("getDept", new Integer(3));
p.setDepartment(d);
sqlMapClient.update("updatePersonDept", p);
conn.commit();
} finally {
sqlMapClient.setUserConnection(null);
if (conn != null) conn.close();
}
}

使用SqlMapClient类的openSession(Connection)方法:

public void runStatementsUsingSetUserConnection() {
SqlMapClient sqlMapClient = SqlMapClientConfig.getSqlMapClient();
Connection conn = null;
SqlMapSession session = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
session = sqlMapClient.openSession(conn);
Person p = (Person)session.queryForObject("getPerson",new Integer(9));
p.setLastName("Smith");
session.update("updatePerson", p);
Department d = (Department)session.queryForObject("getDept", new Integer(3));
p.setDepartment(d);
session.update("updatePersonDept", p);
conn.commit();
} finally {
if (session != null) session.close();
if (conn != null) conn.close();
}
}

6 事务划界

事务的范围越宽越好,但绝不能超过一次用户动作的范围。

事务应在业务逻辑层划界。

使用动态SQL

内容
- 动态SQL介绍
- 动态SQL简单示例
- 高级动态SQL
- 未来的方向

1 处理动态WHERE子句条件

例子:

<select id="getChildCategories" parameterClass="Category" resultClass="Category">
SELECT *
FROM category
<dynamic prepend="WHERE ">
<isNull property="parentCategoryId">
parentCategoryId IS NULL
</isNull>
<isNotNull property="parentCategoryId">
parentCategoryId=#parentCategoryId#
</isNotNull>
</dynamic>
</select>

动态SQL的好处之一就是可以提高SQL代码的可复用性。

2 熟悉动态标签

动态标签可分为5类:dynamic标签、二元标签、一元标签、参数标签以及iterate标签。
所有的标签都有prepend、open和close这3个熟悉。open和close属性在每个标签中的功能是一样的。它们无条件地将其属性值放在标签的结果内容的开始处或者结束处。除了在dynamic标签外,prepend的熟悉也是一样的。

<dynamic prepend="WHERE ">
<isNotEmpty property="y">
y=#y#
</isNotEmpty>

<isNotNull property="x" removeFirstPrepend="true" prepend="AND" open="(" close=")">
<isNotEmpty property="x.a" prepend="OR">
a=#x.a#
</isNotEmpty>

<isNotEmpty property="x.b" prepend="OR">
a=#x.b#
</isNotEmpty>

<isNotEmpty property="x.c" prepend="OR">
a=#x.c#
</isNotEmpty>
</isNotNull>
</dynamic>

2.1 dynamic标签

顶层标签,该标签提供prepend、Open和close属性

<select id="getChildCategories" parameterClass="Category" resultClass="Category">
SELECT *
FROM category
<dynamic prepend="WHERE ">
<isNull property="parentCategoryId">
parentCategoryId IS NULL
</isNull>
<isNotNull property="parentCategoryId">
parentCategoryId=#parentCategoryId#
</isNotNull>
</dynamic>
</select>

2.2 二元标签

用于将参数特性的某个值同另外一个值或者参数特性作比较。

<select id="getShippingType" parameterClass="Cart" resultClass="Shipping">
SELECT * FROM Shipping
<dynamic prepend="WHERE ">
<isGreaterEqual property="weight" compareValue="100">
shippingType='FREIGHT'
</isEqual>
<isLessThan property="weight" compareValue="100">
shippingType='STANDARD'
</isLessThan>
</dynamic>
</select>

二元标签属性:property*,prepend,Open,close,removeFirstPrepend,(compareProperty,compareValue)*
动态标签:< isEqual>,<isNotEqual>,<isGreaterThan>,<isGreaterEqual>,<isLessThan>,<isLessEqual>

2.3 一元标签

一元标签用来直接考察参数对象中某个bean特性的状态,不用与其他值进行比较。

<select id="getProducts" parameterClass="Product" resultClass="Product">
SELECT * FROM Products
<dynamic prepend="WHERE ">
<isNotEmpty property="productType">
productType=#productType#
</isNotEmpty>
</dynamic>
</select>

属性:property*,prepend,Open,close,removeFirstPrepend
标签:<isPropertyAvailable>,<isNotPropertyAvailable>,<isNull>,<isNotNull>,<isEmpty>,<isNotEmpty>

2.4 参数标签

参数标签用来检查某个特定参数是否被传递给了已映射语句

<select id="getProducts" resultClass="Product">
SELECT * FROM Products
<isParameterPresent prepend="WHERE ">
<isNotEmpty property="productType">
productType=#productType#
</isNotEmpty>
</ isParameterPresent >
</select>

参数:prepend、Open、close、removeFirstPrepend
参数标签:<iParameterPresent>,<isNotParameterPresent>

2.5 iterate标签

iterate标签已一个集合或数组类型的特性作为其property属性值。iBATIS通过遍历这个集合来从一组值中重复产生某种SQL小片段。

<select id="getProducts" parameterClass="Product" resultClass="Product">
SELECT * FROM Products
<dynamic prepend="WHERE productType IN ">
<iterate property="productTypes" open="(" close=")" conjunction=",">
productType=#productType#
</iterate>
</dynamic>
</select>

属性:property*,prepend,Open,close,conjunction,removeFirstPrepend。

3 完整例子:

静态模型:

SELECT
PRODUCTID,
NAME,
DESCRIPTION,
IMAGE,
CATEGORYID
FROM
PRODUCT
WHERE
lower(name) like 'adventure%' OR
lower(categoryid) like 'adventure%' OR
lower(description) like 'adventure%' OR
lower(name) like 'Deus%' OR
lower(categoryid) like 'deus%' OR
lower(description) like 'deus%'

动态改造:

<select id="searchProductList" resultClass="product" >
SELECT
PRODUCTID,
NAME,
DESCRIPTION,
IMAGE,
CATEGORYID
FROM
PRODUCT
<dynamic prepend="WHERE">
<iterate property="keywordList" conjunction="OR">
lower(name) like lower(#keywordList[]#) OR
lower(categoryid) like lower(#keywordList[]#) OR
lower(description) like lower(#keywordList[]#)
</iterate>
</dynamic>
</select>

4 高级动态SQL技术

这里举一个例子:
搜索参数类:

public class ProductSearchCriteria {
private String[] categoryIds;
private String productName;
private String productDescription;
private String itemName;
private String itemDescription;

// setters and getters
}

静态SQL模型:

SELECT
p.PRODUCTID AS PRODUCTID,
p.NAME AS NAME,
p.DESCRIPTION AS DESCRIPTION,
p.IMAGE AS IMAGE,
p.CATEGORYID AS CATEGORYID
FROM Product p
INNER JOIN Category c ON
c.categoryId=p.categoryId
INNER JOIN Item i ON
i.productId = p.productId
WHERE
c.categoryId IN ('ACTADV')
AND
p.name LIKE '007'
AND
p.description LIKE '007'
AND
i.name LIKE 'PS2'
AND
i.description LIKE 'PS2'

动态sql:

<select id="searchProductsWithProductSearch" parameterClass="productSearch" resultClass="product" >
SELECT DISTINCT
p.PRODUCTID,
p.NAME,
p.DESCRIPTION,
p.IMAGE,
p.CATEGORYID
FROM Product p
<isEqual property="itemProperties" compareValue="true">
INNER JOIN Item i ON i.productId=p.productId
</isEqual>
<dynamic prepend="WHERE">
<iterate property="categoryIds" open="p.categoryId IN (" close=")" conjunction="," prepend="BOGUS">
#categoryIds[]#
</iterate>
<isNotEmpty property="productName" prepend="AND">
p.name LIKE #productName#
</isNotEmpty>
<isNotEmpty property="productDescription" prepend="AND">
p.description LIKE #productDescription#
</isNotEmpty>
<isNotEmpty property="itemName" prepend="AND">
i.name LIKE #itemName#
</isNotEmpty>
<isNotEmpty property="itemDescription" prepend="AND">
i.description LIKE #itemDescription#
</isNotEmpty>
</dynamic>
</select>

iBATIS的动态SQL是集成库中的一个功能强大的工具。