还是用代码说话,相关细节说明,详见代码注释。。
首先是位于CLASSPATH中的ibatis2的全局配置文件SqlMapConfig.xml
<?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/>放到<settings/>之前定义,否则,你就等着各种报错吧,因为它受DTD的约束嘛 -->
<properties resource="jdbc.properties"/>
<!--
cacheModelsEnabled:是否启用SqlMapClient上的缓存机制。建议设为"true"
enhancementEnabled:是否针对POJO启用字节码增强机制以提升getter/setter的调用效能,避免使用Java Reflect所带来的性能开销
同时,这也为Lazy Loading带来了极大的性能提升。建议设为"true"
lazyLoadingEnabled:是否启用延迟加载机制。建议设为"true"
errorTracingEnabled:是否启用错误日志。开发期间建议设为"true",以方便调试
maxRequests:最大并发请求数【Statement并发数】
maxSessions:最大Session数。即当前最大允许的并发SqlMapClient数
它设定必须介于maxRequests和maxTransactions之间,即maxTransactions < maxSessions =< maxRequests
maxTransactions:最大并发事务数
useStatementNamespaces:是否使用Statement命名空间。这里的命名空间指的是User.xml映射文件中,<sqlMap/>节点的namespace属性
如User.xml中的<sqlMap namespace="User">,即指定了此sqlMap节点下定义的操作均从属于"User"命名空间
当其值为true时,Statement调用需追加命名空间,如sqlMapClient.insert("User.saveBySequence", user)
否则直接通过Statement名称调用即可,如sqlMapClient.insert("saveBySequence", user);
但请注意此时需要保证所有的映射文件中,Statement定义无重名
-->
<settings cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
errorTracingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="false"/>
<!-- 定义ibatis的事务管理器 -->
<!-- 目前提供了三种选择:JDBC、JTA、EXTERNAL -->
<!-- JDBC:通过传统JDBC Connection.commit/rollback实现事务支持 -->
<!-- JTA:使用容器提供的JTA服务实现全局事务管理 -->
<!-- EXTERNAL:外部事务管理。如在EJB中使用ibatis,通过EJB的部署配置即可实现自动的事务管理机制 -->
<!-- 此时ibatis将把所有事务委托给外部容器进行管理。此外,也可以通过Spring等轻量级容器实现事务的配置化管理 -->
<transactionManager type="JDBC">
<!-- 设定ibatis运行期使用的DataSource属性 -->
<!-- dataSource节点的type属性指定了dataSource的实现类型 -->
<!-- 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.JndiDataSourceFactory -->
<dataSource type="SIMPLE">
<!-- JDBC驱动 -->
<property name="JDBC.Driver" value="${driver}" />
<!-- 数据库URL。若是MSSQL,需要在url后追加SelectMethod=Cursor以获得JDBC事务的多Statement支持 -->
<property name="JDBC.ConnectionURL" value="${url}" />
<!-- 数据库用户名 -->
<property name="JDBC.Username" value="${username}" />
<!-- 数据库用户密码 -->
<property name="JDBC.Password" value="${password}" />
<!-- 数据库连接池可维持的最大容量 -->
<property name="Pool.MaximumActiveConnections" value="10"/>
<!-- 数据库连接池中允许的挂起(idle)连接数 -->
<property name="Pool.MaximumIdleConnections" value="5"/>
<!-- 【以上子节点,适用于SIMPLE和DBCP模式】 -->
<!-- 【以下子节点,仅适用SIMPLE模式】 -->
<!-- 数据库连接池中,连接被某个任务所允许占用的最大时间,如果超过这个时间限定,连接将被强制收回。单位为毫秒 -->
<property name="Pool.MaximumCheckoutTime" value="120000"/>
<!-- 当线程试图从连接池中获取连接时,连接池中无可用连接可供使用,此时线程将进入等待状态,直到池中出现空闲连接 -->
<!-- 此参数设定了线程所允许等待的最长时间。单位为毫秒 -->
<property name="Pool.TimeToWait" value="500"/>
<!-- 数据库连接状态检测语句 -->
<!-- 某些数据库连接在某段时间持续处于空闲状态时会将其断开。而连接池管理器将通过此语句检测池中连接是否可用 -->
<!-- 检测语句应是最简化的无逻辑SQL。如select 1 from t_user,若语句执行成功,则认为此连接处于可用状态 -->
<property name="Pool.PingQuery" value="select 1 from t_user"/>
<!-- 是否允许检测连接状态 -->
<property name="Pool.PingEnabled" value="false"/>
<!-- 对持续连接时间超过设定值(毫秒)的连接进行检测 -->
<property name="Pool.PingConnectionsOlderThan" value="1"/>
<!-- 对空闲超过设定值(毫秒)的连接进行检测 -->
<property name="Pool.PingConnectionsNotUsedFor" value="1"/>
<!-- 【以下子节点,仅适用DBCP模式】 -->
<!--
等同于SIMPLE模式下的Pool.TimeToWait
<property name="Pool.MaximumWait" value="500"/>
等同于SIMPLE模式下的Pool.PingQuery
<property name="Pool.ValidationQuery" value="select 1 from t_user"/>
当数据库连接被废弃时,是否打印日志
<property name="Pool.LogAbandoned" value="true"/>
数据库连接被废弃的最大超时时间
<property name="Pool.RemoveAbandonedTimeout" value="150"/>
当连接空闲时间超过RemoveAbandonedTimeout时,是否将其废弃
<property name="Pool.RemoveAbandoned" value="true"/>
-->
</dataSource>
</transactionManager>
<!-- sqlMap节点指定了映射文件的位置,配置中允许出现多个sqlMap节点,以指定项目内所包含的所有映射文件 -->
<sqlMap resource="com/jadyer/model/User.xml" />
</sqlMapConfig>
<!--
【JNDI由于大部分配置是在应用服务器中进行,因此ibatis中的配置相对简单】
【使用JDBC事务管理的JNDI DataSource配置】
<transactionManager type="JDBC">
<dataSource type="JNDI">
<property name="DataSource" value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
【使用JTA事务管理的JNDI DataSource配置】
<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>
-->
然后是用到的实体类User.java
package com.jadyer.model;
import java.util.Date;
public class User {
private Integer id; //编号。。本人比较喜欢使用Integer,个人感觉更加OO
private String name; //姓名
private Date birth; //生日。。java.sql.Date extends java.util.Date
/*--三个属性的setter和getter略--*/
public User(){}
public User(String name, Date birth){
this.name = name;
this.birth = birth;
}
public User(Integer id, String name, Date birth){
this.id = id;
this.name = name;
this.birth = birth;
}
@Override
public String toString() {
return "id=" + id + "/tname=" + name + "/tbirth=" + birth;
}
}
与实体类位于同一包下的映射文件User.xml
<?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="User">
<!-- 模块配置 -->
<!-- typeAlias节点,定义了本映射文件中的别名,以避免过长变量值的反复书写 -->
<!-- 这样在本配置文件的其他部分,需要引用"com.jadyer.model.User"类时,只需以其别名替代即可 -->
<typeAlias alias="user" type="com.jadyer.model.User"/>
<!-- Statement配置 -->
<!-- Statement配置包含了数个与SQL Statement相关的节点:statement、insert、delete、update、select、procedure -->
<!-- 其中statement最为通用,它可以替代其余的所有节点。除statement之外的节点各自对应SQL中的同名操作【procedure对应存储过程】 -->
<!-- 使用statement定义所有操作固然可以达成目标,但缺乏直观性,建议在实际开发中根据操作目的,各自选用对应的节点名加以申明 -->
<!-- 一方面,使得配置文件更加直观,另一方面,也可借助DTD对节点申明进行更有针对性的检查,以避免配置上的失误 -->
<insert id="save" parameterClass="user">
insert into T_USER(id, name, birth) values(#id#, #name#, #birth#)
</insert>
<insert id="saveBySequence" parameterClass="user">
<selectKey resultClass="int" keyProperty="id">
select SEQUENCE_T_USER.nextVal as id from dual
</selectKey>
insert into T_USER(id, name, birth) values(#id#, #name#, #birth#)
</insert>
<update id="update" parameterClass="user">
update T_USER set name=#name#, birth=#birth# where id=#id#
</update>
<delete id="deleteById" parameterClass="Integer">
delete from T_USER where id = #id#
</delete>
<!-- 这里的SQL即实际数据库支持的SQL语句,SQL所需的参数【#id#】在运行期会由传入的user对象的属性【id】填充 -->
<!-- ibatis在运行期会读取id为"findById"的节点的SQL定义,并调用指定的user对象对应的getter()方法获取对应的属性值 -->
<!-- 并用此属性值,对SQL中的参数进行填充,然后提交数据库执行 -->
<!-- id:用于指定操作ID -->
<!-- 在代码中借助该ID的属性值,来执行此节点所定义的操作,如sqlMapClient.insert("save", user) -->
<!-- 并且该id的不同属性值,还可以用来区分同一个配置文件中的两个同名节点。如这里的两个<insert/>节点 -->
<!--parameterClass:用于指定执行该节点中的操作时,需要传入的参数的类型 -->
<!-- 这里insert操作是以com.jadyer.model.User类型的对象作为参数,目的是将提供的User实例添加到数据库 -->
<!-- 在parameterClass="user"中,其user为"com.jadyer.model.Person"类的别名 -->
<!-- 别名可通过typeAlias节点指定,如示例配置文件中的<typeAlias alias="user" type=".....User"/> -->
<!-- resultClass:指定所返回的结果类型。指定结果类型的包含路径的完整类名,可通过别名避免每次重复书写冗长的类名 -->
<!-- cacheModel:表明通过id为"findById"的Select statement获取的数据,使用"userCache"进行缓存 -->
<!-- 以后如果程序再此用次Statement进行数据查询,即直接从缓存中读取查询结果,而无需再去数据库查询 -->
<!-- parameterMap:参数映射,需结合parameterMap节点对映射关系加以定义 -->
<!-- 对于存储过程之外的statement而言,建议使用parameterClass作为参数配置方式 -->
<!-- 一方面避免了参数映射配置工作,另一方面其性能表现也更加出色 -->
<!-- resultMap:结果映射,需结合resultMap节点对映射关系加以定义 -->
<!-- 【另外】使用<![CDATA[……]]>节点,可以避免SQL中与XML规范相冲突的字符对XML映射文件的合法性造成影响 -->
<select id="findById" parameterClass="int" resultClass="user">
<![CDATA[
select * from T_USER where id = #id#
]]>
</select>
<select id="findAll" resultClass="user">
select * from T_USER
</select>
<select id="findByName11" parameterClass="java.lang.String" resultClass="user">
select * from T_USER where name like '$name$'
</select>
<select id="findByName22" parameterClass="java.lang.String" resultClass="user">
select * from T_USER where name like '%$name$%'
</select>
</sqlMap>
位于CLASSPATH中的储存数据库连接信息的属性文件jdbc.properties
driver = oracle.jdbc.OracleDriver
url = jdbc:oracle:thin:@127.0.0.1:1521:jadyer
username = scott
password = jadyer
位于CLASSPATH中的用于管理日志输出行为的属性文件log4j.properties
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.logger.com.ibatis=debug
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug,stdout
然后是使用了JUnit4.x的单元测试类UserDaoIbatisTest.java
package com.jadyer.test;
import java.io.IOException;
import java.io.Reader;
import java.sql.Date;
import java.sql.SQLException;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.jadyer.model.User;
/**
* 本程序用到的Jar文件为ibatis-2.3.4.726.jar和log4j-1.2.16.jar
* 另外,为了让开发过程更加直观,可以打开ibatis日志,以便观察ibatis运作的细节
* ibatis采用Apache common_logging,并结合Apache log4j作为日志输出组件
* 所以,我们要在CLASSPATH中创建log4j.properties文件,并引入log4j.jar
*/
public class UserDaoIbatisTest {
private static SqlMapClient sqlMapClient = null; //SqlMapClient是ibatis的核心组件,提供数据操作的基础平台
/**
* 读取ibatis配置文件,连接数据库,并创建SqlMapClient
*/
@BeforeClass
public static void readConfig() throws IOException {
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close();
}
/**
* 添加用户,其ID是通过实体类自行设定的
*/
@Test
public void save() throws SQLException {
User user = new User(22, "张起灵", Date.valueOf("2011-03-26"));
sqlMapClient.insert("save", user);
}
/**
* 添加用户,其ID是由序列生成的
*/
@Test
public void saveBySequence() throws SQLException {
User user = new User("闷油瓶", Date.valueOf("1988-03-26"));
sqlMapClient.insert("saveBySequence", user);
System.out.println("id = " + user.getId());
}
/**
* 更新用户信息
* ibatis有一个自动化的事务提交机制,即根据当前的调用环境,自动判断操作是否需要自动提交
* 若未显式调用SqlMapClient.startTransaction(),则ibatis会默认当前的数据库操作为AutoCommit=true
* 注意:这里的所谓"自动判定",并不是指ibatis去检查当前是否已开启事务,从而判断当前数据库连接是否设定为自动提交
* 注意:实际上,在执行update语句时,sqlMapClient会检查当前的Session是否已经关联了某个数据库连接
* 注意:如果没有,则取一个数据库连接,将其AutoCommit属性设为true,然后执行update操作,执行完之后又释放这个连接
* 注意:即,下面的两次update实际上先后获取了两个数据库连接,即,下面的两次update并不是基于同一个JDBC Connection
* 总结:对于多条SQL组合的一个JDBC事务操作而言,必须使用start、commit和endTransaction操作以实现整体事务的原子性
*/
@Test
public void update() throws SQLException {
User user1 = new User(22, "陈文锦", Date.valueOf("1988-03-26"));
sqlMapClient.update("update", user1);
User user2 = new User(22, "霍玲", Date.valueOf("1988-03-12"));
sqlMapClient.update("update", user2);
}
/**
* 删除指定ID的用户资料
*/
@Test
public void deleteById() throws SQLException {
sqlMapClient.delete("deleteById", 22);
}
/**
* 查询指定ID的用户资料
*/
@Test
public void findById() throws SQLException {
User user = (User)sqlMapClient.queryForObject("findById", 2);
System.out.println(user);
}
/**
* 查询所有的用户信息
*/
@Test
public void findAll() throws SQLException {
List<User> list = sqlMapClient.queryForList("findAll");
for(User user : list){
System.out.println(user);
}
}
/**
* 根据提供的用户名,模糊查询用户信息
*/
@Test
public void findByName11() throws SQLException {
List<User> list = sqlMapClient.queryForList("findByName11", "%寻%");
for(User user : list){
System.out.println(user);
}
}
@Test
public void findByName22() throws SQLException {
List<User> list = sqlMapClient.queryForList("findByName22", "寻");
for(User user : list){
System.out.println(user);
}
}
}
最后是数据库脚本文件t_user.sql
-- Oracle 11g
-- Create table
create table t_user(
id number,
name varchar2(10),
birth date
);
-- Create sequence
create sequence sequence_t_user increment by 1 start with 1 nomaxvalue nocycle;
-- Add data
insert into t_user values(sequence_t_user.nextval, '玄寻玉', to_date('2011-3-22', 'yyyy-mm-dd'));
insert into t_user values(sequence_t_user.nextval, '李寻欢', to_date('2011-3-23', 'yyyy-mm-dd'));
insert into t_user values(sequence_t_user.nextval, '易天寻', to_date('2011-3-24', 'yyyy-mm-dd'));
insert into t_user values(sequence_t_user.nextval, '寻无迹', to_date('2011-3-25', 'yyyy-mm-dd'));
另附:关于ibatis2.x中的动态查询的配置方式
<?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>
<typeAlias alias="user" type="com.jadyer.model.User"/>
<select id="findByNameOrSex" parameterClass="user" resultClass="user">
select * from t_user
<dynamic prepend="WHERE">
<!-- prepend属性,指明了本节点中定义的SQL子句在主体SQL中出现时的前缀 -->
<!-- 而name属性对应的isNotEmpty节点,ibatis会自动判定是否需要追加prepend前缀 -->
<!-- 这里name = #name#是WHERE子句中的第一个条件子句,无需AND前缀,所以自动省略 -->
<isNotEmpty prepend="AND" property="name">
name = #name#
</isNotEmpty>
<!-- 如果参数类的sex属性非空,则生成的SQL Where子句中包括判定条件sex = #sex# -->
<!-- 其中#sex#将以参数类的sex属性填充 -->
<!-- 实际运行期将生成带占位符的PreparedStatement,之后再为其填充数据 -->
<isNotEmpty prepend="AND" property="sex">
sex = #sex#
</isNotEmpty>
</dynamic>
</select>
<select id="findByAddressUseName" parameterClass="user" resultClass="user">
select * from t_user
<dynamic prepend="WHERE">
<!-- 判定节点的定义可以非常灵活,甚至可以使用嵌套的判定节点来实现复杂的动态映射 -->
<!-- 这段定义规定,只有提供了name,才能结合address进行查询。若只提供address,而未提供name,将视为全检索 -->
<isNotEmpty prepend="AND" property="name">
(name = #name#
<isNotEmpty prepend="AND" property="address">
address = #address#
</isNotEmpty>
)
</isNotEmpty>
</dynamic>
</select>
<select id="findByAge" parameterClass="int" resultClass="user">
select * from t_user
<dynamic prepend="WHERE">
<!-- 这是一个二元判定。二元判定有两个判定参数,一是属性名,一是判定值。如下所示 -->
<!-- 其中,property="age"指定了属性名"age",compareValue="22"指明了判定值为"18" -->
<!-- 下面定义的语义就是:若age属性大于22(compareValue),则在SQL中加入(age=#age#)条件 -->
<isGreaterThan prepend="AND" property="age" compareValue="22">
age = #age#
</isGreaterThan>
</dynamic>
</select>
</sqlMap>
<!--
=====================================================================================================================
在复杂查询过程中,常常要根据用户的选择决定查询条件,这里发生变化的并不只是SQL中的参数
还包括Select语句中所包括的字段和限定条件。比如在一个复杂的组合查询页面,根据用户的选择和输入决定查询的条件组合
对于ibatis这样需要预先指定SQL语句的ORM实现而言,传统的做法无非通过if-else语句对输入参数加以判定
然后针对用户选择调用不同的statement定义。但此时就需要重复定义很多的statement,如此琐碎,令人不堪
考虑到这个问题,ibatis引入了动态映射机制,即在statement定义中,根据不同的查询参数,设定对应的SQL语句
=====================================================================================================================
判定节点并非仅限于isNotEmpty,ibatis中提供了丰富的判定定义功能。判定节点分两类:一元判定、二元判定
一元判定是针对属性值本身的判定,如属性是否为NULL,是否为空值等。以下是比较典型的一元判定
<isPropertyAvailable> 参数类中是否提供了此属性
<isNotPropertyAvailable> 与<isPropertyAvailable>相反
<isNull> 属性值是否为NULL
<isNotNull> 与<isNull>相反
<isEmpty> 如果属性为Collection或者String,其size是否小于壹
如果非以上两种类型,则通过String.valueOf(属性值)获得其String类型的值后,判断其size是否小于壹
<isNotEmpty> 与<isEmpty>相反
=====================================================================================================================
其它的二元判定节点
<isEqual> 相等
<isNotEqual> 不等
<isGreaterThan> 大于
<isGreaterEqual> 大于等于
<isLessThan> 小于
<isLessEqual> 小于等于
=====================================================================================================================
-->
另附:关于ibatis2.x中的缓存机制的相关资料
<?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>
<typeAlias alias="user" type="com.jadyer.model.User"/>
<!-- cacheModel节点,定义了本映射文件中使用的Cache机制 -->
<!-- 这里申明了一个名为"userCache"的cacheModel,之后可以在Statement申明中对其进行引用 -->
<!-- 与Hibernate类似,ibatis通过缓冲接口的插件式实现,提供了多种Cache的实现机制可供选择 -->
<!-- 目前有四种Cache实现:MEMORY、LRU、FIFO、OSCACHE,关于四种实现的细微区别,请Google -->
<cacheModel type="LRU" id="userCache">
<!-- 设定缓存有效期。如果超过此设定值,则按照指定的type=""方式清空缓存 -->
<flushInterval hours="24"/>
<!-- 指定执行特定Statement时,清空缓存。如Statement中的"update"操作将更新数据库的用户信息 -->
<!-- 这将导致缓存中的数据对象与数据库中的实际数据发生偏差,因此必须将缓存清空以避免脏数据的出现 -->
<flushOnExecute statement="update"/>
<!-- 本CacheModel中最大容纳的数据对象数量 -->
<property name="size" value="1000"/>
</cacheModel>
<select id="findById" parameterClass="int" resultClass="user" cacheModel="userCache">
<![CDATA[
select * from T_USER where id = #id#
]]>
</select>
<update id="update" parameterClass="user">
update T_USER set name=#name#, birth=#birth# where id=#id#
</update>
</sqlMap>
<!--
====================================================================================================================
ibatis的缓存机制使用必须特别谨慎。特别是flushOnExecute的设定,需要考虑到所有可能引起实际数据与缓存数据不符的操作
比如当前模块中的Statement对数据的更新,其他模块对数据的更新,甚至第三方系统对数据的更新
否则,脏数据的出现将为系统的正常运行造成极大隐患,若不能完全确定数据更新操作的波及范围,建议避免Cache的盲目使用
====================================================================================================================
【readOnly属性】
readOnly是指缓存中的数据对象是否只读。这里的只读并不是意味着数据对象一旦放入缓存中就无法再对数据进行修改
而是当数据对象发生变化的时候,如数据对象的某个属性发生了变化,则此数据对象就将被从缓存中废除
下次需要重新从数据库读取数据,构造新的数据对象。readOnly="false"则意味着缓存中的数据对象可以被更新
只读Cache能够提供更高的读取性能,但一旦数据发生改变,则效率降低,系统设计时需根据系统的实际情况
比如,数据发生更新的概率有多大,等等,来决定Cache的读写策略
====================================================================================================================
【serialize属性】
如果需要全局的数据缓存,则必须serialize="true",否则数据缓存只对当前Session(可简单理解为当前线程)有效
当serialize="true"时,若有多个Session同时从Cache中读取某个数据对象,Cache将为每个Session返回一个对象的副本
即,每个Session将得到包含相同信息的不同对象实例。因而Session可以对从Cache获得的数据进行存取
而无需担心多线程并发情况下的同步冲突。另附:局部缓存对系统的整体性能提升有限
====================================================================================================================
【MEMORY型Cache】
请参见夏昕《IBATIS2.0开发指南_v1.0》的第40页
【LRU型Cache】
当Cache达到预先设定的最大容量时,ibatis会按照"最少使用"的原则,将使用频率最少的对象从缓冲中清除
对于较长期间内,经常使用某些特定对象的情况,如在PaginatedList和常用的查询关键字结果集中翻页,LRU Cache是个不错的选择
【FIFO型Cache】
先进先出型缓存,最先放入Cache中的数据将被最先废除
对于短时间内持续引用特定的查询而后很可能不再使用的情况,FIFO Cache是很好的选择
【OSCache型Cache】
OSCache来自第三方组织Opensymphony,可以通过http://www.opensymphony.com/oscache/获得OSCache的最新版本
在生产部署时,建议采用OSCache,OSCache是得到了广泛使用的开源Cache实现,在Hibernate中也提供了对OSCache的支持
它基于更加可靠高效的设计,更重要的是,最新版本的OSCache已经支持Cache集群
如果系统需要部署在集群中,或者需要部署在多机负载均衡模式的环境中以获得性能上的优势,那么OSCache在这里则是不二之选
OSCache还拥有自己的配置文件,即oscache.properties,下面是一个典型的OSCache配置文件
配置好之后,将此文件放在CLASSPATH中,OSCache在初始化时会自动找到此文件,并根据其中的配置创建缓存实例
====================================================================================================================
# 是否使用内存作为缓存空间
cache.memory=true
# 缓存管理事件监听器,通过这个监听器可以获知当前Cache的运行情况
cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroadcastingListener
# 如果使用磁盘缓存(cache.memory=false),则需要指定磁盘存储接口实现
# cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
# 磁盘缓存所使用的文件存储路径
# 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;retransmit_timeout=300,600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=20000):
UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=false):
pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)
#cache.cluster.multicast.ip=231.12.21.132
====================================================================================================================
-->