ibatis 开发指南


ibatis 开发指南 夏昕 ibatis 开发指南............................................................................................................................2 ibatis Quick Start............................................................................................3 准备工作 ..........................................................................................................3 构建 ibatis 基础代码 ....................................................................................3 ibatis 配置.............................................................................................................9 ibatis 基础语义......................................................................................................14 XmlSqlMapClientBuilder...................................................................14 SqlMapClient ...........................................................................................14 SqlMapClient 基本操作示例 ..........................................................14 OR 映射...........................................................................................................17 ibatis 高级特性......................................................................................................24 数据关联 ........................................................................................................24 一对多关联 ............................................................................................24 一对一关联 ............................................................................................26 延迟加载 ........................................................................................................28 动态映射 ........................................................................................................29 事务管理 ........................................................................................................33 基于 JDBC 的事务管理机制................................................................33 基于 JTA 的事务管理机制...................................................................34 外部事务管理.........................................................................................36 Cache..............................................................................................................37 MEMORY 类型 Cache 与 WeakReference ........................................38 LRU 型 Cache .......................................................................................40 FIFO 型 Cache ......................................................................................41 OSCache.................................................................................................41 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 实例配置 一个典型的配置文件如下(具体配置项目的含义见后): 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 的映射代码如下: INSERT INTO t_user ( name, sex) VALUES ( #name#, #sex# ) delete from t_user where id = #value# 从上面的映射文件可以看出,通过 UPDATE t_user SET name=#name#, sex=#sex# WHERE id = #id# 可以看到,映射文件主要分为两个部分:模块配置和 Statement 配置。 模块配置包括: Ø typeAlias 节点: 定义了本映射文件中的别名,以避免过长变量值的反复书写,此例中通过 typeAlias 节点为类"com.ibatis.sample.User"定义了一个别名"user", 这样在本配置文件的其他部分,需要引用"com.ibatis.sample.User"类时, 只需以其别名替代即可。 Ø cacheModel 节点 定义了本映射文件中使用的 Cache 机制: 这 里 申 明 了一个名为"userCache" 的 cacheModel , 之后可以在 Statement 申明中对其进行引用: select * from t_user where sex = [?|#propertyName#] order by [$simpleDynamic$] Insert: insert into t_user (name,sex) values ([?|#propertyName#],[?|#propertyName#]) Update: UPDATE t_user SET name=[?|#propertyName#], sex=[?|#propertyName#] WHERE id = [?|#propertyName#] Delete: delete from t_user where id = [?|#propertyName#] 其中以“[]”包围的部分为可能出现的配置栏目。 参数 描述 parameterClass 参数类。指定了参数的完整类名(包括包路径)。 可通过别名避免每次重复书写冗长的类名。 resultClass 结果类。指定结果类型的完整类名(包括包路径) 可通过别名避免每次重复书写冗长的类名。 parameterMap 参数映射,需结合 parameterMap 节点对映射 关系加以定义。 对于存储过程之外的 statement 而言,建议使用 parameterClass 作为参数配置方式,一方面避 免了参数映射配置工作,另一方面其性能表现也 更加出色。 resultMap 结果映射,需结合 resultMap 节点对映射关系 加以定义。 cacheModel statement 对应的 Cache 模块。 对于参数定义而言,尽量使用 parameterClass,即直接将 POJO 作为 statement 的调用参数,这样在 SQL 中可以直接将 POJO 的属性作为参数加以 设定,如: UPDATE t_user SET name=#name#, sex=#sex# WHERE id = #id# 这里将 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 t_user SET name=#name#, sex=#sex# WHERE id = #id# 这里传入的参数就是一个 Map 对象,ibatis 将以 key ”name”、”sex”、”id”从中 提取对应的参数值。 同样的原理,我们也可以在 resultMap 中设定返回类型为 map。 返回的结果将以各字段名为 key 保存在 Map 对象中返回。 在 SQL 中设定参数名时,可以同时指定参数类型,如: SET name=#name:VARCHAR#, sex=#sex:NUMERIC# WHERE id=#id:NUMERIC# 对于返回结果而言,如果是 select 语句,建议也采用 resultClass 进行定义,如: 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 到数据库字段的映射配置,下面是 一个例子: Parameter 的 nullValue 指定了如果参数为空(null)时的默认值。 之后我们即可在 statement 申明中对其进行引用,如: {call sp_getUserList()} {call sp_doUserUpdate(#id#,#name#,#sex#)} 一般而言,对于 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 表中的所有用户记录,然后获取每个用户对应 的所有地址信息。 配置文件如下: 对应代码: 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 语句反复执行才能获得结果,无 疑 ,随 着 记录 的增长,这样的开销将无法承受。 之所以在这里提及这个问题,目的在于引起读者的注意,在系统设计中根据具体情 况,采用一些规避手段(如使用存储过程集中处理大批量关联数据),从而避免因为这 个问题而引起产品品质上的缺陷。 一对一关联 一对一关联是一对多关联的一种特例。这种情况下,如果采用上面的示例将导致 1+1 条 SQL 的执行。 对于这种情况,我们可以采用一次 Select 两张表的方式,避免这样的性能开销(假 设上面示例中,每个 User 只有一个对应的 Address 记录): 与此同时,应该保证 User 类中包含 address 和 zipCode 两个 String 型属性。 延迟加载 在运行上面的例子时,通过观察期间的日志输出顺序我们可以发现,在我们执行 sqlMap.queryForList("User.getUsers", "")时,实际上 ibatis 只向数据库发送 了一条 select id, name, sex from t_user SQL。而 用于获取 Address 记录的 SQL,只有在我 们真正访问 address 对象时,才开始执行。 这也就是所谓的延迟加载(Lazy Loading)机制。即当真正需要数据的时候,才加 载数据。延迟加载机制能为我们的系统性能带来极大的提升。 试想,如果我们只需要获取用户名称和性别数据,在没有延迟加载特性的情况下, ibatis 会一次将所有数据都从数据库取回,包括用户信息及其相关的地址数据,而 此 时 , 关 于 地址数据的读取操作没有意义,也就是说,我们白白在地址数据的查询读取上浪费 了大量的系统资源。延迟加载为我们妥善的处理了性能与编码上的平衡(如果没有延迟 加载,我们为了避免无谓的性能开销,只能专门为此再增加一个不读取地址信息的用户 记录检索模块,无疑增加了编码上的工作量)。 回忆之前“ibatis 配置”中的内容: Settings 节 点有两个与延迟加载相关的属性 lazyLoadingEnabled 和 enhancementEnabled,其中 lazyLoadingEnabled 设定了系统是否使用延迟加载 机制,enhancementEnabled 设定是否启用字节码强化机制(通过字节码强化机制可 以为 Lazy Loading 带来性能方面的改进。 为了使用延迟加载所带来的性能优势,这两项都建议设为"true"。 动态映射 在复杂查询过程中,我们常常需要根据用户的选择决定查询条件,这里发生变化的 并不只是 SQL 中的参数,包括 Select 语句中所包括的字段和限定条件,都可能发生变 化。典型情况,如在一个复杂的组合查询页面,我们必须根据用户的选择和输入决定查 询的条件组合。 一个典型的页面如下: 对于这个组合查询页面,根据用户选择填写的内容,我们应为其生成不同的查询语 句。 如用户没有填写任何信息即提交查询请求,我们应该返回所有记录: Select * from t_user; 如用户只在页面上填写了姓名“Erica”,我们应该生成类似: Select * from t_user where name like ‘%Erica%’ ; 的 SQL 查询语句。 如用户只在页面上填写了地址“Beijing”,我们应该生成类似: Select * from t_user where address like ‘%Beijing%”; 的 SQL。 而如果用户同时填写了姓名和地址(”Erica”&’Beijing’),则我们应生成类似: Select * from t_user where name like ‘%Erica%’ and address like ‘%Beijing%” 的 SQL 查询语句。 对于 ibatis 这样需要预先指定 SQL 语句的 ORM 实现而言,传统的做法无非通过 if-else 语句对输入参数加以判定,然后针对用户选择调用不同的 statement 定义。对于 上面这种简单的情况(两种查询条件的排列组合,共 4 种情况)而言,statement 的重 复定义工作已经让人不厌其烦,而对于动辄拥有七八个查询条件,乃至十几个查询条件 的排列组合而言,琐碎反复的 statement 定义实在让人不堪承受。 考虑到这个问题,ibatis 引入了动态映射机制,即在 statement 定义中,根据不同的 查询参数,设定对应的 SQL 语句。 还是以上面的示例为例: 通过 dynamic 节点,我们定义了一个动态的 WHERE 子句。此 WHERE 子句中将 可能包含两个针对 name 和 address 字段的判断条件。而这两个字段是否加入检索取决 于用户所提供的查询条件(字段是否为空[isNotEmpty])。 对于一个典型的 Web 程序而言,我们通过 HttpServletRequest 获得表单中的字段名 并将其设入查询参数,如: user.setName(request.getParameter("name")); user.setAddress(request.getParameter("address")); sqlMap.queryForList("User.getUsers", user); 在执行 queryForList("User.getUsers", user)时,ibatis 即根据配置文 件中设定的 SQL 动态生成规则,创建相应的 SQL 语句。 上面的示例中,我们通过判定节点 isNotEmpty,指定了关于 name 和 address 属 性的动态规则: (name like #name#) 这个节点对应的语义是,如果参数类的"name"属性非空(isNotEmpty,即非空 字符串””),则在生成的 SQL Where 字句中包括判定条件(name like #name#),其 中#name#将以参数类的 name 属性值填充。 Address 属性的判定生成与 name 属性完全相同,这里就不再赘述。 这样,我们通过在 statement 定义中引入 dynamic 节点,很简单的实现了 SQL 判 定子句的动态生成,对于复杂的组合查询而言,这将带来极大的便利。 判定节点的定义可以非常灵活,我们甚至可以使用嵌套的判定节点来实现复杂的动 态映射,如: ( name=#name# address=#address# ) 这段定义规定,只有用户提供了姓名信息时,才能结合地址数据进行查询( 如果只 提供地址数据,而将姓名信息忽略,将依然被视为全检索)。 Dynamic 节点和判定节点中的 prepend 属性,指明了本节点中定义的 SQL 子句在 主体 SQL 中出现时的前缀。 如: (name like #name#) (address like #address#) 假设"name"属性的值为“Erica”, "address"属性的值为“Beijing”,则会 生成类似下面的 SQL 子句(实际运行期将生成带占位符的 PreparedStatement,之 后再为其填充数据): WHERE (name like ‘Beijing’) AND (address like ‘Beijing’) 其中 WHERE 之后的语句是在 dynamic 节点中所定义,因此以 dynamic 节点的 prepend 设置("WHERE")作为前缀,而其中的”AND”,实际上是 address 属性所对 应的 isNotEmpty 节点的 prepend 设定,它引领了对应节点中定义的 SQL 子句。至于 name 属性对应的 isNotEmpty 节点,由于 ibatis 会自动判定是否需要追加 prepend 前缀,这里(name like #name#)是 WHERE 子句中的第一个条件子句,无需 AND 前 缀,所以自动省略。 判定节点并非仅限于 isNotEmpty,ibatis 中提供了丰富的判定定义功能。 判定节点分两类: Ø 一元判定 一元判定是针对属性值本身的判定,如属性是否为 NULL,是 否 为 空值等。 上面示例中 isNotEmpty 就是典型的一元判定。 一元判定节点有: 节点名 描述 参数类中是否提供了此属性 相反 属性值是否为 NULL 相反 如果属性为 Collection 或者 String,其 size 是否<1, 如果非以上两种类型,则通过 String.valueOf(属性值) 获得其 String 类型的值后,判断其 size 是否<1 相反。 Ø 二元判定 二元判定有两个判定参数,一是属性名,而是判定值,如 (age=#age#) 其中,property="age"指定了属性名”age”,compareValue=”18”指明 了判定值为”18”。 上 面 判 定 节 点 isGreaterThan 对 应 的 语 义是:如果 age 属 性 大于 18(compareValue),则在 SQL 中加入(age=#age#)条件。 二元判定节点有: 节点名 属性值与 compareValues 的关系 相等。 不等。 大于 大于等于 小于 小于等于 事务管理 基于 JDBC 的事务管理机制 ibatis 提供了自动化的 JDBC 事务管理机制。 对于传统 JDBC Connection 而言,我们获取 Connection 实例之后,需要调用 Connection.setAutoCommit 设定事务提交模式。 在 AutoCommit 为 true 的情况下,JDBC 会对我们的操作进行自动提交,此时,每 个 JDBC 操作都是一个独立的任务。 为了实现整体事务的原子性,我们需要将 AutoCommit 设为 false,并结合 Connection.commit/rollback 方法进行事务的提交/回滚操作。 ibatis 的所谓“自动化的事务提交机制”,即 ibatis 会根据当前的调用环境,自动 判断操作是否需要自动提交。 如果代码没有显式的调用 SqlMapClient.startTransaction()方法,则 ibatis 会将当前的数据库操作视为自动提交模式(AutoCommit=true),如: sqlMap = xmlBuilder.buildSqlMap(reader); User user = new User(); user.setId(new Integer(1)); user.setName("Erica"); user.setSex(new Integer(0)); sqlMap.update("User.updateUser", user); User user2 = new User(); user2.setId(new Integer(2)); user2.setName("Kevin"); user2.setSex(new Integer(1)); sqlMap.update("User.updateUser", user2); 在执行 sqlMap.update 的时候,ibatis 会自动判定当前的运行环境,这里 update 操作 并没有相对应的事务范围(startTransaction 和 endTransaction 代码块),于是 ibatis 将其作为一个单独的事务,并自动提交。对于上面的代码,update 执行了两次, 与其相对应,事务也提交了两次(即每个 update 操作为一个单独的事务)。 不过,值得注意的是,这里的所谓“自动判定”,可能有些误导,ibatis 并没有去 检查当前是否已经有事务开启,从而判断当前数据库连接是否设定为自动提交。 实际上,在执行 update 语句时,sqlMap 会检查当前的 Session 是否已经关联了某个 数据库连接,如果没有,则取一个数据库连接,将其 AutoCommit 属性设为 true,然后 执行 update 操作,执行完之后又将这个连接释放。这样,上面两次 update 操作实际上 先后获取了两个数据库连接,而不是我们通常所认为的两次 update 操作都基于同一个 JDBC Connection。这点在开发时需特别注意。 对 于多条 SQL 组合而成的一个 JDBC 事 务 操作而言,必须使用 startTransaction、commit 和 endTransaction 操作以实现整体事务的原子性。 如: try{ sqlMap = xmlBuilder.buildSqlMap(reader); sqlMap.startTransaction(); User user = new User(); user.setId(new Integer(1)); user.setName("Erica"); user.setSex(new Integer(0)); sqlMap.update("User.updateUser", user); User user2 = new User(); user2.setId(new Integer(2)); user2.setName("Kevin"); user2.setSex(new Integer(1)); sqlMap.update("User.updateUser", user2); sqlMap.commitTransaction(); }finally{ sqlMap.endTransaction(); } 如果 user1 或者 user2 的 update 操作失败,整 个 事 务就会在 endTransaction 时回 滚,从而保证了两次 update 操作的原子性。 基于 JTA 的事务管理机制 JTA 提供了跨数据库连接(或其他 JTA 资源)的事务管理能力。这一点是与 JDBC Transaction 最大的差异。 JDBC 事务由 Connnection 管理,也就是说,事务管理实际上是在 JDBC Connection 中实现。事务周期限于 Connection 的生命周期。同样,对于基于 JDBC 的 ibatis 事务管 理机制而言,事务管理在 SqlMapClient 所依托的 JDBC Connection 中实现,事务周期限 于 SqlMapClient 的生命周期。 JTA 事务管理则由 JTA 容器实现,JTA 容器对当前加入事务的众多 Connection 进 行调度,实现其事务性要求。JTA 的事务周期可横跨多个 JDBC Connection 生命周期。 同样,对于基于 JTA 事务的 ibatis 而言,JTA 事务横跨可横跨多个 SqlMapClient。 下面这幅图形象的说明了这个问题: 为了在 ibatis 中使用 JTA 事务管理,我们需要在配置文件中加以设定: …… 在实际开发中,我们可能需要面对分布式事务的处理,如系统范围内包含了多个数据库, 也许还引入了 JMS 上的事务管理(这在 EAI 系统实现中非常常见)。我们就需要引入 JTA 以实现系统范围内的全局事务,如下面示例中,我们同时将 user 对象更新到两个不同的数 据库: User user = new User(); user.setId(new Integer(1)); user.setName("Erica"); user.setSex(new Integer(0)); 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 的事务管理还有另外一个特殊情况,就是利用外部事务管理机制。 对于外部事务管理,我们需要在配置文件中进行如下设定: …… 下面是一个外部事务管理的典型示例: 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 将为每个数据操作获取一个数据连接,就算引入了数据库连接池机制,这样的 无谓开销也应尽量避免,具体请参见 JDBC 事务管理中的描述),并保持代码风格的统 一。 Cache 在特定硬件基础上(同时假设系统不存在设计上的缺漏和糟糕低效的 SQL 语句) Cache 往往是提升系统性能的最关键因素)。 相对 Hibernate 等封装较为严密的 ORM 实现而言(因为对数据对象的操作实现 了较为严密的封装,可以保证其作用范围内的缓存同步,而 ibatis 提供的是半封闭 的封装实现,因此对缓存的操作难以做到完全的自动化同步)。 ibatis 的缓存机制使用必须特别谨慎。特别是 flushOnExecute 的设定(见 “ibatis 配置”一节中的相关内容),需要考虑到所有可能引起实际数据与缓存数据 不符的操作。如本模块中其他 Statement 对数据的更新,其他模块对数据的更新,甚 至第三方系统对数据的更新。否则,脏数据的出现将为系统的正常运行造成极大隐患。 如果不能完全确定数据更新操作的波及范围,建议避免 Cache 的盲目使用。 结合 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. MEMORY 2. LRU 3. FIFO 4. OSCACHE MEMORY 类型 Cache 与 WeakReference MEMORY 类型的 Cache 实现,实际上是通过 Java 对象引用进行。ibatis 中,其实现类 为 com.ibatis.db.sqlmap.cache.memory.MemoryCacheController,MemoryCacheController 内部, 使用一个 HashMap 来保存当前需要缓存的数据对象的引用。 这里需要注意的是 Java2 中的三种对象引用关系: a SoftReference b WeakReference c PhantomReference 传统的 Java 对象引用,如: public void doSomeThing(){ User user = new User() …… } 当 doSomeThing 方法结束时,user 对象的引用丢失,其所占的内存空间将由 JVM 在下 次垃圾回收时收回。如果我们将 user 对象的引用保存在一个全局的 HashMap 中,如: Map map = new HashMap(); public void doSomeThing(){ User user = new User(); map.put("user",user); } 此时,user 对象由于在 map 中保存了引用,只要这个引用存在,那么 JVM 永远也不会 收回 user 对象所占用的内存。 这样的内存管理机制相信诸位都已经耳熟能详,在绝大多数情况下,这几乎是一种完美 的解决方案。但在某些情况下,却有些不便。如对于这里的 Cache 而言,当 user 对象使用 之后,我们希望保留其引用以供下次需要的时候可以重复使用,但又不希望这个引用长期保 存,如果每个对象的引用都长期保存下去的话,那随着时间推移,有限 的 内 存空 间将立即被 这些数据所消耗殆尽。最好的方式,莫过于有一种引用方式,可 以在对象没有被垃圾回收器 回收之前,依然能够访问此对象,当垃圾回收器启动时,如果此对象没有被其他对象所使用, 则按照常规对其进行回收。 SoftReference、WeakReference、PhantomReference 为上面的思路提供了有力支持。 这三种类型的引用都属于“非持续性引用”,也就是说,这种引用关系并非持续存在, 它们所代表的引用的生命周期与 JVM 的运行密切相关,而非与传统意义上的引用一样依赖 于编码阶段的预先规划。 先看一个 SoftReference 的例子: SoftReference ref; public void doSomeThing(){ User user = new User(); ref = new SoftReference(user); } public void doAnotherThing(){ User user = (User)ref.get();//通过SoftReference获得对象引用 System.out.println(user.getName()); } 假设我们先执行了 doSomeThing 方法,产生了一个 User 对象,并为其创建了一个 SoftReference 引用。 之后的某个时刻,我们调用了 doAnotherThing 方法,并通过 SoftReference 获取 User 对 象的引用。 此时我们是否还能取得 user 对象的引用?这要看 JVM 的运行情况。对于 SoftReference 而言,只有当目前内存不足的情况下,JVM 在垃圾回收时才会收回其包含的引用(JVM 并 不是只有当内存不足时才启动垃圾回收机制,何时进行垃圾回收取决于各版本 JVM 的垃圾 回收策略。如某这垃圾回收策略为:当系统目前较为空闲,且无效对象达到一定比率时启动 垃圾回收机制,此时的空余内存倒可能还比较充裕)。这里可能出现两种情况,即: Ø JVM 目前还未出现过因内存不足所引起的垃圾回收,user 对象的引用可以通过 SoftReference 从 JVM Heap 中收回。 Ø JVM 已经因为内存不足启动了垃圾回收机制,SoftReference 所包含的 user 对象的 引用被 JVM 所废弃。此时 ref.get 方法将返回一个空引用(null),对于上面 的代码而言,也就意味着这里可能抛出一个 NullPointerException。 WeakReference 比 SoftReference 在引用的维持性上来看更加微弱。无需等到内存不足的 情况,只 要 JVM 启动了垃圾回收机制,那么 WeakReference 所对应的对象就将被 JVM 回收。 也就是说,相对 SoftReference 而言,WeakReference 被 JVM 回收的概率更大。 PhantomReference 比 WeakReference 的 引 用 维 持 性 更 弱 。 与 WeakReference 和 SoftReference 不同,PhantomReference 所引用的对象几乎无法被回收重用。它指向的对象实 际上已经被 JVM 销毁(finalize 方法已经被执行),只是暂时还没被垃圾回收器收回而已。 PhantomReference 主要用于辅助对象的销毁过程,在实际应用层研发中,几乎不会涉及。 MEMORY 类型的 Cache 正是借助 SoftReference、WeakReference 以及通常意义上的 Java Reference 实现了对象的缓存管理。 下面是一个典型的 MEMORY 类型 Cache 配置: 其中 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 会按照“最少使用”原则将使用频率最少 的对象从缓冲中清除。 可配置的参数有: u flushInterval 指定了多长时间清除缓存,上 例 中 指 定 每 24 小时强行清空缓存区的所有内容。 u size Cache 的最大容量。 FIFO 型 Cache 先进先出型缓存,最先放入 Cache 中的数据将被最先废除。可 配置参数与 LRU 型相同: OSCache 与上面几种类型的 Cache 不同,OSCache 来自第三方组织 Opensymphony。可以通过以 下网址获得 OSCache 的最新版本(http://www.opensymphony.com/oscache/)。 在生产部署时,建议采用 OSCache,OSCache 是得到了广泛使用的开源 Cache 实现 (Hibernate 中也提供了对 OSCache 的支持),它基于更加可靠高效的设计,更重要的是, 最新版本的 OSCache 已经支持 Cache 集群。如果系统需要部署在集群中,或者需要部署在 多机负载均衡模式的环境中以获得性能上的优势,那么 OSCache 在这里则是不二之选。 Ibatis 中对于 OSCache 的配置相当简单: 之所以配置简单,原因在于,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 在初始化时会自动找到此 文件并根据其中的配置创建缓存实例。
还剩41页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 4 人已下载

下载pdf

pdf贡献者

kobe24bean

贡献于2012-10-22

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf