相对于Hibernate一站式的ORM解决方案而言,iBatis之一种半自动化的ORM实现;在一定情况下,这种一站式的ORM解决方案有时候会有一定的问题出现,例如:
1.开发规范中要求,所有业务部分的数据库操作,必须在数据库层由存储过程实现;
2.系统数据量较大,对性能有苛刻要求,需要执行我们高度优化过的SQL语句。
面对这样的需求,我们发现iBatis是一个很好的选择,我们面对的是纯粹的java对象,同时SQL语句由我们自己来编写。
一、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.mysql.jdbc.Driver" /> <property name="JDBC.ConnectionURL" value="jdbc:mysql://127.0.0.1:3309/sample" /> <property name="JDBC.Username" value="root" /> <property name="JDBC.Password" value="root" /> <property name="Pool.MaximumActiveConnections" value="10" /> <property name="Pool.MaximumIdleConnections" value="5" /> <property name="Pool.MaximumCheckoutTime" value="120000" /> <property name="Pool.TimeToWait" value="500" /> <property name="Pool.PingQuery" value="select 1 from sample" /> <property name="Pool.PingEnabled" value="false" /> <property name="Pool.PingConnectionsOlderThan" value="1" /> <property name="Pool.PingConnectionsNotUsedFor" value="1" /> </dataSource> </transactionManager> <sqlMap resource="IBatisTest/UserInfo.xml" /> </sqlMapConfig>
配置说明:cacheModelsEnabled:是否启用SqlMapClient上的缓存机制,建议为true
enhancementEnabled:是否针对POJO启动字节码增强机制以提升get/set调用效能,避免反射带来的性能开销,建议为true
lazyLoadingEnabled:是否启用延迟加载机制,建议为true
errorTracingEnabled:是否启用错误日志,开发期间建议为true
maxRequests:最大并发请求数,建议为true
maxTransactions:最大并发事务数,建议为true
maxSessions:最大session数,即当前允许的最大并发SqlMapClient数,maxSessions数必须大于maxTransactions,小于maxRequests
useStatementNamespaces:是否使用Statement命名空间,指映射文件中,SqlMap节点的UserInfo属性,
如果设置为true,调用的时候需要写成:sqlMap.update("UserInfo.upDateUserInfo", userInfo);
2.持久化类
package IBatisTest; import java.io.Serializable; public class UserInfo implements Serializable { /** * */ private static final long serialVersionUID = -4774273886584667516L; private Integer userId; private String userName; private Integer userSex; public UserInfo() { } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getUserSex() { return userSex; } public void setUserSex(Integer userSex) { this.userSex = userSex; } }
3.关系-对象映射文件
<?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="UserInfo"> <typeAlias alias="UserInfo" type="IBatisTest.UserInfo" /> <insert id="addUser" parameterClass="UserInfo" > <![CDATA[ insert into userinfo (username,usersex) values(#userName#,#userSex#) ]]> </insert> <select id="getUserInfo" parameterClass="java.lang.String" resultClass="UserInfo" > <![CDATA[ select userId,usersex from UserInfo where username = #userName# ]]> </select> <select id="getAllUser" resultClass="UserInfo" > <![CDATA[ select userId,userName,usersex from UserInfo ]]> </select> <update id="upDateUserInfo" parameterClass="UserInfo"> <![CDATA[ update userinfo set usersex=#userSex# where username = #userName# ]]> </update> <delete id="deleteUser" parameterClass="java.lang.Integer"> <![CDATA[ delete from userinfo where userid=#userId# ]]> </delete> </sqlMap>
其中映射文件中的#参数#对应的名称是持久话类中的属性名,iBatis会利用反射去get/set操作;
4.iBatis操作数据
package IBatisTest; import java.io.IOException; import java.io.Reader; import java.sql.SQLException; import java.util.Iterator; import java.util.List; import java.util.Map; import com.ibatis.common.resources.Resources; import com.ibatis.common.util.PaginatedList; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapClientBuilder; public class test { @SuppressWarnings({ "unchecked", "unchecked" }) public static void main(String[] args) { String resource = "IBatisData/SqlMapConfig.xml"; Reader reader; try { reader = Resources.getResourceAsReader(resource); SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader); try { sqlMap.startTransaction(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } UserInfo userInfo = new UserInfo(); userInfo.setUserName("aaa"); userInfo.setUserSex(111); // userInfo.setUserId(2); List<UserInfo> userResult = null; UserInfo user2 = null; try { sqlMap.delete("deleteUser", userInfo.getUserId()); sqlMap.insert("addUser", userInfo); sqlMap.update("upDateUserInfo", userInfo); userResult = sqlMap.queryForList("getUserInfo", userInfo.getUserName()); user2 = (UserInfo) sqlMap.queryForObject("getUserInfo", userInfo.getUserName()); Iterator<UserInfo> it = userResult.iterator(); while (it.hasNext()) { UserInfo user = it.next(); System.out.println(user.getUserSex()+"-"+user2.getUserId()); } //list分页查询 PaginatedList list = sqlMap.queryForPaginatedList("getAllUser", 2); System.out.println("第一页:"); for(int i=0;i<list.size();i++){ UserInfo user =(UserInfo)list.get(i); System.out.println(user.getUserName()); } list.nextPage(); System.out.println("第二页:"); for(int i=0;i<list.size();i++){ UserInfo user =(UserInfo)list.get(i); System.out.println(user.getUserName()); } //Map批量查询 Map userMap = sqlMap.queryForMap("getAllUser", null, "userId"); UserInfo userFromMap = (UserInfo) userMap.get(3); System.out.println(userFromMap.getUserName()); sqlMap.commitTransaction(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
5.缓存机制的配置,我们可以在映射文件中配置缓存机制
<typeAlias alias="UserInfo" type="IBatisTest.UserInfo" /> <cacheModel id="userCache" type="LRU"> <flushInterval hours="24"/> <flushOnExecute statement="upDateUserInfo" /> <property name="size" value="1000" /> </cacheModel>
我们在使用缓存的时候,可以直接使用userCache
<select id="getUserInfo" parameterClass="java.lang.String" resultClass="UserInfo" cacheModel="userCache" > <![CDATA[ select userId,usersex from UserInfo where username = #userName# ]]> </select>
这样我们通过对id为getUserInfo的Select Statement获取数据的时候,使用userCache进行缓存,之后程序再次执行这个Statement的时候,直接从缓存结果中取数据,无需再次执行SQL语句;flushInterval设置缓存的超时清空时间;flushOnExecute执行特定的Statement的时候将缓存清空;size缓存池中容纳的最大对象数量;
有时候,数据库表中的字段过于晦涩,为了使代码更易于理解,我们在映射到POJO时候,采用比较易读的属性名,我们可以采用select的as对字段进行转移,例如:
<select id="getUserInfo" parameterClass="java.lang.String" resultClass="UserInfo" cacheModel="userCache" > <![CDATA[ select userId,xingbie as userSex from UserInfo where username = #userName# ]]> </select>
这样,iBatis会根据转以后的字段名进行POJO映射;有时候存储过程相对而言比较封闭,我们难以通过select as进行字段名转义,此时通过使用resultMap来建立字段名和POJO中属性的映射就显得非常有效:
<resultMap id="getUserInfoMap" class="UserInfo"> <result property="name" column="xinming" jdbcType="VARCHAR" javaType="java.lang.String" /> </resultMap>
随后我们可以在Statement声明中对其引用:
<procedure id="getUserInfoList" resultMap="getUserInfoMap"> {call sp_getUserInfoList()} </procedure>
二、iBatis高级特性
1.通过Statement嵌套实现关联数据的操作
a.一对多关联
<?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="UserInfo"> <typeAlias alias="UserInfo" type="IBatisTest.UserInfo" /> <typeAlias alias="UserAddress" type="IBatisTest.UserAddr" /> <cacheModel id="userCache" type="LRU"> <flushInterval hours="24"/> <flushOnExecute statement="upDateUserInfo" /> <property name="size" value="1000" /> </cacheModel> <resultMap id="getUserResult" class="UserInfo"> <result property="userId" column="userid" /> <result property="userName" column="username" /> <result property="userSex" column="usersex" /> <result property="userAddr" column="username" select="getUserAddrByName" /> </resultMap> <select id="getUserInfoList" parameterClass="java.lang.String" resultMap="getUserResult"> <![CDATA[ select userid,username,usersex from userinfo where username = #userName# ]]> </select> <select id="getUserAddrByName" parameterClass="java.lang.String" resultClass="java.lang.String"> <![CDATA[ select useraddr from useraddress where username = #userName# ]]> </select> </sqlMap>
List<UserInfo> userList = sqlMap.queryForList("getUserInfoList","libin"); for(int i=0; i<userList.size();i++){ UserInfo user0 = userList.get(i); System.out.println("-->"+user0.getUserAddr()); }
b.一对一关联
<resultMap id="getUserResult" class="UserInfo"> <result property="userId" column="userid" /> <result property="userName" column="username" /> <result property="userSex" column="usersex" /> <result property="userAddr" column="useraddress.useraddr" /> <!-- <result property="userAddr" column="username" select="getUserAddrByName" />--> </resultMap>
<select id="getUserInfoList" parameterClass="java.lang.String" resultMap="getUserResult"> <!-- <![CDATA[ select userid,username,usersex from userinfo where username = #userName# ]]> --> <![CDATA[ select userinfo.userid,userinfo.username,userinfo.usersex,useraddr from userinfo,useraddress where userinfo.username = #userName# and userinfo.username = useraddress.username ]]> </select> <select id="getUserAddrByName" parameterClass="java.lang.String" resultClass="java.lang.String"> <![CDATA[ select useraddr from useraddress where username = #userName# ]]> </select>
两种方式都可以实现多表之间的关联;
2.延迟加载
当我们执行List<UserInfo> userList = sqlMap.queryForList("getUserInfoList","libin");时候,实际上iBatis只向数据库发送了一条:select userid,username,usersex from userinfo where username = #userName#语句,用于查询addr的SQL,只有在我们真正访问useraddress对象的时候才执行,这就是延迟加载,Lazy Loading.
3.动态映射
<select id="getUserInfo" parameterClass="UserInfo" resultClass="UserInfo" cacheModel="userCache" > <![CDATA[ select userId,usersex from UserInfo where username = #userName# ]]> <dynamic> <isNotEmpty prepend="AND" property="userId"> <![CDATA[ userid = #userId# ]]> </isNotEmpty> </dynamic> </select>
该Statement会判断POJO的userId属性是否为空来决定是否添加SQL: AND userid=userid,根据逻辑需要,isNotEmpty 还可以进行嵌套;
如果<dynamic>下有多个<isNotEmpty>,where子句中第一个子句需不需要and,iBatis会自动进行判断忽略,其他判定节点如下: