ibatis介紹
ibatis簡介:
ibatis一詞來源于“internet”和“abatis”的組合,是一個(gè)由Clinton Begin在2001年發(fā)起的開放源代碼項(xiàng)目。初側(cè)重于密碼軟件的開發(fā),現(xiàn)在是一個(gè)基于Java的持久層框架。
將“Internet”中象征性的“i”和abatis中的“batis”組合所以暗示了抵御Internet的意思。
相對Hibernate和Apache OJB等“一站式”ORM解決方案而言,ibatis 是一種“半 自動化”的ORM實(shí)現(xiàn)。
ibatis需要手寫sql語句,也可以生成一部分,Hibernate則基本上可以自動生成,偶爾會寫一些Hql。同樣的需求, ibatis的工作量比Hibernate要大很多。類似的,如果涉及到數(shù)據(jù)庫字段的修改,Hibernate修改的地方很少,而ibatis要把那些sql mapping的地方一一修改。
可維護(hù)性方面,ibatis更好一些。因?yàn)?iBatis 的 sql 都保存到單獨(dú)的文件中。而 Hibernate 在有些情況下可能會在 java 代碼中保sql/hql。
如果你開發(fā)一個(gè)新系統(tǒng),希望完全控制對象模型,進(jìn)行數(shù)據(jù)庫設(shè)計(jì),那么Hibernate是一個(gè)好的選擇;如果你工作在一個(gè)很古老的數(shù)據(jù)庫系統(tǒng)上,數(shù)據(jù)庫設(shè)計(jì)非常糟糕,你又沒有權(quán)利去改變數(shù)據(jù)庫設(shè)計(jì),那么iBATIS更合適一些。
因?yàn)閕batis是以SQL的方式開發(fā),所以ibatis自身是不能動態(tài)生成表的,當(dāng)然可以把SQL預(yù)先寫入xml中,但是這種方式在開發(fā)中使用率并不高,并且以后不斷的建表會比較繁瑣,所以通常情況下,我們需要首先創(chuàng)建表,然后再使用ibatis來操作數(shù)據(jù),在這方面,就沒有hibernate建表那么容易。
ibatis簡單入門
ibatis快速開發(fā):
創(chuàng)建一個(gè)java項(xiàng)目,然后導(dǎo)入驅(qū)動包到lib文件夾下面,我們需要的驅(qū)動包有ibatis-2.3.4.726.jar和classes12.jar,然后添加一個(gè)SqlMapConfig.xml到src下面,在此xml中寫入以下內(nèi)容:
<?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>
<transactionManager type="JDBC" commitRequired="false">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="JDBC.ConnectionURL" value="jdbc:oracle:thin:@localhost:1521:ORAC"/>
<property name="JDBC.Username" value="test"/>
<property name="JDBC.Password" value="test"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/cn/thinkmore/bean/BuMen_SqlMap.xml"/>
</sqlMapConfig>
然后創(chuàng)建一個(gè)pojo,對應(yīng)數(shù)據(jù)庫中的一個(gè)表,例如:
package com.cn.thinkmore.bean;
public class BuMen {
private String bid;
private String bumenname;
private String bumencode;
public BuMen(){}
public String getBumencode() {
return bumencode;
}
public void setBumencode(String bumencode) {
this.bumencode = bumencode;
}
public String getBid() {
return bid;
}
public void setBid(String bid) {
this.bid = bid;
}
public String getBumenname() {
return bumenname;
}
public void setBumenname(String bumenname) {
this.bumenname = bumenname;
}
}
接著創(chuàng)建此pojo的映射xml文件,名字自定義,盡量pojo類名_SqlMap命名,我在這里的創(chuàng)建的POJO類名叫BuMen,所以我創(chuàng)建此xml文件叫BuMen_SqlMap.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>
<select id="selectAll" resultClass="com.cn.thinkmore.bean.BuMen">
select bid,bumenname,bumencode from BuMen
</select>
</sqlMap>
后,我們來創(chuàng)建一個(gè)帶有main方法的測試類:
package com.cn.thinkmore.bean;
import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.util.List;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
public class TestConsole {
private static SqlMapClient sqlMapClient;
static {
try {
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close();
} catch (IOException e) {
// Fail fast.
throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
}
}
public static void main(String[] args) {
try {
List list = sqlMapClient.queryForList("selectAll");
System.out.println(list);
for(Object obj:list){
BuMen bm = (BuMen)obj;
System.out.println("bid="+bm.getBid()+" bumenname="+bm.getBumenname()+" bumencode="+bm.getBumencode());
}
}catch (SQLException e) {
e.printStackTrace();
}
}
}
后運(yùn)行成功,這樣我們就快速搭配起來了ibatis的環(huán)境了。
ibatis核心技術(shù)
ibatis配置文件---sqlMapConfig.xml:
下面,我們來詳細(xì)介紹sqlMapConfig中的常用標(biāo)簽:
<sqlMapConfig>
配置文件的父標(biāo)簽,所有標(biāo)簽必須寫在此標(biāo)簽內(nèi)部。
<properties/>
引用屬性文件,用于引用標(biāo)準(zhǔn)的propertis屬性文件。
在屬性文件中定義的屬性,可以作為變量在配置文件及其包含的所有映射文件中引用,例如:如果在屬性文件中包含如下屬性:
url=jdbc:oracle:thin:@localhost:1521:ORAC
那么就可以在xml文件中,這樣調(diào)用:
<property name=" JDBC.ConnectionURL" value="${url}"/>
<settings/>
設(shè)置屬性,用于配置和優(yōu)化SqlMap實(shí)例的各種屬性,此標(biāo)簽本身及其所有的屬性都是可選的。下面是它的內(nèi)部屬性介紹:
maxRequests:同時(shí)執(zhí)行SQL語句的大線程數(shù)。大于這個(gè)值的時(shí)候,后面的線程將被阻塞,直到有線程執(zhí)行完成,不同的數(shù)據(jù)庫有不同的限制值,但是任何數(shù)據(jù)庫通常應(yīng)該把此屬性值設(shè)置為maxTransactions的10倍,并且總是大于maxSessions和maxTransactions,減少此值可能會提高性能(默認(rèn)值 512)
maxSessions:同一時(shí)間內(nèi)活動的大Session數(shù),一個(gè)Session可以是代碼請求的顯式Session,也可以是當(dāng)線程使用SqlMapClient實(shí)例(執(zhí)行一條語句)自動獲得的Session,它應(yīng)該總是大于或者等于maxTransactions
并且小于maxRequests,減小這個(gè)參數(shù)值可能會減少內(nèi)存使用(默認(rèn)值 128)
maxTransactions:同時(shí)進(jìn)入SqlMapClient.startTransaction()的大線程數(shù),大于這個(gè)值的線程將阻塞直到另一個(gè)線程退出。不同的數(shù)據(jù)庫有不同的限制值,但任何數(shù)據(jù)庫都有這些限制,這個(gè)參數(shù)值應(yīng)該總是小于或等于maxSessions,并總是遠(yuǎn)遠(yuǎn)小于maxRequests,減小這個(gè)參數(shù)值可能會提高性能(默認(rèn)值 32)
cacheModelsEnabled:全局性的啟用或禁用SqlMapClient的所有緩存Model,在調(diào)試程序時(shí)使用(默認(rèn)值 true)
lazyLoadingEnabled:全局性的啟用或禁用SqlMapClient的所有延遲加載,在調(diào)試程序時(shí)使用(默認(rèn)值 true)
useStatementNamespaces:如果啟用本屬性,則必須使用全限定名來引用執(zhí)行命令,例如:sqlMapClient.queryForObject("映射文件名.方法名");
(默認(rèn)值 false)
<transactionManager/>
定義事務(wù)管理器,包含一個(gè)唯一的屬性type,用以指定使用的事務(wù)管理器類型。有三種不同的選擇:JDBC,JTA,EXTERNAL
JDBC:讓jdbc來管理事務(wù)(常用)
JTA:使用JTA全局事務(wù)
EXTERNAL:可以讓開發(fā)人員來管理事務(wù)
<dataSource/>
定義數(shù)據(jù)源,type="SIMPLE"定義一個(gè)基本的實(shí)現(xiàn),基于ibatis的SimpleDataSource連接池實(shí)現(xiàn),其別名為SIMPLE
<property/>
設(shè)置數(shù)據(jù)源參數(shù)
JDBC.Driver JDBC驅(qū)動程序類名
JDBC.ConnectionURL 數(shù)據(jù)庫URL
JDBC.Username 數(shù)據(jù)庫用戶名
JDBC.Password 數(shù)據(jù)庫密碼
JDBC.DefaultAutoCommit 連接池中所有連接默認(rèn)的自動提交模式(默認(rèn)為true)
Pool.MaximumActiveConnections 數(shù)據(jù)庫連接池可維持的大容量 默認(rèn)值為10
Pool.MaximumIdleConnections 數(shù)據(jù)庫連接池中允許的掛起連接數(shù),默認(rèn)值為5
<sqlMap/>
引用映射文件
POJO映射文件---SqlMap.xml:
<sqlMap namespace="BuMen">
此配置文件的父標(biāo)簽,所有標(biāo)簽都需要配置在此標(biāo)簽內(nèi)部,它可以設(shè)定一個(gè)命名空間,但是通常沒有作用。
<typeAlias alias="BuMen" type="com.cn.thinkmore.bean.BuMen"/>
為POJO設(shè)置一個(gè)別名,為了在使用SQL標(biāo)簽的時(shí)候引用,例如:
<select id="selectOne" resultClass="BuMen">
type屬性是指定POJO的包名+類名,alias屬性是給此POJO定義一個(gè)別名,為以后調(diào)用
<resultMap id="BuMenResult" class="BuMen">
<result property="bid" javaType="java.lang.String" column="bid" jdbcType="varchar2" nullValue="null"/>
<result property="bumenname" column="bumenname"/>
<result property="bumencode" column="bumencode"/>
</resultMap>
設(shè)置結(jié)果集映射,優(yōu)先選用resultClass而不是resultMap,此標(biāo)簽大多時(shí)候用于存儲過程的處理
id:引用的值
class:映射的POJO
result標(biāo)簽是指定每一個(gè)屬性
property:指定關(guān)聯(lián)的屬性名
javaType:指定屬性的類型
column:指定此屬性關(guān)聯(lián)的字段名
jdbcType:指定字段的類型
nullValue:當(dāng)此屬性沒有取到值的時(shí)候,指定的默認(rèn)值
通常情況下,只需要設(shè)置property和column屬性。
下面介紹在ibatis中怎么操作數(shù)據(jù)庫的增刪改查,對應(yīng)的標(biāo)簽分別是insert, delete,update,select,在這四個(gè)標(biāo)簽中,都有id,resultClass,parameterClass常用屬性:
id:指定要調(diào)用的名字
parameterClass:指定傳入?yún)?shù)的類型,例如:parameterClass="com.cn.thinkmore.bean.BuMen",也可以設(shè)定為基本類型
parameterMap:指定傳入?yún)?shù)的類型,(很少使用)
resultClass:指定返回的類型,例如:
resultClass="com.cn.thinkmore.bean.BuMen",也可以設(shè)定為基本類型
resultMap:指定返回的類型,(很少使用)
注意:為了避免特殊符號與XML的標(biāo)簽沖突,所以有時(shí)候需要使用
<![CDATA[select * from BuMen where bid<#bid#]]>的方式來編寫SQL
<select id="selectAll" resultClass="com.cn.thinkmore.bean.BuMen">
select bid,bumenname,bumencode from BuMen
</select>
定義一個(gè)SQL的插入語句,ibatis會自動根據(jù)select語句中的字段名,調(diào)用對應(yīng)POJO 的set方法設(shè)定屬性值,如上例中,ibatis會調(diào)用setBid,setBumenname,setBumencode 方法將Select語句返回的數(shù)據(jù),裝載到相應(yīng)的POJO實(shí)例屬性中。
<insert id="insertObj" parameterClass="com.cn.thinkmore.bean.BuMen">
insert into BuMen(bid,bumenname,bumencode) values (#bid#,#bumenname#,#bumencode#)
</insert>
定義一個(gè)SQL的插入語句,ibatis會自動根據(jù)select語句中的字段名,調(diào)用對應(yīng)POJO 的get方法設(shè)定屬性值,如上例中,ibatis會調(diào)用getBid,getBumenname,getBumencode 方法將POJO實(shí)例屬性中的值插入了SQL語句中。
<delete id="deleteObj" parameterClass="int">
delete from BuMen where bid=#bid#
</delete>
定義一個(gè)SQL的刪除語句,parameterClass屬性定義傳入的類型為Integer類型,它會自動添加到#bid#位置,現(xiàn)在的#bid#不會再調(diào)用set或者get,ibatis會自動把它當(dāng)做填充符使用。
<update id="updateObj" parameterClass="bumen">
update bumen set bid=#bid#,bumenname=#bumenname#,bumencode=#bumencode# where bumencode=#bumencode#
</update>
定義一個(gè)SQL的更新語句。
使用SqlMapClient操作
SqlMapClient的開發(fā):
每一個(gè)Dao或者Service都會有一個(gè)SqlMapClient的實(shí)例,通常情況,我們并不希望外部能訪問此實(shí)例對象,所以我們需要這樣做:
private static SqlMapClient sqlMapper;
為了讓程序在開始的時(shí)候就可以操作數(shù)據(jù)庫,所以,我們還需要一個(gè)靜態(tài)方法來自動創(chuàng)建這個(gè)實(shí)例:
static {
try {
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
sqlmapClient = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close();
} catch (IOException e) {
// Fail fast.
throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
}
}
這樣做了以后,我們就可以創(chuàng)建我們想操作數(shù)據(jù)庫的方法了。
我們在第二章里面已經(jīng)學(xué)習(xí)怎樣快速搭建ibatis并且把它使用起來了,現(xiàn)在來說說,開發(fā)中,有哪些方法可以讓我們操作,這些方法都需要SqlMapClient的實(shí)例來調(diào)用。
1. sqlmapClient.queryForList(String id) 基于一個(gè)id指定的sql來執(zhí)行查詢,返回一個(gè)List
2. sqlmapClient.queryForList(String id,Object param) 基于一個(gè)id和一個(gè)字符串參數(shù)來執(zhí)行查詢,返回一個(gè)List
3. sqlmapClient.queryForList(String id,Object pojo) 基于一個(gè)id和一個(gè)pojo參數(shù)來執(zhí)行查詢,返回一個(gè)List
4. sqlmapClient.queryForList(String id,Object map) 基于一個(gè)id和一個(gè)map參數(shù)來執(zhí)行查詢(適合于多參查詢的時(shí)候使用),返回一個(gè)List
5. sqlmapClient.queryForObject(String id) 基于一個(gè)id來執(zhí)行查詢,返回一個(gè)Object,此方法也有4種常用方式,用法和queryForList一樣,只是返回值只有一個(gè)。
6. sqlmapClient.update(String id) 基于一個(gè)id來執(zhí)行更新,返回一個(gè)Integer值決定執(zhí)行了多少條記錄,此方法也有4種常用方式,和queryForList類似,只是返回值是執(zhí)行的結(jié)果個(gè)數(shù)
7. sqlmapClient.insert(String id) 和update類似
8. sqlmapClient.delete(String id) 和update類似
事務(wù)管理
SqlMapClient中的事務(wù):
sqlMapClient.startTransaction();
開始事務(wù)
sqlMapClient.commitTransaction();
提交事務(wù)
sqlMapClient.endTransaction();
結(jié)束事務(wù),操作失敗的時(shí)候,整個(gè)事務(wù)就會在endTransaction時(shí)回滾。
新版的ibatis中,不再有rollbackTransaction方法,只能選擇endTransaction代替。
如果代碼沒有顯式的調(diào)用SqlMapClient.startTransaction()方法,則ibatis會將當(dāng)前的數(shù)據(jù)庫操作視為自動提交模式(AutoCommit=true),不過,值得注意的是,這里的所謂“自動判定”,實(shí)際上ibatis并沒有去檢查當(dāng)前是否已經(jīng)有事務(wù)開啟。
實(shí)際上,在執(zhí)行update語句時(shí),sqlMap會檢查當(dāng)前的Session是否已經(jīng)關(guān)聯(lián)了某個(gè)
數(shù)據(jù)庫連接,如果沒有,則取一個(gè)數(shù)據(jù)庫連接,將其AutoCommit屬性設(shè)為true,然后
執(zhí)行update 操作,執(zhí)行完之后又將這個(gè)連接釋放。這樣,上面兩次update 操作實(shí)際上
先后獲取了兩個(gè)數(shù)據(jù)庫連接,而不是我們通常所認(rèn)為的兩次update 操作都基于同一個(gè)
JDBC Connection。這點(diǎn)在開發(fā)時(shí)需特別注意。
對于多條SQL 組合而成的一個(gè)JDBC 事務(wù)操作而言,必須使用startTransaction、commit和endTransaction操作以實(shí)現(xiàn)整體事務(wù)的原子性。
注意!事務(wù)不能嵌套。在調(diào)用commit或end方法之前,從同一線程多次調(diào)用startTransaction將引起拋出例外。換句話說,對于每個(gè)SqlMap實(shí)例,每個(gè)線程多只能打開一個(gè)事務(wù)。
深入SqlMap.xml中的SQL開發(fā)
增刪改查4種SQL的深入化開發(fā):
<select id="selectLike" parameterClass="java.lang.String" resultClass="bumen">
select * from BuMen where bumencode like '%$bumencode$%'
</select>
當(dāng)我們需要使用模糊查詢的時(shí)候,通配符需要特殊處理,否則無法查詢到想要的數(shù)據(jù)
<select id="selectUser" parameterClass="bumen" resultClass="int">
select count(*) from BuMen where bid=#bid# and bumenname=#bumenname#
</select>
我們可以使用聚集函數(shù)來查詢,返回類型定義為int類型,也就是java.lang.Integer類型,這里我們傳入的參數(shù)是一個(gè)POJO類型,ibatis會自動get需要的屬性值,不用的屬性值不會get出來,這樣做雖然可以實(shí)現(xiàn)查詢,但是看來有點(diǎn)浪費(fèi)資源。
<resultMap id="BuMenResult" class="BuMen">
<result property="bid" javaType="java.lang.String" column="bid" jdbcType="varchar2" nullValue="null"/>
<result property="bumenname" column="bumenname"/>
<result property="bumencode" column="bumencode"/>
</resultMap>
<select id="selectUser" parameterClass="bumen" resultMap="BuMenResult">
select count(*) from BuMen where bid=#bid# and bumenname=#bumenname#
</select>
和上面的查詢是一樣的效果,只是返回的方式有所改變,我們這次選用了resultMap來返回,這樣做的好處是可以讓其他開發(fā)人員來看懂POJO和數(shù)據(jù)庫中表的對應(yīng)關(guān)系。
<select id="selectAll2" parameterClass="int" resultClass="bumen">
<![CDATA[select bid as id,bumenname as name,bumencode as code from BuMen where bid<#bid#]]>
</select>
數(shù)據(jù)庫表中的字段名和POJO中的屬性名不相同的時(shí)候,我們希望字段映射到POJO時(shí),我們可以通過Select的as 字句對字段名進(jìn)行轉(zhuǎn)義,這樣就會調(diào)用POJO的setId,setName,SetCode方法裝載數(shù)據(jù)。并且小于符號是xml中的關(guān)鍵字,使用會報(bào)錯(cuò),所以,我們還需要<![CDATA[……]]>的方式來編寫SQL語句。
<update id="updateUser" parameterClass="java.util.Map">
update bumen
set
bumenname=#bumenname#,
bumencode=#bumencode#
where bid = #bid#
</update>
這里傳入的參數(shù)就是一個(gè)Map對象,ibatis將以key "bid"、"bumenname"、"bumencode"從中提取對應(yīng)的參數(shù)值,
此方式大部分時(shí)候用于嵌套查詢,或者多表查詢,把要查詢的條件以Map拼接。
此方式還可以用于任意的增刪改查SQL中,例如,我們要查詢一個(gè)用戶的名字和密碼是否存在的時(shí)候。
<select id="getUsers" parameterClass="bumen" resultClass="bumen">
select bid,bumenname,bumencode from bumen
<dynamic prepend="where">
<isNotEmpty prepend="and" property="bumenname">
(bumenname=#bumenname#)
</isNotEmpty>
<isNotEmpty prepend="and" property="bumencode">
(bumencode=#bumencode#)
</isNotEmpty>
</dynamic>
</select>
在很多時(shí)候,可能我們需要一些變化的sql語句,比如說,當(dāng)用戶輸入幾個(gè)關(guān)鍵字查詢的時(shí)候,我們需要判斷用戶到底輸入了幾個(gè)關(guān)鍵字,然后通過if-else的方式,組建不同的SQL語句來操作數(shù)據(jù)庫,但是這樣做的話,會產(chǎn)生大量的代碼或者配置文件內(nèi)容,但是我們在使用ibatis的時(shí)候,可以避免這個(gè)問題,我們通過dynamic節(jié)點(diǎn),定義了一個(gè)動態(tài)的where子句。此where子句中將可能包含兩個(gè)針對bumenname和bumencode 字段的判斷條件。而這兩個(gè)字段是否加入查詢?nèi)Q于用戶所提供的查詢條件(字段是否為空[isNotEmpty])。
由于ibatis會自動判定是否需要追加prepend前綴,這里(bumenname=#bumenname#)是where 子句中的第一個(gè)條件子句,無需and前綴,所以自動省略。
當(dāng)用戶沒有傳入?yún)?shù)的時(shí)候,我們可以讓ibatis使用全表查詢,也就是上面配置中的select bid,bumenname,bumencode from bumen語句,如果用戶傳入了一個(gè)參數(shù)查詢的時(shí)候,我們可以讓ibatis基于一個(gè)條件查詢,也就是上面配置中的
select bid,bumenname,bumencode from bumen where bumenname=#username#語句,如果用戶傳入了兩個(gè)參數(shù)查詢的時(shí)候,我們可以讓ibatis基于兩個(gè)條件查詢,也就是上面配置中的
select bid,bumenname,bumencode from bumen where bumenname=#username# and bumencode=#bumencode#語句,通過這樣的方式,我們就可以靈活的操作用戶的查詢規(guī)則,而不必要去增加太多的代碼和配置內(nèi)容,下面還有幾種判定標(biāo)簽可以使用。
<isPropertyAvailable> 參數(shù)類中是否提供了此屬性
<isNotPropertyAvailable> 與<isPropertyAvailable>相反
<isNull> 屬性值是否為NULL
<isNotNull> 與<isNull>相反
<isNotEmpty> 屬性值是否為空。
<isEmpty> 與<isNotEmpty>相反
二元判斷(使用方式和dynamic節(jié)點(diǎn)一樣,只是加入了一個(gè)屬性compareValue,它可以指定一個(gè)比較值):
例如:
<isEqual prepend="AND" property="bid" compareValue="18">
(bid=#bid#)
</isEqual>
上面的作用是如果bid屬性等于18(compareValue),則在SQL中加入(bid=#bid#)條件。
同樣,二元判斷也有及各種判斷標(biāo)簽供我們選擇。
<isEqual> 相等。
<isNotEqual> 不等。
<isGreaterThan> 大于
<isGreaterEqual> 大于等于
<isLessThan> 小于
<isLessEqual> 小于等于