数据关联
一对多关联
User实例的addresses是一个List<Address>集合
<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>
一对一关联
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="address" column="t_address.address"/>
<result property="zipCode" column="t_address.zipcode"/>
</resultMap>
<select id="getUsers" parameterClass="java.lang.String" resultMap="get-user-result">
<![CDATA[select * from t_user,t_addresswhere t_user.id=t_address.user_id ]]>
</select>
延迟加载
在运行上面的例子时,通过观察期间的日志输出顺序我们可以发现,在我们执行sqlMap.queryForList("User.getUsers", "")时,实际上ibatis只向数据库发送了一条select id,name, sex from t_user SQL。而用于获取Address记录的SQL,只有在我们真正访问address对象时,才开始执行。
这也就是所谓的延迟加载(Lazy Loading)机制。即当真正需要数据的时候,才加载数据。延迟加载机制能为我们的系统性能带来极大的提升。
Settings 节点有两个与延迟加载相关的属性lazyLoadingEnabled和enhancementEnabled,其中lazyLoadingEnabled设定了系统是否使用延迟加载机制,enhancementEnabled设定是否启用字节码强化机制(通过字节码强化机制可以为Lazy Loading带来性能方面的改进。为了使用延迟加载所带来的性能优势,这两项都建议设为"true"。
动态映射
<select id="" parameterClass="" resultClass="">
select * from test
<dynamic prepend="WHERE">
<isNotEmpty prepend="AND" property="name">
name= #name#
</dynamic>
</select>
Dynamic节点和判定节点中的prepend属性,指明了本节点中定义的SQL子句在主体SQL中出现时的前缀。其中WHERE 之后的语句是在dynamic 节点中所定义,因此以dynamic 节点的prepend设置("WHERE")作为前缀,而其中的”AND”,实际上是address属性所对应的isNotEmpty节点的prepend设定,它引领了对应节点中定义的SQL子句。至于name属性对应的isNotEmpty节点,由于ibatis会自动判定是否需要追加prepend前缀,这里(name like #name#)是WHERE 子句中的第一个条件子句,无需AND 前缀,所以自动省略。
一元判断
一元判断是针对属性值本身的判断,如属性是否为NULL,是否为空值等。
节点名称 |
节点描述 |
<isPropertyAvailable> |
参数类中,是否提供了该属性 |
<isNotPropertyAvailable> |
与<isPropertyAvailable>相反 |
<isNull> |
属性值是否为NULL |
<isNotNull> |
与<isNull>相反 |
<isEmpty> |
如果属性为Collection或String,则其size是否<1。 如果非以上两种类型,则通过String.valueof(属性值)获取其String类型值之后再判断其size是否<1。 |
<isNotEmpty> |
与<isEmpty>相反 |
二元判断
二元判断则有两个判断参数,一个是属性名,另一个则是判断值
节点名称 |
属性值与compareValue的关系 |
<isEqual> |
相等 |
<isNotEqual> |
不相等 |
<isGreaterThan> |
大于 |
<isGreaterEqual> |
大于等于 |
<isLessThan> |
小于 |
<isLessEqual> |
小于等于 |
事务管理
基于JDBC的事务管理机制
ibatis提供了自动化的JDBC事务管理机制。对于传统JDBC Connection 而言,我们获取Connection实例之后,需要调用Connection.setAutoCommit设定事务提交模式。在 AutoCommit为true的情况下,JDBC会对我们的操作进行自动提交,此时,每个JDBC操作都是一个独立的任务。为了实现整体事务的原子性,我们需要将AutoCommit 设为false,并结合Connection.commit/rollback方法进行事务的提交/回滚操作。ibatis 的所谓“自动化的事务提交机制”,即ibatis 会根据当前的调用环境,自动判断操作是否需要自动提交。如果代码没有显式的调用SqlMapClient.startTransaction()方法,则ibatis会将当前的数据库操作视为自动提交模式(AutoCommit=true)
实际上,在执行update语句时,sqlMap会检查当前的Session是否已经关联了某个数据库连接,如果没有,则取一个数据库连接,将其AutoCommit属性设为true,然后执行update 操作,执行完之后又将这个连接释放。这样,上面两次update 操作实际上先后获取了两个数据库连接,而不是我们通常所认为的两次update 操作都基于同一个JDBCConnection。这点在开发时需特别注意。
对于多条SQL 组合而成的一个JDBC 事务操作而言,必须使用startTransaction、commit和endTransaction操作以实现整体事务的原子性。
基于JTA的事务管理机制
JTA提供了跨数据库连接(或其他JTA资源)的事务管理能力。这一点是与JDBC Transaction最大的差异。JDBC事务由Connnection管理,也就是说,事务管理实际上是在JDBC Connection中实现。事务周期限于Connection的生命周期。同样,对于基于JDBC的ibatis事务管理机制而言,事务管理在SqlMapClient所依托的JDBCConnection中实现,事务周期限于SqlMapClient 的生命周期。JTA事务管理则由JTA容器实现,JTA容器对当前加入事务的众多Connection进行调度,实现其事务性要求。JTA的事务周期可横跨多个JDBCConnection生命周期。同样,对于基于JTA事务的ibatis而言,JTA事务横跨可横跨多个SqlMapClient。
为了在ibatis中使用JTA事务管理,我们需要在配置文件中加以设定:
<transactionManager type="JTA">
.........
< /transactionManager>
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
try{
sqlMap1.startTransaction();
sqlMap1.update("User.updateUser",user);
sqlMap2.update("User.updateUser",user);
sqlMap1.commitTransaction();
}finally{
sqlMap1.endTransaction();
}
上面的代码中,两个针对不同数据库的SqlMapClient 实例,在同一个JTA 事务中对user 对象所对应的数据库记录进行更新。外层的sqlMap1启动了一个全局事务,此事务将涵盖本线程内commitTransaction 之前的所有数据库操作。只要其间发生了异常,则整个事务都将被回滚。
外部事务管理
基于JTA的事务管理还有另外一个特殊情况,就是利用外部事务管理机制。对于外部事务管理,我们需要在配置文件中进行如下设定:
<transactionManager type="EXTERNAL">
……
</transactionManager>
下面是一个外部事务管理的典型示例:
UserTransaction tx = new InitialContext().lookup(“……”);
……
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
……
tx.commit();
此时,我们借助JTA UserTransaction实例启动了一个全局事务。之后的ibatis操作(sqlMap1.update 和sqlMap2.update)全部被包含在此全局事务之中,当UserTransaction提交的时候,ibatis操作被包含在事务中提交,反之,如果UserTransaction回滚,那么其间的ibatis操作也将作为事务的一部分被回滚。这样,我们就实现了ibatis外部的事务控制。
另一种外部事务管理方式是借助EJB 容器,通过EJB 的部署配置,我们可以指定
EJB方法的事务性下面是一个Session Bean的doUpdate方法,它的事务属性被申明为“Required”,EJB容器将自动维护此方法执行过程中的事务:
/**
* @ejb.interface-method
* view-type="remote"
*
* @ejb.transaction type = "Required"
**/
public void doUpdate(){
//EJB环境中,通过部署配置即可实现事务申明,而无需显式调用事务
……
sqlMap1 =xmlBuilder.buildSqlMap(db1Config);
sqlMap2 =xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser",user);
sqlMap2.update("User.updateUser",user);
……
}//方法结束时,如果没有异常发生,则事务由EJB容器自动提交。
上面的示例中,ibatis数据操作的事务管理将全部委托给EJB容器管理,由EJB容器控制其事务调度
在上面JTA事务管理的例子中,为了保持清晰,我们省略了startTransaction、commitTransaction 和endTransaction 的编写,在这种情况下,调用ibatis的事务管理方法并非必须,不过在实际开发时,请酌情添加startTransaction、commitTransaction和endTransaction 语句,这样可以获得更好的性能(如果省略了startTransaction、commitTransaction和endTransaction 语句,ibatis将为每个数据操作获取一个数据连接,就算引入了数据库连接池机制,这样的无谓开销也应尽量避免。
Cache
在特定硬件基础上(同时假设系统不存在设计上的缺漏和糟糕低效的SQL 语句)Cache往往是提升系统性能的最关键因素)。
相对Hibernate 等封装较为严密的ORM 实现而言(因为对数据对象的操作实现了较为严密的封装,可以保证其作用范围内的缓存同步,而ibatis 提供的是半封闭的封装实现,因此对缓存的操作难以做到完全的自动化同步)。
ibatis 的缓存机制使用必须特别谨慎。特别是flushOnExecute 的设定,需要考虑到所有可能引起实际数据与缓存数据不符的操作。如果不能完全确定数据更新操作的波及范围,建议避免Cache的盲目使用。
cacheModel的几个重要属性:
属性名称 |
属性作用 |
readOnly |
readOnly值的是缓存中的数据对象是否只读。这里的只读并不是意味着数据对象一 旦放入缓存中就无法再对数据进行修改。而是当数据对象发生变化的时候,如数据对 象的某个属性发生了变化,则此数据对象就将被从缓存中废除,下次需要重新从数据 库读取数据,构造新的数据对象。而 readOnly="false"则意味着缓存中的数据对象可更新。只读Cache能提供更高的读取性能,但一旦数据发生改变,则效率降低。系统设计时需根据系统的实际情况(数据发生更新的概率有多大)来决定Cache的读写策略。 |
serialize |
如果需要全局的数据缓存,CacheModel的serialize属性必须被设为true。否则数据 缓存只对当前Session有效,局部缓存对系统的整体性能提升有限。在 serialize="true"的情况下,如果有多个Session同时从Cache 中读取某个数据对象,Cache 将为每个Session返回一个对象的复本,也就是说,每个Session 将得到包含相同信息的不同对象实例。因而Session 可以对其从Cache 获得的数据进行存取而无需担心多线程并发情况下的同步冲突。 |
type |
与hibernate类似,ibatis通过缓冲接口的插件式实现,提供了多种Cache的实现机制可供选择:MEMORY、LRU、FIFO、OSCACHE |
MEMORY类型的Cache
MEMORY 类型的Cache 实现,实际上是通过Java 对象引用进行。ibatis 中,其实现类为com.ibatis.db.sqlmap.cache.memory.MemoryCacheController,MemoryCacheController 内部,使用一个HashMap来保存当前需要缓存的数据对象的引用。
这里指的是:软引用、弱引用、虚引用
软引用:当内存不足时进行回收。
弱引用:随时可能被垃圾回收机制进行回收。
虚引用:它指向的对象实际上已经被JVM 销毁(finalize方法已经被执行),只是暂时还没被垃圾回收器收回而已。PhantomReference主要用于辅助对象的销毁过程,在实际应用层研发中,几乎不会涉及。
下面是一个典型的MEMORY类型Cache配置:
<cacheModel id="user_cache" type="MEMORY">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="reference-type" value="WEAK"/>
</cacheModel>
其中flushInterval 指定了多长时间清除缓存,上例中指定每24 小时强行清空缓存区的所有内容。reference-type属性可以有以下几种配置:
1. STRONG:即基于传统的Java对象引用机制,除非对Cache显式清空(如到了flushInterval设定的时间;执行了flushOnExecute所指定的方法;或代码中对Cache执行了清除操作等),否则引用将被持续保留。此类型的设定适用于缓存常用的数据对象,或者当前系统内存非常充裕的情况。
2. SOFT:基于SoftReference的缓存实现,只有JVM 内存不足的时候,才会对缓冲池中的数据对象进行回收。此类型的设定适用于系统内存较为充裕,且系统并发量比较稳定的情况。
3. WEAK:基于WeakReference的缓存实现,当JVM 垃圾回收时,缓存中的数据对象将被JVM收回。一般情况下,可以采用WEAK的MEMORY型Cache配置。
LRU类型的Cache
当Cache达到预先设定的最大容量时,ibatis会按照“最少使用”原则将使用频率最少的对象从缓冲中清除。
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000"/>
</cacheModel>
FIFO类型的Cache
先进先出型缓存,最先放入Cache中的数据将被最先废除。可配置参数与LRU型相同:
<cacheModel id="userCache" type="FIFO">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000"/>
</cacheModel>
OSCACHE类型的Cache
与上面几种类型的Cache不同,OSCache来自第三方组织Opensymphony。在生产部署时,建议采用OSCache,OSCache 是得到了广泛使用的开源Cache实现(Hibernate 中也提供了对OSCache 的支持),它基于更加可靠高效的设计,更重要的是,最新版本的OSCache 已经支持Cache 集群。如果系统需要部署在集群中,或者需要部署在多机负载均衡模式的环境中以获得性能上的优势,那么OSCache在这里则是不二之选。
<cacheModel id="userCache" type="OSCACHE">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000"/>
</cacheModel>
之所以配置简单,原因在于,OSCache拥有自己的配置文件(oscache.properties),下面是一个典型的OSCache配置文件:
#是否使用内存作为缓存空间
cache.memory=true
#缓存管理事件监听器,通过这个监听器可以获知当前Cache的运行情况
cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroa
dcastingListener
#如果使用磁盘缓存(cache.memory=false),则需要指定磁盘存储接口实现
#cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.Disk
PersistenceListener
# 磁盘缓存所使用的文件存储路径
# cache.path=c:\\myapp\\cache
# 缓存调度算法,可选的有LRU,FIFO和无限缓存(UnlimitedCache)
# cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
#cache.algorithm=com.opensymphony.oscache.base.algorithm.UnlimitedCache
cache.algorithm=com.opensymphony.oscache.base.algorithm.LRUCache
#内存缓存的最大容量
cache.capacity=1000
# 是否限制磁盘缓存的容量
# cache.unlimited.disk=false
# 基于JMS的集群缓存同步配置
#cache.cluster.jms.topic.factory=java:comp/env/jms/TopicConnectionFactory
#cache.cluster.jms.topic.name=java:comp/env/jms/OSCacheTopic
#cache.cluster.jms.node.name=node1
# 基于JAVAGROUP的集群缓存同步配置
#cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_
ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):PING(timeout
=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000
):FD_SOCK:VERIFY_SUSPECT(timeout=1500):pbcast.NAKACK(gc_lag=50;retransm
it_timeout=300,600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=20000):
UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=fal
se):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_loc
al_addr=true)
#cache.cluster.multicast.ip=231.12.21.132
配置好之后,将此文件放在CLASSPATH 中,OSCache 在初始化时会自动找到此文件并根据其中的配置创建缓存实例。