分类: Ibatis 2007-11-07 10:56 15894人阅读 评论(11) 收藏 举报
ibatis 开发指南
ibatis Quick Start......................................... 5
准备工作............................................... 5
构建ibatis 基础代码...................................... 5
ibatis 配置.............................................. 11
ibatis 基础语义............................................ 16
XmlSqlMapClientBuilder....................................... 16
SqlMapClient .............................................. 16
SqlMapClient 基本操作示例................................. 16
OR 映射................................................ 19
ibatis 高级特性...................................... 26
数据关联.......................................... 26
一对多关联....................................... 26
一对一关联...................................... 28
延迟加载.................................... 30
动态映射..................................... 31
事务管理........................................... 35
基于JDBC 的事务管理机制.................................... 35
基于JTA 的事务管理机制................................ 36
外部事务管理............................................ 38
Cache .......................................... 39
MEMORY 类型Cache 与WeakReference .................. 40
LRU 型Cache ............................................... 42
FIFO 型Cache ....................................... 43
OSCache.............................................. 43
ibatis 开发指南
相对Hibernate 和Apache OJB 等“一站式”ORM 解决方案而言,ibatis 是一种“半
自动化”的ORM 实现。
所谓“半自动”,可能理解上有点生涩。纵观目前主流的ORM ,无论Hibernate 还是
Apache OJB,都对数据库结构提供了较为完整的封装,提供了从POJO 到数据库表的全
套映射机制。程序员往往只需定义好了POJO 到数据库表的映射关系,即可通过Hibernate
或者OJB 提供的方法完成持久层操作。程序员甚至不需要对SQL 的熟练掌握,
Hibernate/OJB 会根据制定的存储逻辑,自动生成对应的SQL 并调用JDBC 接口加以执
行。
大多数情况下( 特别是对新项目,新系统的开发而言)
,这样的机制无往不利,大有一
统天下的势头。但是,在一些特定的环境下,这种一站式的解决方案却未必灵光。
在笔者的系统咨询工作过程中,常常遇到以下情况:
1. 系统的部分或全部数据来自现有数据库,处于安全考虑,只对开发团队提供几
条Select SQL(或存储过程)以获取所需数据,具体的表结构不予公开。
2. 开发规范中要求, 所有牵涉到业务逻辑部分的数据库操作,必须在数据库层由
存储过程实现(就笔者工作所面向的金融行业而言,工商银行、中国银行、交
通银行,都在开发规范中严格指定)
3. 系统数据处理量巨大,性能要求极为苛刻,这往往意味着我们必须通过经过高
度优化的SQL 语句(或存储过程)才能达到系统性能设计指标。
面对这样的需求,再次举起Hibernate 大刀,却发现刀锋不再锐利,甚至无法使用,
奈何?恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用JDBC
进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作
令人厌烦。
“半自动化”的ibatis,却刚好解决了这个问题。
这里的“半自动化”,是相对Hibernate 等提供了全面的数据库封装机制的“全自动化”
ORM 实现而言,“全自动”ORM 实现了POJO 和数据库表之间的映射,以及SQL 的自动
生成和执行。而ibatis 的着力点,则在于POJO 与SQL 之间的映射关系。也就是说,ibatis
并不会为程序员在运行期自动生成SQL 执行。具体的SQL 需要程序员编写,然后通过映
射配置文件,将SQL 所需的参数,以及返回的结果字段映射到指定POJO 。
使用ibatis 提供的ORM 机制,对业务逻辑实现人员而言,面对的是纯粹的Java 对象,
这一层与通过Hibernate 实现ORM 而言基本一致,而对于具体的数据操作,Hibernate
会自动生成SQL 语句,而ibatis 则要求开发者编写具体的SQL 语句。相对Hibernate 等
“全自动”ORM 机制而言,ibatis 以SQL 开发的工作量和数据库移植性上的让步,为系统
设计提供了更大的自由空间。作为“全自动”ORM 实现的一种有益补充,ibatis 的出现显
得别具意义。
ibatis Quick Start
准备工作
1. 下载ibatis 软件包(http://www.ibatis.com)。
2. 创建测试数据库,并在数据库中创建一个t_user 表,其中包含三个字段:
. id(int)
. name(varchar)
. sex(int) 。
3. 为了在开发过程更加直观,我们需要将ibatis 日志打开以便观察ibatis 运作的细节。
ibatis 采用Apache common_logging,并结合Apache log4j 作为日志输出组件。在
CLASSPATH 中新建log4j.properties 配置文件,内容如下:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1} -%m%n
log4j.logger.java.sql.PreparedStatement=DEBUG
构建ibatis 基础代码
ibatis 基础代码包括:
1. ibatis 实例配置
一个典型的配置文件如下(具体配置项目的含义见后):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
errorTracingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="false"
/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/>
</sqlMapConfig>
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
2. POJO(Plain Ordinary Java Object)
下面是我们用作示例的一个POJO:
public class User implements Serializable {
private Integer id;
private String name;
private Integer sex;
private Set addresses = new HashSet();
/** default constructor */
public User() {
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSex() {
return this.sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
}
3. 映射文件
与Hibernate 不同。因为需要人工编写SQL 代码,ibatis 的映射文件一般采
用手动编写(通过Copy/Paste,手工编写映射文件也并没想象中的麻烦)。
针对上面POJO 的映射代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
<![CDATA[
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
]]>
</update>
<insert id="insertUser"
parameterClass="user"
>
INSERT INTO t_user (
name,
sex)
VALUES (
#name#,
#sex#
)
</insert>
<delete id="deleteUser"
parameterClass="java.lang.String">
delete from t_user
where id = #value#
</delete>
</sqlMap>
从上面的映射文件可以看出,通过<insert>、<delete>、<update>、
<select>四个节点,我们分别定义了针对TUser 对象的增删改查操作。在这
四个节点中,我们指定了对应的SQL 语句,以update 节点为例:
……
<update id="updateUser" ⑴
parameterClass="user"> ⑵
<![CDATA[ ⑶
UPDATE t_user ⑷
SET (
IBATIS Developer’s Guide Version 1.0
name=#name#, ⑸
sex=#sex# ⑹
)
WHERE id = #id# ⑺
]]>
</update>
……
⑴ ID
指定了操作ID,之后我们可以在代码中通过指定操作id 来执行此节点所定
义的操作,如:
sqlMap.update("updateUser",user);
ID 设定使得在一个配置文件中定义两个同名节点成为可能(两个update 节
点,以不同id 区分)
⑵ parameterClass
指定了操作所需的参数类型,此例中update 操作以
com.ibatis.sample.User 类型的对象作为参数,目标是将提供的User
实例更新到数据库。
parameterClass="user"中,user 为“com.ibatis.sample.User”
类的别名,别名可通过typeAlias 节点指定,如示例配置文件中的:
<typeAlias alias="user" type="com.ibatis.sample.User"/>
⑶ <![CDATA[……]]>
通过<![CDATA[……]]>节点,可以避免SQL 中与XML 规范相冲突的字符对
XML 映射文件的合法性造成影响。
⑷ 执行更新操作的SQL,这里的SQL 即实际数据库支持的SQL 语句, 将由
ibatis 填入参数后交给数据库执行。
⑸ SQL 中所需的用户名参数,
“#name#”在运行期会由传入的user 对象的name
属性填充。
⑹ SQL 中所需的用户性别参数“#sex#”, 将在运行期由传入的user 对象的
sex 属性填充。
⑺ SQL 中所需的条件参数“#id#”, 将在运行期由传入的user 对象的id 属性
填充。
对于这个示例,ibatis 在运行期会读取id 为“updateUser”的update 节点
的SQL 定义,并调用指定的user 对象的对应getter 方法获取属性值,并用此
属性值,对SQL 中的参数进行填充后提交数据库执行。
此例对应的应用级代码如下,其中演示了的基本使用方法:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
ibatis SQLMap
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系统初始化完毕,开始执行update操作
try{
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(1));
sqlMap.update("updateUser",user);
sqlMap.commitTransaction();
finally{
sqlMap.endTransaction();
}
其中,SqlMapClient 是ibatis 运作的核心,所有操作均通过SqlMapClient
实例完成。
可以看出,对于应用层而言,程序员面对的是传统意义上的数据对象,而非JDBC
中烦杂的ResultSet,这使得上层逻辑开发人员的工作量大大减轻,同时代码更
加清晰简洁。
数据库操作在映射文件中加以定义,从而将数据存储逻辑从上层逻辑代码中独立
出来。
而底层数据操作的SQL 可配置化,使得我们可以控制最终的数据操作方式,通过
SQL 的优化获得最佳的数据库执行效能,这在依赖SQL 自动生成的“全自动”ORM
机制中是所难以实现的。
ibatis 配置
结合上面示例中的ibatis 配置文件。下面是对配置文件中各节点的说明:
<?xml version="1.0" encoding="UTF-8" ?>
PUBLIC
">
<sqlMapConfig>
<settings ⑴
cacheModelsEnabled=
enhancementEnabled=
lazyLoadingEnabled=
errorTracingEnabled=
maxRequests=
maxSessions=
maxTransactions=
useStatementNamespaces=
/>
<transactionManager type="JDBC"> ⑵
<dataSource type="SIMPLE"> ⑶
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
<!DOCTYPE sqlMapConfig
"-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd"true"
"true"
"true"
"true"
"32"
"10"
"5"
"false"
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/> ⑷
<sqlMap resource="com/ibatis/sample/Address.xml"/>
</sqlMapConfig>
⑴ Settings 节点
参数描述
cacheModelsEnabled 是否启用SqlMapClient 上的缓存机制。
建议设为"true"
enhancementEnabled 是否针对POJO 启用字节码增强机制以提升
getter/setter 的调用效能,避免使用Java
Reflect 所带来的性能开销。
同时,这也为Lazy Loading 带来了极大的性能
提升。
建议设为"true"
errorTracingEnabled 是否启用错误日志,在开发期间建议设为"true"
以方便调试
lazyLoadingEnabled 是否启用延迟加载机制,建议设为"true"
maxRequests 最大并发请求数(Statement 并发数)
maxTransactions 最大并发事务数
maxSessions 最大Session 数。即当前最大允许的并发
SqlMapClient 数。
maxSessions 设定必须介于
maxTransactions 和maxRequests 之间,即
maxTransactions<maxSessions=<
maxRequests
useStatementNamespaces 是否使用Statement 命名空间。
这里的命名空间指的是映射文件中,sqlMap 节点
的namespace 属性,如在上例中针对t_user
表的映射文件sqlMap 节点:
<sqlMap namespace="User">
这里,指定了此sqlMap 节点下定义的操作均从
属于"User"命名空间。
在useStatementNamespaces="true"的情
况下,Statement 调用需追加命名空间,如:
⑵ transactionManager 节点
sqlMap.update("User.updateUser",use
r);
否则直接通过Statement 名称调用即
sqlMap.update("updateUser",user);
但请注意此时需要保证所有映射
定义无重名。
可,如:
文件中,
Statement
transactionManager 节点定义了ibatis 的事务管理器,目前提供了以下几
种选择:
. JDBC
通过传统JDBC Connection.commit/rollback 实现事务支持。
. JTA
使用容器提供的JTA 服务实现全局事务管理。
. EXTERNAL
外部事务管理, 如在EJB 中使用ibatis,通过EJB 的部署配置即可实现自
动的事务管理机制。此时ibatis 将把所有事务委托给外部容器进行管理。
此外,通过Spring 等轻量级容器实现事务的配置化管理也是一个不错的选
择。关于结合容器实现事务管理,参见“高级特性”中的描述。
⑶ dataSource 节点
dataSource 从属于transactionManager 节点,用于设定ibatis 运行期使
用的DataSource 属性。
type 属性: dataSource 节点的type属性指定了dataSource 的实现类型。
可选项目:
. SIMPLE:
SIMPLE 是ibatis 内置的dataSource 实现,其中实现了一个简单的
数据库连接池机制,对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory 。
. DBCP:
基于Apache DBCP 连接池组件实现的DataSource 封装,当无容器提
供DataSource 服务时,建议使用该选项,对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory 。
. JNDI:
使用J2EE 容器提供的DataSource 实现,DataSource 将通过指定
的JNDI Name 从容器中获取。对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.JndiDataSourceFacto
ry。
dataSource 的子节点说明(SIMPLE&DBCP):
参数描述
JDBC.Driver JDBC 驱动。
如:org.gjt.mm.mysql.Driver
JDBC.ConnectionURL 数据库URL 。
如:jdbc:mysql://localhost/sample
如果用的是SQLServer JDBC Driver,需要
在url 后追加SelectMethod=Cursor 以获得
JDBC 事务的多Statement 支持。
JDBC.Username 数据库用户名
JDBC.Password 数据库用户密码
Pool.MaximumActiveConn 数据库连接池可维持的最大容量。
ections
Pool.MaximumIdleConnec
tions
数据库连接池中允许的挂起(idle)连接数。
以上子节点适用于SIMPLE 和DBCP 模式,分别针对SIMPLE 和DBCP 模式的
DataSource 私有配置节点如下:
SIMPLE:
参数描述
Pool.MaximumCheckoutTi 数据库联接池中,连接被某个任务所允许占用的
me 最大时间,如果超过这个时间限定,连接将被强
制收回。(毫秒)
Pool.TimeToWait 当线程试图从连接池中获取连接时,连接池中无
可用连接可供使用,此时线程将进入等待状态,
直到池中出现空闲连接。此参数设定了线程所允
许等待的最长时间。(毫秒)
Pool.PingQuery 数据库连接状态检测语句。
某些数据库在连接在某段时间持续处于空闲状态
时会将其断开。而连接池管理器将通过此语句检
测池中连接是否可用。
检测语句应该是一个最简化的无逻辑SQL 。
如“select 1 from t_user”, 如果执行此语句
成功,连接池管理器将认为此连接处于可用状态。
Pool.PingEnabled 是否允许检测连接状态。
Pool.PingConnectionsOl 对持续连接时间超过设定值(毫秒)的连接进行
derThan 检测。
Pool.PingConnectionsNo
tUsedFor
对空闲超过设定值(毫秒)的连接进行检测。
DBCP:
参数描述
Pool.MaximumWait 当线程试图从连接池中获取连接时,连接池中无
可用连接可供使用,此时线程将进入等待状态,
直到池中出现空闲连接。此参数设定了线程所允
许等待的最长时间。(毫秒)
Pool.ValidationQuery 数据库连接状态检测语句。
某些数据库在连接在某段时间持续处于空闲状态
时会将其断开。而连接池管理器将通过此语句检
测池中连接是否可用。
检测语句应该是一个最简化的无逻辑SQL 。
如“select 1 from t_user”, 如果执行此语句
成功,连接池管理器将认为此连接处于可用状态。
Pool.LogAbandoned 当数据库连接被废弃时,是否打印日志。
Pool.RemoveAbandonedTi 数据库连接被废弃的最大超时时间
meout
Pool.RemoveAbandoned 当连接空闲时间超过
RemoveAbandonedTimeout 时,是否将其废
弃。
JNDI 由于大部分配置是在应用服务器中进行,因此ibatis 中的配置相对简单,下面
是分别使用JDBC 和JTA 事务管理的JDNI 配置:
使用JDBC 事务管理的JNDI DataSource 配置
<transactionManager type="JDBC" >
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
<transactionManager type="JTA" >
<property name="UserTransaction"
value="java:/ctx/con/UserTransaction"/>
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
⑷ sqlMap 节点
sqlMap 节点指定了映射文件的位置,配置中可出现多个sqlMap 节点,以指定
项目内所包含的所有映射文件。
ibatis 基础语义
XmlSqlMapClientBuilder
XmlSqlMapClientBuilder 是ibatis 2.0 之后版本新引入的组件,用以替代1.x
版本中的XmlSqlMapBuilder。其作用是根据配置文件创建SqlMapClient 实例。
SqlMapClient
SqlMapClient 是ibatis 的核心组件,提供数据操作的基础平台。SqlMapClient
可通过XmlSqlMapClientBuilder 创建:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
"com/ibatis/sample/SqlMapConfig.xml"指明了配置CLASSPATH文件在
中的相对路径。XmlSqlMapClientBuilder 通过接受一个Reader 类型的配置文
件句柄,根据配置参数,创建SqlMapClient 实例。
SqlMapClient 提供了众多数据操作方法,下面是一些常用方法的示例,具体说明
文档请参见ibatis java doc,或者ibatis 官方开发手册。
SqlMapClient 基本操作示例
以下示例摘自ibatis 官方开发手册,笔者对其进行了重新排版以获得更好的阅读效果。
例1: 数据写入操作(insert, update, delete):
sqlMap.startTransaction();
Product product = new Product();
product.setId (1);
product.setDescription (“Shih Tzu”);
int rows = sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();
例2: 数据查询(select)
sqlMap.startTransaction();
Integer key = new Integer (1);
Product product = (Product)sqlMap.queryForObject
(“getProduct”, key);
sqlMap.commitTransaction();
例3: 在指定对象中存放查询结果(select)
sqlMap.startTransaction();
Customer customer = new Customer();
sqlMap.queryForObject(“getCust”, parameterObject, customer);
sqlMap.queryForObject(“getAddr”, parameterObject, customer);
sqlMap.commitTransaction();
例4: 执行批量查询(select)
sqlMap.startTransaction();
List list = sqlMap.queryForList (“getProductList”, null);
sqlMap.commitTransaction();
例5: 关于AutoCommit
//没有预先执行startTransaction时,默认为模式
int rows = sqlMap.insert (“insertProduct”, product);
auto_commit
例6:查询指定范围内的数据
sqlMap.startTransaction();
= “”, nullsqlMap.commitTransaction();
List list sqlMap.queryForList (getProductList, 0, 40);
例7: 结合RowHandler 进行查询(select)
MyRowHandler implements RowHandler {
handleRow (Object object, List list) throws
SQLException {
Product product = (Product) object;
product.setQuantity (10000);
sqlMap.update (“updateProduct”, product);
}
}
sqlMap.startTransaction();
RowHandler rowHandler = new MyRowHandler();
List list = sqlMap.queryForList (“getProductList”, null,
rowHandler);
sqlMap.commitTransaction();
public classpublic void
例8: 分页查询(select)
PaginatedList list =
sqlMap.queryForPaginatedList (“getProductList”, null, 10);
list.nextPage();
list.previousPage();
例9: 基于Map 的批量查询(select)
sqlMap.startTransaction();
Map map = sqlMap.queryForMap (“getProductList”, null,
“productCode”);
sqlMap.commitTransaction();
Product p = (Product) map.get(“EST-93”);
OR 映射
相对Hibernate 等ORM 实现而言,ibatis 的映射配置更为简洁直接,下面是一
个典型的配置文件。
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<!--模块配置-->
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement=" updateUser"/>
<property name="size" value="1000" />
</cacheModel>
<!—Statement配置-->
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
</sqlMap>
可以看到,映射文件主要分为两个部分:模块配置和Statement 配置。
模块配置包括:
. typeAlias 节点:
定义了本映射文件中的别名,以避免过长变量值的反复书写,此例中通过
typeAlias 节点为类"com.ibatis.sample.User"定义了一个别名"user",
这样在本配置文件的其他部分,需要引用"com.ibatis.sample.User" 类时,
只需以其别名替代即可。
. cacheModel 节点
定义了本映射文件中使用的Cache 机制:
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
这里申明了一个名为"userCache" 的,之后可以在cacheModel
Statement 申明中对其进行引用:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
这表明对通过id 为"getUser"的获取的数据,使用Select statement
cacheModel "userCache" 进行缓存。之后如果程序再次用此Statement
进行数据查询,即直接从缓存中读取查询结果,而无需再去数据库查询。
cacheModel 主要有下面几个配置点:
. flushInterval :
设定缓存有效期,如果超过此设定值,则将此CacheModel 的缓存清空。
. size:
本CacheModel 中最大容纳的数据对象数量。
. flushOnExecute:
指定执行特定Statement 时,将缓存清空。如updateUser 操作将更
新数据库中的用户信息,这将导致缓存中的数据对象与数据库中的实际
数据发生偏差,因此必须将缓存清空以避免脏数据的出现。
关于Cache 的深入探讨,请参见“高级特性”中的相关章节。
Statement 配置:
Statement 配置包含了数个与SQL Statement 相关的节点,分别为:
. statement
. insert
. delete
. update
. select
. procedure
其中,statement 最为通用,它可以替代其余的所有节点。除statement 之外
的节点各自对应了SQL 中的同名操作(procedure 对应存储过程)。
使用statement 定义所有操作固然可以达成目标,但缺乏直观性,建议在实际
开发中根据操作目的,各自选用对应的节点名加以申明。一方面,使得配置文件
更加直观,另一方面,也可借助DTD 对节点申明进行更有针对性的检查,以避免
配置上的失误。
各种类型的Statement 配置节点的参数类型基本一致,区别在于数量不同。如
insert、update、delete 节点无需返回数据类型定义(总是int)。
主要的配置项如下:
statement:
<statement id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
>
select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</statement>
select:
<select id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
>
select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</select>
Insert:
<insert id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
insert into t_user
(name,sex)
values
([?|#propertyName#],[?|#propertyName#])
</insert>
Update:
<update id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
UPDATE t_user
SET
name=[?|#propertyName#],
sex=[?|#propertyName#]
WHERE id = [?|#propertyName#]
</update>
Delete:
<delete id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
delete from t_user
where id = [?|#propertyName#]
</delete>
其中以“[]”包围的部分为可能出现的配置栏目。
参数描述
parameterClass 参数类。指定了参数的完整类名(包括包路径)。
可通过别名避免每次重复书写冗长的类名。
resultClass 结果类。指定结果类型的完整类名(包括包路径)
可通过别名避免每次重复书写冗长的类名。
parameterMap 参数映射,需结合parameterMap 节点对映射
关系加以定义。
对于存储过程之外的statement 而言,建议使用
parameterClass 作为参数配置方式, 一方面避
免了参数映射配置工作,另一方面其性能表现也
更加出色。
resultMap 结果映射,需结合resultMap 节点对映射关系
加以定义。
cacheModel statement 对应的Cache 模块。
对于参数定义而言,尽量使用parameterClass,即直接将POJO 作为
statement 的调用参数,这样在SQL 中可以直接将POJO 的属性作为参数加以
设定,如:
<update id="updateUser"
parameterClass="com.ibatis.sample.User">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
这里将com.ibatis.sample.User 类设定为的参数,之后,update statement
我们即可在SQL 中通过#propertyName#对POJO 的属性进行引用。如上例
中的:
SET name=#name#, sex=#sex# WHERE id=#id#
运行期,将通过调用对象的、和方法获得相ibatis User getName getSex getId
应的参数值,并将其作为SQL 的参数。
如果parameterClass 中设定的是jdk 的中的简单对象类型,如String、
Integer,ibatis 会直接将其作为SQL 中的参数值。
我们也可以将包含了参数数据的Map 对象传递给Statement ,如:
<update id="updateUser"
parameterClass="java.util.Map">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
这里传入的参数就是一个对象,将以””、””、”id”从中Map ibatis key namesex
提取对应的参数值。
同样的原理,我们也可以在resultMap 中设定返回类型为map 。
<select id="getUser"
parameterClass="java.lang.String"
resultClass="java.util.Map">
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
返回的结果将以各字段名为保存在对象中返回。key Map
在SQL 中设定参数名时,可以同时指定参数类型, 如:
SET name=#name:VARCHAR#,sex=#sex:NUMERIC# WHERE
id=#id:NUMERIC#
对于返回结果而言,如果是select 语句,建议也采用resultClass 进行定义,如:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
会自动根据语句中的字段名,调用对应的方法设定属性ibatis select POJO set
值,如上例中,ibatis 会调用setName,setSex 方法将Select 语句返回的数据赋
予相应的POJO 实例。
有些时候,数据库表中的字段名过于晦涩,而为了使得代码更易于理解,我们
希望字段映射到POJO 时,采用比较易读的属性名,此时,我们可以通过Select
的as 字句对字段名进行转义,如(假设我们的书库中对应用户名的字段为
xingming ,对应性别的字段为xingbie):
select
xingming as name,
xingbie as sex
from t_user
where id = #id#
会根据转义后的字段名进行属性映射(即调用的方法而ibatis POJO setName
不是setXingming 方法)。
parameterMap 和resultMap 实现了POJO 到数据库字段的映射配置,下面是
一个例子:
<resultMap id="get_user_result" class="user">
<result property="name" column="xingming"
jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="sex" column="xingbie"
jdbcType="int" javaType="java.lang.Integer"/>
<result property="id" column="id"
jdbcType="int" javaType="java.lang.Integer"/>
</resultMap>
<parameterMap id="update_user_para" class="redemption" >
<parameter property="name"
jdbcType="VARCHAR"
javaType="java.lang.String"
nullValue=""
/>
<parameter property="sex"
jdbcType="int"
javaType="java.lang.Integer"
nullValue=""
/>
</parameterMap>
Parameter 的nullValue 指定了如果参数为空(null)时的默认值。
之后我们即可在statement 申明中对其进行引用,如:
<procedure id="getUserList"
resultMap="get_user_result"
>
{call sp_getUserList()}
</procedure>
<procedure id="doUserUpdate"
parameterMap="update_user_para"
>
{call sp_doUserUpdate(#id#,#name#,#sex#)}
</procedure>
一般而言,对于insert 、update 、delete 、select 语句,优先采用parameterClass
和resultClass 。
parameterMap 使用较少,而resultMap 则大多用于嵌套查询以及存储过程的
处理,之所以这样,原因是由于存储过程相对而言比较封闭(很多情况下需要调用现有
的存储过程,其参数命名和返回的数据字段命名往往不符合Java 编程中的命名习惯,
并且由于我们难以通过Select SQL 的as子句进行字段名转义,无法使其自动与POJO
中的属性名相匹配)。此时,使用resultMap 建立字段名和POJO 属性名之间的映射
关系就显得非常有效。另一方面,由于通过resultMap 指定了字段名和字段类型,
ibatis 无需再通过JDBC ResultSetMetaData 来动态获取字段信息,在一定程度
上也提升了性能表现。
ibatis 高级特性
数据关联
至此,我们讨论的都是针对独立数据的操作。在实际开发中,我们常常遇到关联数
据的情况,如User 对象拥有若干Address 对象,每个Address 对象描述了对应User 的
一个联系地址,这种情况下,我们应该如何处理?
通过单独的Statement 操作固然可以实现(通过Statement 用于读取用户数据,再手
工调用另外一个Statement 根据用户ID 返回对应的Address 信息)。不过这样未免失之
繁琐。下面我们就看看在ibatis 中,如何对关联数据进行操作。
ibatis 中,提供了Statement 嵌套支持,通过Statement 嵌套,我们即可实现关联数
据的操作。
一对多关联
下面的例子中,我们首选读取t_user 表中的所有用户记录,然后获取每个用户对应
的所有地址信息。
配置文件如下:
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<typeAlias alias="address" type="com.ibatis.sample.Address"/>
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="addresses" column="id"
select="User.getAddressByUserId"/>
</resultMap>
<select id="getUsers"
parameterClass="java.lang.String"
resultMap="get-user-result">
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
<select id="getAddressByUserId"
parameterClass="int"
resultClass="address">
<![CDATA[
select
address,
zipcode
from t_address
where user_id = #userid#
]]>
</select>
</sqlMap>
对应代码:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder= new XmlSqlMapClientBuilder();
sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系统初始化完毕
List userList = sqlMap.queryForList("User.getUsers", "");
for (int i = 0; i < userList.size(); i++) {
User user = (User)userList.get(i);
System.out.println("==>" + user.getName());
for (int k = 0; k < user.getAddresses().size(); k++) {
Address addr = (Address) user.getAddresses().get(k);
System.out.println(addr.getAddress());
}
}
这里通过在resultMap 中定义嵌套查询getAddressByUserId,我们实现了关联
数据的读取。
实际上,这种方式类似于前面所说的通过两条单独的Statement 进行关联数据的读
取,只是将关联关系在配置中加以描述,由ibatis 自动完成关联数据的读取。
需要注意的是,这里有一个潜在的性能问题,也就是所谓“n+1”Select 问题。
注意上面示例运行过程中的日志输出:
……
PreparedStatement -{pstm-100001} PreparedStatement: select id, name, sex from
t_user
……
PreparedStatement -{pstm-100004} PreparedStatement: select address, zipcode from
t_address where user_id = ?
……
PreparedStatement -{pstm-100007} PreparedStatement: select address,zipcode from
t_address where user_id = ?
第一条将表中的所有数据读取出来PreparedStatement t_user (目前t_user 表中有两
条测试数据),随即,通过两次Select 操作,从t_address 表中读取两个用户所关联的
Address 记录。
如果t_user 表中记录较少,不会有明显的影响,假设t_user 表中有十万条记录,那
么这样的操作将需要100000+1 条Select 语句反复执行才能获得结果,无疑,随着记录
的增长,这样的开销将无法承受。
之所以在这里提及这个问题,目的在于引起读者的注意,在系统设计中根据具体情
况,采用一些规避手段(如使用存储过程集中处理大批量关联数据),从而避免因为这
个问题而引起产品品质上的缺陷。
ibatis 开发指南
ibatis Quick Start......................................... 5
准备工作............................................... 5
构建ibatis 基础代码...................................... 5
ibatis 配置.............................................. 11
ibatis 基础语义............................................ 16
XmlSqlMapClientBuilder....................................... 16
SqlMapClient .............................................. 16
SqlMapClient 基本操作示例................................. 16
OR 映射................................................ 19
ibatis 高级特性...................................... 26
数据关联.......................................... 26
一对多关联....................................... 26
一对一关联...................................... 28
延迟加载.................................... 30
动态映射..................................... 31
事务管理........................................... 35
基于JDBC 的事务管理机制.................................... 35
基于JTA 的事务管理机制................................ 36
外部事务管理............................................ 38
Cache .......................................... 39
MEMORY 类型Cache 与WeakReference .................. 40
LRU 型Cache ............................................... 42
FIFO 型Cache ....................................... 43
OSCache.............................................. 43
ibatis 开发指南
相对Hibernate 和Apache OJB 等“一站式”ORM 解决方案而言,ibatis 是一种“半
自动化”的ORM 实现。
所谓“半自动”,可能理解上有点生涩。纵观目前主流的ORM ,无论Hibernate 还是
Apache OJB,都对数据库结构提供了较为完整的封装,提供了从POJO 到数据库表的全
套映射机制。程序员往往只需定义好了POJO 到数据库表的映射关系,即可通过Hibernate
或者OJB 提供的方法完成持久层操作。程序员甚至不需要对SQL 的熟练掌握,
Hibernate/OJB 会根据制定的存储逻辑,自动生成对应的SQL 并调用JDBC 接口加以执
行。
大多数情况下( 特别是对新项目,新系统的开发而言)
,这样的机制无往不利,大有一
统天下的势头。但是,在一些特定的环境下,这种一站式的解决方案却未必灵光。
在笔者的系统咨询工作过程中,常常遇到以下情况:
1. 系统的部分或全部数据来自现有数据库,处于安全考虑,只对开发团队提供几
条Select SQL(或存储过程)以获取所需数据,具体的表结构不予公开。
2. 开发规范中要求, 所有牵涉到业务逻辑部分的数据库操作,必须在数据库层由
存储过程实现(就笔者工作所面向的金融行业而言,工商银行、中国银行、交
通银行,都在开发规范中严格指定)
3. 系统数据处理量巨大,性能要求极为苛刻,这往往意味着我们必须通过经过高
度优化的SQL 语句(或存储过程)才能达到系统性能设计指标。
面对这样的需求,再次举起Hibernate 大刀,却发现刀锋不再锐利,甚至无法使用,
奈何?恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用JDBC
进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作
令人厌烦。
“半自动化”的ibatis,却刚好解决了这个问题。
这里的“半自动化”,是相对Hibernate 等提供了全面的数据库封装机制的“全自动化”
ORM 实现而言,“全自动”ORM 实现了POJO 和数据库表之间的映射,以及SQL 的自动
生成和执行。而ibatis 的着力点,则在于POJO 与SQL 之间的映射关系。也就是说,ibatis
并不会为程序员在运行期自动生成SQL 执行。具体的SQL 需要程序员编写,然后通过映
射配置文件,将SQL 所需的参数,以及返回的结果字段映射到指定POJO 。
使用ibatis 提供的ORM 机制,对业务逻辑实现人员而言,面对的是纯粹的Java 对象,
这一层与通过Hibernate 实现ORM 而言基本一致,而对于具体的数据操作,Hibernate
会自动生成SQL 语句,而ibatis 则要求开发者编写具体的SQL 语句。相对Hibernate 等
“全自动”ORM 机制而言,ibatis 以SQL 开发的工作量和数据库移植性上的让步,为系统
设计提供了更大的自由空间。作为“全自动”ORM 实现的一种有益补充,ibatis 的出现显
得别具意义。
ibatis Quick Start
准备工作
1. 下载ibatis 软件包(http://www.ibatis.com)。
2. 创建测试数据库,并在数据库中创建一个t_user 表,其中包含三个字段:
. id(int)
. name(varchar)
. sex(int) 。
3. 为了在开发过程更加直观,我们需要将ibatis 日志打开以便观察ibatis 运作的细节。
ibatis 采用Apache common_logging,并结合Apache log4j 作为日志输出组件。在
CLASSPATH 中新建log4j.properties 配置文件,内容如下:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1} -%m%n
log4j.logger.java.sql.PreparedStatement=DEBUG
构建ibatis 基础代码
ibatis 基础代码包括:
1. ibatis 实例配置
一个典型的配置文件如下(具体配置项目的含义见后):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
errorTracingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="false"
/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/>
</sqlMapConfig>
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
2. POJO(Plain Ordinary Java Object)
下面是我们用作示例的一个POJO:
public class User implements Serializable {
private Integer id;
private String name;
private Integer sex;
private Set addresses = new HashSet();
/** default constructor */
public User() {
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSex() {
return this.sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
}
3. 映射文件
与Hibernate 不同。因为需要人工编写SQL 代码,ibatis 的映射文件一般采
用手动编写(通过Copy/Paste,手工编写映射文件也并没想象中的麻烦)。
针对上面POJO 的映射代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
<![CDATA[
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
]]>
</update>
<insert id="insertUser"
parameterClass="user"
>
INSERT INTO t_user (
name,
sex)
VALUES (
#name#,
#sex#
)
</insert>
<delete id="deleteUser"
parameterClass="java.lang.String">
delete from t_user
where id = #value#
</delete>
</sqlMap>
从上面的映射文件可以看出,通过<insert>、<delete>、<update>、
<select>四个节点,我们分别定义了针对TUser 对象的增删改查操作。在这
四个节点中,我们指定了对应的SQL 语句,以update 节点为例:
……
<update id="updateUser" ⑴
parameterClass="user"> ⑵
<![CDATA[ ⑶
UPDATE t_user ⑷
SET (
IBATIS Developer’s Guide Version 1.0
name=#name#, ⑸
sex=#sex# ⑹
)
WHERE id = #id# ⑺
]]>
</update>
……
⑴ ID
指定了操作ID,之后我们可以在代码中通过指定操作id 来执行此节点所定
义的操作,如:
sqlMap.update("updateUser",user);
ID 设定使得在一个配置文件中定义两个同名节点成为可能(两个update 节
点,以不同id 区分)
⑵ parameterClass
指定了操作所需的参数类型,此例中update 操作以
com.ibatis.sample.User 类型的对象作为参数,目标是将提供的User
实例更新到数据库。
parameterClass="user"中,user 为“com.ibatis.sample.User”
类的别名,别名可通过typeAlias 节点指定,如示例配置文件中的:
<typeAlias alias="user" type="com.ibatis.sample.User"/>
⑶ <![CDATA[……]]>
通过<![CDATA[……]]>节点,可以避免SQL 中与XML 规范相冲突的字符对
XML 映射文件的合法性造成影响。
⑷ 执行更新操作的SQL,这里的SQL 即实际数据库支持的SQL 语句, 将由
ibatis 填入参数后交给数据库执行。
⑸ SQL 中所需的用户名参数,
“#name#”在运行期会由传入的user 对象的name
属性填充。
⑹ SQL 中所需的用户性别参数“#sex#”, 将在运行期由传入的user 对象的
sex 属性填充。
⑺ SQL 中所需的条件参数“#id#”, 将在运行期由传入的user 对象的id 属性
填充。
对于这个示例,ibatis 在运行期会读取id 为“updateUser”的update 节点
的SQL 定义,并调用指定的user 对象的对应getter 方法获取属性值,并用此
属性值,对SQL 中的参数进行填充后提交数据库执行。
此例对应的应用级代码如下,其中演示了的基本使用方法:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
ibatis SQLMap
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系统初始化完毕,开始执行update操作
try{
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(1));
sqlMap.update("updateUser",user);
sqlMap.commitTransaction();
finally{
sqlMap.endTransaction();
}
其中,SqlMapClient 是ibatis 运作的核心,所有操作均通过SqlMapClient
实例完成。
可以看出,对于应用层而言,程序员面对的是传统意义上的数据对象,而非JDBC
中烦杂的ResultSet,这使得上层逻辑开发人员的工作量大大减轻,同时代码更
加清晰简洁。
数据库操作在映射文件中加以定义,从而将数据存储逻辑从上层逻辑代码中独立
出来。
而底层数据操作的SQL 可配置化,使得我们可以控制最终的数据操作方式,通过
SQL 的优化获得最佳的数据库执行效能,这在依赖SQL 自动生成的“全自动”ORM
机制中是所难以实现的。
ibatis 配置
结合上面示例中的ibatis 配置文件。下面是对配置文件中各节点的说明:
<?xml version="1.0" encoding="UTF-8" ?>
PUBLIC
">
<sqlMapConfig>
<settings ⑴
cacheModelsEnabled=
enhancementEnabled=
lazyLoadingEnabled=
errorTracingEnabled=
maxRequests=
maxSessions=
maxTransactions=
useStatementNamespaces=
/>
<transactionManager type="JDBC"> ⑵
<dataSource type="SIMPLE"> ⑶
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
<!DOCTYPE sqlMapConfig
"-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd"true"
"true"
"true"
"true"
"32"
"10"
"5"
"false"
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/> ⑷
<sqlMap resource="com/ibatis/sample/Address.xml"/>
</sqlMapConfig>
⑴ Settings 节点
参数描述
cacheModelsEnabled 是否启用SqlMapClient 上的缓存机制。
建议设为"true"
enhancementEnabled 是否针对POJO 启用字节码增强机制以提升
getter/setter 的调用效能,避免使用Java
Reflect 所带来的性能开销。
同时,这也为Lazy Loading 带来了极大的性能
提升。
建议设为"true"
errorTracingEnabled 是否启用错误日志,在开发期间建议设为"true"
以方便调试
lazyLoadingEnabled 是否启用延迟加载机制,建议设为"true"
maxRequests 最大并发请求数(Statement 并发数)
maxTransactions 最大并发事务数
maxSessions 最大Session 数。即当前最大允许的并发
SqlMapClient 数。
maxSessions 设定必须介于
maxTransactions 和maxRequests 之间,即
maxTransactions<maxSessions=<
maxRequests
useStatementNamespaces 是否使用Statement 命名空间。
这里的命名空间指的是映射文件中,sqlMap 节点
的namespace 属性,如在上例中针对t_user
表的映射文件sqlMap 节点:
<sqlMap namespace="User">
这里,指定了此sqlMap 节点下定义的操作均从
属于"User"命名空间。
在useStatementNamespaces="true"的情
况下,Statement 调用需追加命名空间,如:
⑵ transactionManager 节点
sqlMap.update("User.updateUser",use
r);
否则直接通过Statement 名称调用即
sqlMap.update("updateUser",user);
但请注意此时需要保证所有映射
定义无重名。
可,如:
文件中,
Statement
transactionManager 节点定义了ibatis 的事务管理器,目前提供了以下几
种选择:
. JDBC
通过传统JDBC Connection.commit/rollback 实现事务支持。
. JTA
使用容器提供的JTA 服务实现全局事务管理。
. EXTERNAL
外部事务管理, 如在EJB 中使用ibatis,通过EJB 的部署配置即可实现自
动的事务管理机制。此时ibatis 将把所有事务委托给外部容器进行管理。
此外,通过Spring 等轻量级容器实现事务的配置化管理也是一个不错的选
择。关于结合容器实现事务管理,参见“高级特性”中的描述。
⑶ dataSource 节点
dataSource 从属于transactionManager 节点,用于设定ibatis 运行期使
用的DataSource 属性。
type 属性: dataSource 节点的type属性指定了dataSource 的实现类型。
可选项目:
. SIMPLE:
SIMPLE 是ibatis 内置的dataSource 实现,其中实现了一个简单的
数据库连接池机制,对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory 。
. DBCP:
基于Apache DBCP 连接池组件实现的DataSource 封装,当无容器提
供DataSource 服务时,建议使用该选项,对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory 。
. JNDI:
使用J2EE 容器提供的DataSource 实现,DataSource 将通过指定
的JNDI Name 从容器中获取。对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.JndiDataSourceFacto
ry。
dataSource 的子节点说明(SIMPLE&DBCP):
参数描述
JDBC.Driver JDBC 驱动。
如:org.gjt.mm.mysql.Driver
JDBC.ConnectionURL 数据库URL 。
如:jdbc:mysql://localhost/sample
如果用的是SQLServer JDBC Driver,需要
在url 后追加SelectMethod=Cursor 以获得
JDBC 事务的多Statement 支持。
JDBC.Username 数据库用户名
JDBC.Password 数据库用户密码
Pool.MaximumActiveConn 数据库连接池可维持的最大容量。
ections
Pool.MaximumIdleConnec
tions
数据库连接池中允许的挂起(idle)连接数。
以上子节点适用于SIMPLE 和DBCP 模式,分别针对SIMPLE 和DBCP 模式的
DataSource 私有配置节点如下:
SIMPLE:
参数描述
Pool.MaximumCheckoutTi 数据库联接池中,连接被某个任务所允许占用的
me 最大时间,如果超过这个时间限定,连接将被强
制收回。(毫秒)
Pool.TimeToWait 当线程试图从连接池中获取连接时,连接池中无
可用连接可供使用,此时线程将进入等待状态,
直到池中出现空闲连接。此参数设定了线程所允
许等待的最长时间。(毫秒)
Pool.PingQuery 数据库连接状态检测语句。
某些数据库在连接在某段时间持续处于空闲状态
时会将其断开。而连接池管理器将通过此语句检
测池中连接是否可用。
检测语句应该是一个最简化的无逻辑SQL 。
如“select 1 from t_user”, 如果执行此语句
成功,连接池管理器将认为此连接处于可用状态。
Pool.PingEnabled 是否允许检测连接状态。
Pool.PingConnectionsOl 对持续连接时间超过设定值(毫秒)的连接进行
derThan 检测。
Pool.PingConnectionsNo
tUsedFor
对空闲超过设定值(毫秒)的连接进行检测。
DBCP:
参数描述
Pool.MaximumWait 当线程试图从连接池中获取连接时,连接池中无
可用连接可供使用,此时线程将进入等待状态,
直到池中出现空闲连接。此参数设定了线程所允
许等待的最长时间。(毫秒)
Pool.ValidationQuery 数据库连接状态检测语句。
某些数据库在连接在某段时间持续处于空闲状态
时会将其断开。而连接池管理器将通过此语句检
测池中连接是否可用。
检测语句应该是一个最简化的无逻辑SQL 。
如“select 1 from t_user”, 如果执行此语句
成功,连接池管理器将认为此连接处于可用状态。
Pool.LogAbandoned 当数据库连接被废弃时,是否打印日志。
Pool.RemoveAbandonedTi 数据库连接被废弃的最大超时时间
meout
Pool.RemoveAbandoned 当连接空闲时间超过
RemoveAbandonedTimeout 时,是否将其废
弃。
JNDI 由于大部分配置是在应用服务器中进行,因此ibatis 中的配置相对简单,下面
是分别使用JDBC 和JTA 事务管理的JDNI 配置:
使用JDBC 事务管理的JNDI DataSource 配置
<transactionManager type="JDBC" >
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
<transactionManager type="JTA" >
<property name="UserTransaction"
value="java:/ctx/con/UserTransaction"/>
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
⑷ sqlMap 节点
sqlMap 节点指定了映射文件的位置,配置中可出现多个sqlMap 节点,以指定
项目内所包含的所有映射文件。
ibatis 基础语义
XmlSqlMapClientBuilder
XmlSqlMapClientBuilder 是ibatis 2.0 之后版本新引入的组件,用以替代1.x
版本中的XmlSqlMapBuilder。其作用是根据配置文件创建SqlMapClient 实例。
SqlMapClient
SqlMapClient 是ibatis 的核心组件,提供数据操作的基础平台。SqlMapClient
可通过XmlSqlMapClientBuilder 创建:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
"com/ibatis/sample/SqlMapConfig.xml"指明了配置CLASSPATH文件在
中的相对路径。XmlSqlMapClientBuilder 通过接受一个Reader 类型的配置文
件句柄,根据配置参数,创建SqlMapClient 实例。
SqlMapClient 提供了众多数据操作方法,下面是一些常用方法的示例,具体说明
文档请参见ibatis java doc,或者ibatis 官方开发手册。
SqlMapClient 基本操作示例
以下示例摘自ibatis 官方开发手册,笔者对其进行了重新排版以获得更好的阅读效果。
例1: 数据写入操作(insert, update, delete):
sqlMap.startTransaction();
Product product = new Product();
product.setId (1);
product.setDescription (“Shih Tzu”);
int rows = sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();
例2: 数据查询(select)
sqlMap.startTransaction();
Integer key = new Integer (1);
Product product = (Product)sqlMap.queryForObject
(“getProduct”, key);
sqlMap.commitTransaction();
例3: 在指定对象中存放查询结果(select)
sqlMap.startTransaction();
Customer customer = new Customer();
sqlMap.queryForObject(“getCust”, parameterObject, customer);
sqlMap.queryForObject(“getAddr”, parameterObject, customer);
sqlMap.commitTransaction();
例4: 执行批量查询(select)
sqlMap.startTransaction();
List list = sqlMap.queryForList (“getProductList”, null);
sqlMap.commitTransaction();
例5: 关于AutoCommit
//没有预先执行startTransaction时,默认为模式
int rows = sqlMap.insert (“insertProduct”, product);
auto_commit
例6:查询指定范围内的数据
sqlMap.startTransaction();
= “”, nullsqlMap.commitTransaction();
List list sqlMap.queryForList (getProductList, 0, 40);
例7: 结合RowHandler 进行查询(select)
MyRowHandler implements RowHandler {
handleRow (Object object, List list) throws
SQLException {
Product product = (Product) object;
product.setQuantity (10000);
sqlMap.update (“updateProduct”, product);
}
}
sqlMap.startTransaction();
RowHandler rowHandler = new MyRowHandler();
List list = sqlMap.queryForList (“getProductList”, null,
rowHandler);
sqlMap.commitTransaction();
public classpublic void
例8: 分页查询(select)
PaginatedList list =
sqlMap.queryForPaginatedList (“getProductList”, null, 10);
list.nextPage();
list.previousPage();
例9: 基于Map 的批量查询(select)
sqlMap.startTransaction();
Map map = sqlMap.queryForMap (“getProductList”, null,
“productCode”);
sqlMap.commitTransaction();
Product p = (Product) map.get(“EST-93”);
OR 映射
相对Hibernate 等ORM 实现而言,ibatis 的映射配置更为简洁直接,下面是一
个典型的配置文件。
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<!--模块配置-->
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement=" updateUser"/>
<property name="size" value="1000" />
</cacheModel>
<!—Statement配置-->
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
</sqlMap>
可以看到,映射文件主要分为两个部分:模块配置和Statement 配置。
模块配置包括:
. typeAlias 节点:
定义了本映射文件中的别名,以避免过长变量值的反复书写,此例中通过
typeAlias 节点为类"com.ibatis.sample.User"定义了一个别名"user",
这样在本配置文件的其他部分,需要引用"com.ibatis.sample.User" 类时,
只需以其别名替代即可。
. cacheModel 节点
定义了本映射文件中使用的Cache 机制:
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
这里申明了一个名为"userCache" 的,之后可以在cacheModel
Statement 申明中对其进行引用:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
这表明对通过id 为"getUser"的获取的数据,使用Select statement
cacheModel "userCache" 进行缓存。之后如果程序再次用此Statement
进行数据查询,即直接从缓存中读取查询结果,而无需再去数据库查询。
cacheModel 主要有下面几个配置点:
. flushInterval :
设定缓存有效期,如果超过此设定值,则将此CacheModel 的缓存清空。
. size:
本CacheModel 中最大容纳的数据对象数量。
. flushOnExecute:
指定执行特定Statement 时,将缓存清空。如updateUser 操作将更
新数据库中的用户信息,这将导致缓存中的数据对象与数据库中的实际
数据发生偏差,因此必须将缓存清空以避免脏数据的出现。
关于Cache 的深入探讨,请参见“高级特性”中的相关章节。
Statement 配置:
Statement 配置包含了数个与SQL Statement 相关的节点,分别为:
. statement
. insert
. delete
. update
. select
. procedure
其中,statement 最为通用,它可以替代其余的所有节点。除statement 之外
的节点各自对应了SQL 中的同名操作(procedure 对应存储过程)。
使用statement 定义所有操作固然可以达成目标,但缺乏直观性,建议在实际
开发中根据操作目的,各自选用对应的节点名加以申明。一方面,使得配置文件
更加直观,另一方面,也可借助DTD 对节点申明进行更有针对性的检查,以避免
配置上的失误。
各种类型的Statement 配置节点的参数类型基本一致,区别在于数量不同。如
insert、update、delete 节点无需返回数据类型定义(总是int)。
主要的配置项如下:
statement:
<statement id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
>
select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</statement>
select:
<select id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
>
select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</select>
Insert:
<insert id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
insert into t_user
(name,sex)
values
([?|#propertyName#],[?|#propertyName#])
</insert>
Update:
<update id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
UPDATE t_user
SET
name=[?|#propertyName#],
sex=[?|#propertyName#]
WHERE id = [?|#propertyName#]
</update>
Delete:
<delete id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
delete from t_user
where id = [?|#propertyName#]
</delete>
其中以“[]”包围的部分为可能出现的配置栏目。
参数描述
parameterClass 参数类。指定了参数的完整类名(包括包路径)。
可通过别名避免每次重复书写冗长的类名。
resultClass 结果类。指定结果类型的完整类名(包括包路径)
可通过别名避免每次重复书写冗长的类名。
parameterMap 参数映射,需结合parameterMap 节点对映射
关系加以定义。
对于存储过程之外的statement 而言,建议使用
parameterClass 作为参数配置方式, 一方面避
免了参数映射配置工作,另一方面其性能表现也
更加出色。
resultMap 结果映射,需结合resultMap 节点对映射关系
加以定义。
cacheModel statement 对应的Cache 模块。
对于参数定义而言,尽量使用parameterClass,即直接将POJO 作为
statement 的调用参数,这样在SQL 中可以直接将POJO 的属性作为参数加以
设定,如:
<update id="updateUser"
parameterClass="com.ibatis.sample.User">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
这里将com.ibatis.sample.User 类设定为的参数,之后,update statement
我们即可在SQL 中通过#propertyName#对POJO 的属性进行引用。如上例
中的:
SET name=#name#, sex=#sex# WHERE id=#id#
运行期,将通过调用对象的、和方法获得相ibatis User getName getSex getId
应的参数值,并将其作为SQL 的参数。
如果parameterClass 中设定的是jdk 的中的简单对象类型,如String、
Integer,ibatis 会直接将其作为SQL 中的参数值。
我们也可以将包含了参数数据的Map 对象传递给Statement ,如:
<update id="updateUser"
parameterClass="java.util.Map">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
这里传入的参数就是一个对象,将以””、””、”id”从中Map ibatis key namesex
提取对应的参数值。
同样的原理,我们也可以在resultMap 中设定返回类型为map 。
<select id="getUser"
parameterClass="java.lang.String"
resultClass="java.util.Map">
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
返回的结果将以各字段名为保存在对象中返回。key Map
在SQL 中设定参数名时,可以同时指定参数类型, 如:
SET name=#name:VARCHAR#,sex=#sex:NUMERIC# WHERE
id=#id:NUMERIC#
对于返回结果而言,如果是select 语句,建议也采用resultClass 进行定义,如:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
会自动根据语句中的字段名,调用对应的方法设定属性ibatis select POJO set
值,如上例中,ibatis 会调用setName,setSex 方法将Select 语句返回的数据赋
予相应的POJO 实例。
有些时候,数据库表中的字段名过于晦涩,而为了使得代码更易于理解,我们
希望字段映射到POJO 时,采用比较易读的属性名,此时,我们可以通过Select
的as 字句对字段名进行转义,如(假设我们的书库中对应用户名的字段为
xingming ,对应性别的字段为xingbie):
select
xingming as name,
xingbie as sex
from t_user
where id = #id#
会根据转义后的字段名进行属性映射(即调用的方法而ibatis POJO setName
不是setXingming 方法)。
parameterMap 和resultMap 实现了POJO 到数据库字段的映射配置,下面是
一个例子:
<resultMap id="get_user_result" class="user">
<result property="name" column="xingming"
jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="sex" column="xingbie"
jdbcType="int" javaType="java.lang.Integer"/>
<result property="id" column="id"
jdbcType="int" javaType="java.lang.Integer"/>
</resultMap>
<parameterMap id="update_user_para" class="redemption" >
<parameter property="name"
jdbcType="VARCHAR"
javaType="java.lang.String"
nullValue=""
/>
<parameter property="sex"
jdbcType="int"
javaType="java.lang.Integer"
nullValue=""
/>
</parameterMap>
Parameter 的nullValue 指定了如果参数为空(null)时的默认值。
之后我们即可在statement 申明中对其进行引用,如:
<procedure id="getUserList"
resultMap="get_user_result"
>
{call sp_getUserList()}
</procedure>
<procedure id="doUserUpdate"
parameterMap="update_user_para"
>
{call sp_doUserUpdate(#id#,#name#,#sex#)}
</procedure>
一般而言,对于insert 、update 、delete 、select 语句,优先采用parameterClass
和resultClass 。
parameterMap 使用较少,而resultMap 则大多用于嵌套查询以及存储过程的
处理,之所以这样,原因是由于存储过程相对而言比较封闭(很多情况下需要调用现有
的存储过程,其参数命名和返回的数据字段命名往往不符合Java 编程中的命名习惯,
并且由于我们难以通过Select SQL 的as子句进行字段名转义,无法使其自动与POJO
中的属性名相匹配)。此时,使用resultMap 建立字段名和POJO 属性名之间的映射
关系就显得非常有效。另一方面,由于通过resultMap 指定了字段名和字段类型,
ibatis 无需再通过JDBC ResultSetMetaData 来动态获取字段信息,在一定程度
上也提升了性能表现。
ibatis 高级特性
数据关联
至此,我们讨论的都是针对独立数据的操作。在实际开发中,我们常常遇到关联数
据的情况,如User 对象拥有若干Address 对象,每个Address 对象描述了对应User 的
一个联系地址,这种情况下,我们应该如何处理?
通过单独的Statement 操作固然可以实现(通过Statement 用于读取用户数据,再手
工调用另外一个Statement 根据用户ID 返回对应的Address 信息)。不过这样未免失之
繁琐。下面我们就看看在ibatis 中,如何对关联数据进行操作。
ibatis 中,提供了Statement 嵌套支持,通过Statement 嵌套,我们即可实现关联数
据的操作。
一对多关联
下面的例子中,我们首选读取t_user 表中的所有用户记录,然后获取每个用户对应
的所有地址信息。
配置文件如下:
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<typeAlias alias="address" type="com.ibatis.sample.Address"/>
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="addresses" column="id"
select="User.getAddressByUserId"/>
</resultMap>
<select id="getUsers"
parameterClass="java.lang.String"
resultMap="get-user-result">
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
<select id="getAddressByUserId"
parameterClass="int"
resultClass="address">
<![CDATA[
select
address,
zipcode
from t_address
where user_id = #userid#
]]>
</select>
</sqlMap>
对应代码:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder= new XmlSqlMapClientBuilder();
sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系统初始化完毕
List userList = sqlMap.queryForList("User.getUsers", "");
for (int i = 0; i < userList.size(); i++) {
User user = (User)userList.get(i);
System.out.println("==>" + user.getName());
for (int k = 0; k < user.getAddresses().size(); k++) {
Address addr = (Address) user.getAddresses().get(k);
System.out.println(addr.getAddress());
}
}
这里通过在resultMap 中定义嵌套查询getAddressByUserId,我们实现了关联
数据的读取。
实际上,这种方式类似于前面所说的通过两条单独的Statement 进行关联数据的读
取,只是将关联关系在配置中加以描述,由ibatis 自动完成关联数据的读取。
需要注意的是,这里有一个潜在的性能问题,也就是所谓“n+1”Select 问题。
注意上面示例运行过程中的日志输出:
……
PreparedStatement -{pstm-100001} PreparedStatement: select id, name, sex from
t_user
……
PreparedStatement -{pstm-100004} PreparedStatement: select address, zipcode from
t_address where user_id = ?
……
PreparedStatement -{pstm-100007} PreparedStatement: select address,zipcode from
t_address where user_id = ?
第一条将表中的所有数据读取出来PreparedStatement t_user (目前t_user 表中有两
条测试数据),随即,通过两次Select 操作,从t_address 表中读取两个用户所关联的
Address 记录。
如果t_user 表中记录较少,不会有明显的影响,假设t_user 表中有十万条记录,那
么这样的操作将需要100000+1 条Select 语句反复执行才能获得结果,无疑,随着记录
的增长,这样的开销将无法承受。
之所以在这里提及这个问题,目的在于引起读者的注意,在系统设计中根据具体情
况,采用一些规避手段(如使用存储过程集中处理大批量关联数据),从而避免因为这
个问题而引起产品品质上的缺陷。