一,iBatis简介
相对Hibernate和Apache OJB 等“一站式”ORM(对象关系映射 Object Relational Mapping,简称ORM)解决方案而言,ibatis 是一种“半自动化”的ORM实现。
纵观目前主流的ORM,无论Hibernate 还是Apache OJB,都对数据库结构提供了较为完整的封装,提供了从POJO 到数据库表的全套映射机制,程序员往往只需定义好了POJO 到数据库表的映射关系,即可通过Hibernate或者OJB 提供的方法完成持久层操作,可以说是“一站式”的服务,程序员甚至不需要对SQL 的熟练掌握。但是当遇到这样的情况时Hibernate就显得没有那么好用了
1. 系统的部分或全部数据来自现有数据库,处于安全考虑,只对开发团队提供几条Select SQL(或存储过程)以获取所需数据,具体的表结构不予公开。
2. 系统数据处理量巨大,性能要求高,需要高度优化的SQL语句才能达到系统性能要求。
3. IBatis相较于Hibernate比较容易上手,掌握。
“半自动化”的ibatis,却刚好解决了这个问题。这里的“半自动化”,是相对Hibernate等提供了全面的数据库封装机制的“全自动化”ORM 实现而言,“全自动”ORM 实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行。而ibatis 的着力点,则在于POJO 与SQL之间的映射关系。也就是说,ibatis并不会为程序员在运行期自动生成SQL 执行。具体的SQL 需要程序员编写,然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。
总结:Hibernate对ORM提供了“一站式,全自动”的封装,实现了POJO和数据库表之间的映射,程序员可以不用编写具体的sql语句,也能完成持久层的操作,但是不够灵活。iBatis实现了POJO和sql语句的映射,通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。所以需要程序员编写具体的sql语句,但是也较灵活,效率更高。
二,具体运用(与spring结合使用)
1 、导入ibatis相关的jar 包,ibatis-2.3.0.677.jar 、mysql-connector-java-5.1.6-bin.jar
2、spring加载ibatis相关xm配置l文件SqlMapClinetFactory.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="business_SqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:sql-map-config.xml"/>
<property name="dataSource" ref="business_datasource"/>
<property name="lobHandler" ref="lobHandler"/>
</bean>
<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"/>
<!-- 支持 @Transactional 标记 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="business_datasource" />
</bean>
</beans>
3、sql-map-config.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="false"
errorTracingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="true"/>
<sqlMap resource="com/remote3c/ibatis/customer.xml"/>
</sqlMapConfig>
相关节点
参数
|
描述
|
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
调用需追加命名空间,如:
sqlMap.update(
"User.updateUser"
,use
r);
否则直接通过
Statement
名称调用即可,如:
sqlMap.update(
"updateUser"
,user);
但请注意此时需要保证所有映射文件中,
Statement
定义无重名。
|
<?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="customer">
<!--别名-->
<typeAlias alias="customer" type="com.remote3c.bean.Customer"/>
<!-- 根据查询所有分类信息 -->
<select id="findAll" resultClass="customer" >
select * from customer
</select>
<insert id="insert" parameterClass="customer" >
<![CDATA[
insert into customer
(
name,
address
) values
(
#name#,
#address#
);
]]>
</insert>
<update id="update" parameterClass="customer" >
update customer set
name=#name#
,address=#address#
where customer_id=$customer_id$
</update>
</sqlMap>
以update节点为例:
⑴ ID
指定了操作ID,之后我们可以在代码中通过指定操作id 来执行此节点所定义的操作,如:
this.getSqlMapClient().update("customer.update", customer);
ID设定使得在一个配置文件中定义两个同名节点成为可能(两个update节点,以不同id区分)
⑵ parameterClass
指 定 了操作所需的参数类型, 此例中update 操作以com.remote3c.bean.Customer类型的对象作为参数,目标是将提供的Customer实例更新到数据库。parameterClass="customer"中,customer为“com.remote3c.bean.Customerr”类的别名,别名可通过typeAlias节点指定,如示例配置文件中的:<typeAlias alias="user" type="com.ibatis.sample.User"/>
⑶ <![CDATA[……]]>
通过<![CDATA[……]]>节点,可以避免SQL 中与XML 规范相冲突的字符对XML映射文件的合法性造成影响。在sql语句中有大于小于符合“> <”时要使用这个节点,否则会报错。
(4) SQL中所需的用户名参数,“#name#”在运行期会由传入的user对象的name属性自动填充。
(5) SQL 中所需的用户性别参数“#sex#”,将在运行期由传入的user 对象的sex属性自动填充。
(6) SQL中所需的条件参数“#id#”,将在运行期由传入的user对象的id属性填充。
(7)#和$的区别
1,
$ 的作用实际上是字符串拼接,
select * from $tableName$
等效于
StringBuffer sb = new StringBuffer(256);
sb.append("select * from ").append(tableName);
sb.toString();
结果 :select * from user;
如果这里用#tanbleName#,结果会变成这个样子 select * from 'user';
2,
#用于变量替换
select * from table where id = #id#
等效于
prepareStement = stmt.createPrepareStement("select * from table where id = ?")
prepareStement.setString(1,'abc');
结果就是 select * from table where id ='abc';
3,一般传参数能用#的就别用$. 直观的说
#str# 出来的效果是 'str'
$str$ 出来的效果是 str
如果字段类型为字符串,则必须使用#str# 传参,使用$str$会报错。
4,#能防止sql注入。
三、高级特性
1、一对多的关联
在实际开发中,我们常常遇到关联数据的情况,如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>
result常见的有两种类型,resultClass和resultMap,这里通过在resultMap 中定义嵌套查询getAddressByUserId,我们实现了关联数据的读取。实际上,这种方式类似于前面所说的通过两条单独的Statement 进行关联数据的读取,只是将关联关系在配置中加以描述,由ibatis自动完成关联数据的读取。
需要注意的是,这里有一个潜在的性能问题,也就是所谓“n+1”Select问题。,假设t_user 表中有十万条记录,那么这样的操作将需要100000+1 条Select语句反复执行才能获得结果,无疑,随着记录的增长,这样的开销将无法承受。
2、动态的映射查询条件
<select id="findPageList" parameterClass="com.bean.Member" resultClass="com.bean.Member">
SELECT * from member
WHERE 1=1
<isNotEmpty property="mac">
and m.mac = '#mac#'
</isNotEmpty>
<isNotEmpty property="name">
and m.name = '#name#'
</isNotEmpty>
<isNotEmpty property="userName">
and m.userName = '#userName#'
</isNotEmpty>
还有
<isNotEmpty property="dealerId">
<isNotEqual property="dealerId" compareValue="-1">
and m.dealerId = '#dealerId#'
</isNotEqual>
</isNotEmpty>
节点名 |
描述
|
<isNull>
|
属性值是否为
NULL
|
<isNotNull>
|
与
<isNull>
相反
|
<isEmpty>
|
如果属性为
Collection
或者
String
,其
size
是否
<1
,
如果非以上两种类型,则通过
String.valueOf(
属性值
)
获得其
String
类型的值后,判断其
size
是否
<1
|
<isNotEmpty>
|
与
<isEmpty>
相反。
|
3、缓存 Cache
在特定硬件基础上(同时假设系统不存在设计上的缺漏和糟糕低效的SQL 语句)Cache往往是提升系统性能的最关键因素)。相对Hibernate 等封装较为严密的ORM 实现而言(因为对数据对象的操作实现了较为严密的封装,可以保证其作用范围内的缓存同步,而ibatis 提供的是半封闭的封装实现,因此对缓存的操作难以做到完全的自动化同步)。ibatis 的缓存机制使用必须特别谨慎。特别是flushOnExecute 的设定(见“ibatis配置”一节中的相关内容),需要考虑到所有可能引起实际数据与缓存数据不符的操作。如本模块中其他Statement对数据的更新,其他模块对数据的更新,甚至第三方系统对数据的更新。否则,脏数据的出现将为系统的正常运行造成极大隐患。如果不能完全确定数据更新操作的波及范围,建议避免Cache的盲目使用。
定义了本映射文件中使用的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操作将更新数据库中的用户信息,这将导致缓存中的数据对象与数据库中的实际数据发生偏差,因此必须将缓存清空以避免脏数据的出现
高级一点的属性
结合cacheModel来看:
<cacheModel
id="product-cache"
type ="LRU"
readOnly="true"
serialize="false">
</cacheModel>
可以看到,Cache有如下几个比较重要的属性:
Ø readOnly
Ø serialize
Ø type
readOnly
readOnly值的是缓存中的数据对象是否只读。这里的只读并不是意味着数据对象一旦放入缓存中就无法再对数据进行修改。而是当数据对象发生变化的时候,如数据对
象的某个属性发生了变化,则此数据对象就将被从缓存中废除,下次需要重新从数据库读取数据,构造新的数据对象。而 readOnly="false"则意味着缓存中的数据对象可更新,如user 对象的name属性发生改变。只读Cache能提供更高的读取性能,但一旦数据发生改变,则效率降低。系统设计时需根据系统的实际情况(数据发生更新的概率有多大)来决定Cache的读写策略。
serialize
如果需要全局的数据缓存,CacheModel的serialize属性必须被设为true。否则数据缓存只对当前Session(可简单理解为当前线程)有效,局部缓存对系统的整体性能提
升有限。在 serialize="true"的情况下,如果有多个Session同时从Cache 中读取某个数据对象,Cache 将为每个Session返回一个对象的复本,也就是说,每个Session 将
得到包含相同信息的不同对象实例。因而Session 可以对其从Cache 获得的数据进行存取而无需担心多线程并发情况下的同步冲突。
Cache Type:
与hibernate类似,ibatis通过缓冲接口的插件式实现,提供了多种Cache的实现机
制可供选择:
1.LRU --当Cache达到预先设定的最大容量时,ibatis会按照“最少使用”原则将使用频率最少的对象从缓冲中清除
2.FIFO --先进先出型缓存,最先放入Cache中的数据将被最先废除。可配置参数与LRU型相同
3.OSCACHE --与上面几种类型的Cache不同,OSCache来自第三方组织Opensymphony。