CRUD操作中除了查詢操作,其他都統(tǒng)一稱為更新操作,因?yàn)樵鰟h改都是更新數(shù)據(jù)庫表的,SqlMap API中對(duì)應(yīng)的方法就是insert,update和delete,我們逐一來看。
insert方法的方法簽名為:Object insert(String id, String parameterObject) throws SQLException。那么我們需要傳遞的參數(shù)就是XML文件中的映射語句名稱和執(zhí)行插入操作所需要的參數(shù)。返回值為Object類型,也就是說它可以返回一個(gè)對(duì)象。我們想想通過插入語句我們希望得到什么呢?沒錯(cuò),就是插入這條記錄的主鍵。
這里還不得不多說一下主鍵的問題。雖然每種數(shù)據(jù)庫都有自己主鍵的生成方式,但對(duì)于這種將數(shù)據(jù)插入到數(shù)據(jù)庫后立即要知道主鍵的情況,就會(huì)產(chǎn)生問題。iBatis中的insert元素的一個(gè)子元素selectKey可以幫助我們獲取自動(dòng)生成的主鍵并保存在返回對(duì)象中,它有兩種調(diào)用方式:
第一就是數(shù)據(jù)插入到數(shù)據(jù)庫中之后立即抓取這條記錄的主鍵的值,但此時(shí)要注意抓取的值確實(shí)是剛插入的記錄的,也就是說數(shù)據(jù)庫的驅(qū)動(dòng)支持這么操作。如果有兩個(gè)線程同時(shí)執(zhí)行insert操作,其可能的順序?yàn)閕nsert #1,insert #2,selectKey #1,selectKey #2,那么在執(zhí)行selectKey #1時(shí)得到的就是第二個(gè)記錄的主鍵值了,這顯然對(duì)程序產(chǎn)生了影響。
第二種方式就是在插入記錄之前就獲取鍵,然后連同這個(gè)鍵一同插入到數(shù)據(jù)庫中,這樣就不存在第一種情況的風(fēng)險(xiǎn),是安全的操作。這種方式更傾向于支持序列的數(shù)據(jù)庫。
在MySQL數(shù)據(jù)庫中沒有序列的支持,那么可以使用LAST_INSERT_ID()函數(shù)來獲取自動(dòng)生成的主鍵,我們來一段代碼:
<insert id="addUser" parameterClass="parameterMap" >
insert into users(USERNAME,PASSWORD,AGE,MOBILE,EMAIL)
values(#userName:VARCHAR#,#password:VARCHAR#,#age:INT#,#mobile:VARCHAR#,#email:VARCHAR#)
<selectKey keyProperty="userId" resultClass="int">
select LAST_INSERT_ID()
</selectKey>
</insert>
向數(shù)據(jù)庫中插入一條記錄同時(shí)獲取了其生成的主鍵值,主鍵字段是userId,類型是int這都很好理解。而在程序中,我們只需這么來操作: ParameterMap parameterMap = new ParameterMap("userName", "sarin",
"password", "123", "age", "23", "mobile", "1", "email", "@");
Object obj = sqlMap.insert("User.addUser", parameterMap);
System.out.println(obj);
打印出的就是主鍵的值了,這個(gè)示例不但演示了iBatis的插入操作,也演示了插入操作同時(shí)獲取主鍵值的方法,當(dāng)然在Oracle中使用序列就行了,SQL Server是使用SCOPE_IDENTITY()函數(shù),這參考一下數(shù)據(jù)庫文檔即可。
上面我們?cè)趫?zhí)行更新操作時(shí)傳遞的參數(shù)是我們寫好的ParameterMap類型,即:
package ibatis.util;
import java.util.HashMap;
public class ParameterMap extends HashMap<Object, Object> {
private static final long serialVersionUID = 1L;
public ParameterMap(Object... parameters) {
for (int i = 0; i < parameters.length - 1; i += 2) {
super.put(parameters[i], parameters[i + 1]);
}
}
}
這是內(nèi)聯(lián)參數(shù)映射的一種,當(dāng)然我們也可以使用Javabean作為參數(shù)傳遞的對(duì)象,來看一個(gè)示例:User user = new User(null, "sarin", "123", "15940900000", "@", 23);
Object obj = sqlMap.insert("User.addUser", user);
System.out.println(obj);
這樣使用的前提是在User類中重載一個(gè)構(gòu)造方法來傳遞屬性的值,當(dāng)然默認(rèn)的構(gòu)造方法也要有(當(dāng)你調(diào)用了queryForObject()方法時(shí))。另外一種是外部參數(shù)映射,我們定義一個(gè)parameterMap來測試這種情況: <parameterMap class="ibatis.util.User" id="userPramaterMap">
<parameter property="userName" jdbcType="VARCHAR" />
<parameter property="password" jdbcType="VARCHAR" />
<parameter property="age" jdbcType="INT" />
<parameter property="mobile" jdbcType="VARCHAR" />
<parameter property="email" jdbcType="VARCHAR" />
</parameterMap>
自動(dòng)生成的主鍵userId這里我們就不用寫了,然后定義插入語句的映射: <insert id="addUserUseParameterMap" parameterMap="userPramaterMap">
insert into
users(USERNAME,PASSWORD,AGE,MOBILE,EMAIL)
values(?,?,?,?,?)
<selectKey keyProperty="userId" resultClass="int">
select LAST_INSERT_ID()
</selectKey>
</insert>
最后在程序中,則和上面內(nèi)聯(lián)參數(shù)沒有什么不同,除了映射的語句名稱:User user = new User(null, "sarin", "123", "15940900000", "@", 23);
Object obj = sqlMap.insert("User.addUserUseParameterMap", user);
System.out.println(obj);
更新操作的第二種是update,SqlMap API中更新操作的方法簽名是int update(String id, Object parameterObject) throws SQLException,參數(shù)的含義都已經(jīng)非常清楚了,就是映射的語句名稱和參數(shù),而返回值是int型的。在JDBC規(guī)范中我們知道更新操作返回的是影響的結(jié)果行數(shù),其實(shí)insert也是,只不過SqlMap給我們了新的選擇。
要注意的一點(diǎn)是iBatis允許在一條SQL語句中更新一條或多條記錄,而其他面向?qū)ο蟮墓ぞ呤侵辉试S修改一條記錄(比如Hibernate),但Hibernate針對(duì)批量操作也做了處理。
說到批量操作,就會(huì)有并發(fā)的問題,那么事務(wù)機(jī)制很自然就要想到了,這里簡單介紹一下iBatis處理批量操作的方法,后面會(huì)有詳細(xì)介紹iBatis的事務(wù)。這里給出一個(gè)處理并發(fā)的小技巧,就是在高并發(fā)的數(shù)據(jù)表中加一個(gè)時(shí)間戳字段或者是版本字段控制,每次更新操作都修改這個(gè)字段,就能一定程度上控制數(shù)據(jù)完整性。
由于iBatis是SQL映射工具,那么就不需要像使用Hibernate那樣考慮對(duì)象間的關(guān)系,在程序中做好處理就行了。先看下面這個(gè)示例:購物車訂單,這是很經(jīng)典的示例,我們需要建立兩個(gè)類(訂單和訂單項(xiàng)),兩個(gè)SQL映射文件和一個(gè)測試類。數(shù)據(jù)庫表結(jié)構(gòu)如下:
這是訂單表,簡單做示例,就訂單名稱和生成訂單的時(shí)間兩項(xiàng)即可。
這是訂單項(xiàng)表,字段見名知意,這里就不多解釋了。下面來看看實(shí)體類的設(shè)計(jì):
package ibatis.model;
import java.util.Date;
import java.util.List;
public class Order implements java.io.Serializable {
private Integer orderId;
private String orderName;
private java.util.Date generateTime;
private List<OrderItem> orderItems;
public Order() {
}
public Order(Integer orderId, String orderName, Date generateTime,
List<OrderItem> orderItems) {
super();
this.orderId = orderId;
this.orderName = orderName;
this.generateTime = generateTime;
this.orderItems = orderItems;
}
// 省略getter和setter方法
@Override
public String toString() {
return "Order [generateTime=" + generateTime + ", orderId=" + orderId+ ", orderItems=" + orderItems + ", orderName=" + orderName+ "]";
}
}
下面是訂單項(xiàng)的實(shí)體類,也很簡單:
package ibatis.model;
public class OrderItem implements java.io.Serializable {
private Integer oderItemId;
private String itemName;
private int quantity;
private float price;
private Integer orderId;
public OrderItem() {
}
public OrderItem(Integer oderItemId, String itemName, int quantity,
float price, Integer orderId) {
super();
this.oderItemId = oderItemId;
this.itemName = itemName;
this.quantity = quantity;
this.price = price;
this.orderId = orderId;
}
// 省略getter和setter方法
@Override
public String toString() {
return "OrderItem [itemName=" + itemName + ", oderItemId=" + oderItemId+ ", orderId=" + orderId + ", price=" + price + ", quantity="
+ quantity + "]";
}
}
雖然iBatis是SQL映射,但是實(shí)體類中我們使用對(duì)象類型而不是基本數(shù)據(jù)類型還是有很多好處的,比如直接和null判斷。下面我們來看SQL映射文件,其中使用了簡單的動(dòng)態(tài)SQL,這個(gè)后面會(huì)詳細(xì)來說明。 <sqlMap namespace="Order">
<typeAlias alias="Order" type="ibatis.model.Order" />
<insert id="insert" parameterClass="Order">
insert into
orders(orderName,generateTime)
values
(#orderName:VARCHAR#,now())
<selectKey keyProperty="orderId" resultClass="int">
select LAST_INSERT_ID()
</selectKey>
</insert>
<update id="update" parameterClass="Order">
update
orders
set
<dynamic>
<isNotEmpty property="orderName">
orderName=#orderName:VARCHAR#
</isNotEmpty>
</dynamic>
where
orderId=#orderId:INT#
</update>
<delete id="deleteDetails" parameterClass="Order">
delete from
orderitems
where
orderId=#orderId:INT#
</delete>
</sqlMap>
分析一下這個(gè)配置,首先定義了Order類型,在文件中可以直接使用。然后就是SQL語句了,有添加,更新和刪除操作,其中刪除是刪除所有的訂單項(xiàng),插入操作后返回自動(dòng)生成的主鍵,這個(gè)在前面已經(jīng)說明了,很好理解。那么訂單項(xiàng)的配置文件就更簡單了:<sqlMap namespace="OrderItem">
<typeAlias alias="OrderItem" type="ibatis.model.OrderItem" />
<insert id="insert" parameterClass="OrderItem">
insert into
orderitems(itemName,quantity,price,orderId)
values (#itemName:VARCHAR#,#quantity:INT#,#price:FLOAT#,#orderId:INT#)
</insert>
</sqlMap>
只有一個(gè)添加語句做示例。最后不要忘了在SqlMapConfig中引入這兩個(gè)配置文件,下面來看看測試類:package ibatis;
// 省略包引入語句
public class OrderDemo {
private static String config = "ibatis/SqlMapConfig.xml";
private static Reader reader;
private static SqlMapClient sqlMap;
static {
try {
reader = Resources.getResourceAsReader(config);
} catch (IOException e) {
e.printStackTrace();
}
sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
}
public static void main(String[] args) throws SQLException{
OrderItem item1 = new OrderItem(null, "IBM THINKPAD T410", 1, 10000, null);
OrderItem item2 = new OrderItem(null, "HP 6930P", 1, 7000, null);
OrderItem item3 = new OrderItem(null, "APPLE MC024", 1, 16000, null);
// 創(chuàng)建OrderItem對(duì)象集合,放入三個(gè)購物項(xiàng)
List<OrderItem> orderItems = new ArrayList<OrderItem>();
orderItems.add(item1);
orderItems.add(item2);
orderItems.add(item3);
Order order = new Order(null, "Sarin's Order", null, orderItems);
saveOrder(sqlMap, order);
}
public static void saveOrder(SqlMapClient sqlMap, Order order)
throws SQLException {
// 判斷是插入訂單還是更新
if (null == order.getOrderId()) {
sqlMap.insert("Order.insert", order);
} else {
sqlMap.update("Order.update", order);
}
// 清除訂單原有信息
sqlMap.delete("Order.deleteDetails", order);
// 插入訂單項(xiàng)目
for (int i = 0; i < order.getOrderItems().size(); i++) {
OrderItem oi = order.getOrderItems().get(i);
oi.setOrderId(order.getOrderId());
sqlMap.insert("OrderItem.insert", oi);
}
}
}
代碼能為我們完成任務(wù),但是可以看出,這里沒有任何的事務(wù)隔離,如果循環(huán)插入時(shí)發(fā)生了異常,那么數(shù)據(jù)完整性將遭到破壞。這是因?yàn)榇藭r(shí)的事務(wù)是在每條語句執(zhí)行時(shí)提交的,這也會(huì)影響程序的執(zhí)行性能。做如下測試,修改main函數(shù)為: public static void main(String[] args) throws SQLException {
long start = System.currentTimeMillis();
// 中間執(zhí)行代碼
long end = System.currentTimeMillis();
System.out.println(end - start);
}
一次測試時(shí)間是494,而如果: public static void main(String[] args) throws SQLException,IOException {
long start = System.currentTimeMillis();
String config = "ibatis/SqlMapConfig.xml";
Reader reader= Resources.getResourceAsReader(config);
SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
/ 中間執(zhí)行代碼
long end = System.currentTimeMillis();
System.out.println(end - start);
}
執(zhí)行時(shí)間可達(dá)到874,所以性能是很低的。下面我們來看看批量更新操作,將saveOrder()方法做如下修改:public static void saveOrder(SqlMapClient sqlMap, Order order)
throws SQLException {
// 開啟事務(wù)
sqlMap.startTransaction();
try {
// 判斷是插入訂單還是更新
if (null == order.getOrderId()) {
sqlMap.insert("Order.insert", order);
} else {
sqlMap.update("Order.update", order);
}
// 開始批量操作
sqlMap.startBatch();
// 清除訂單原有信息
sqlMap.delete("Order.deleteDetails", order);
// 插入訂單項(xiàng)目
for (int i = 0; i < order.getOrderItems().size(); i++) {
OrderItem oi = order.getOrderItems().get(i);
oi.setOrderId(order.getOrderId());
sqlMap.insert("OrderItem.insert", oi);
}
sqlMap.executeBatch();
sqlMap.commitTransaction();
} finally {
sqlMap.endTransaction();
}
}
測試出的運(yùn)行時(shí)間是432,確實(shí)效率提高了,因?yàn)閿?shù)據(jù)量小,不是很明顯。這里要注意批量操作的開始地方,因?yàn)楹竺娴腛rderItem使用到了Order的主鍵,而這個(gè)主鍵是數(shù)據(jù)庫自動(dòng)生成的,那么我們必須獲取到這個(gè)主鍵才能執(zhí)行批量那個(gè)操作。而在executeBatch()執(zhí)行時(shí)才會(huì)執(zhí)行SQL操作,如果開始批量的位置不對(duì),則不能獲取到創(chuàng)建的主鍵的值,那么后面的操作也不能被執(zhí)行了。
最后就是存儲(chǔ)過程了,這里給出一個(gè)簡單的示例:在存儲(chǔ)過程中刪除訂單詳細(xì)項(xiàng),一個(gè)比較奇怪的地方就是iBatis調(diào)用mysql的存儲(chǔ)過程要使用insert()方法,這里并不知道其原因。首先我們定義一個(gè)存儲(chǔ)過程:
在SQL配置中,我們定義一個(gè)存儲(chǔ)過程和映射參數(shù)類型:
<parameterMap class="java.util.Map" id="pm_delOiByOId">
<parameter property="orderId"/>
</parameterMap>
<procedure id="deleteOrderitemsByOrderId" parameterMap="pm_delOiByOId" resultClass="int">
call delete_orderitems_by_orderid(?)
</procedure>
程序中,使用insert()方法調(diào)用存儲(chǔ)過程,比如: Map m = new HashMap();
m.put("orderId", 3);
sqlMap.queryForObject("Order.deleteOrderitemsByOrderId", m);
除了存儲(chǔ)過程,還有函數(shù),我們也來看個(gè)示例:CREATE DEFINER = `root`@`localhost` FUNCTION `max_in_example`(`a` int,`b` int)
RETURNS int(10)
BEGIN
if(a > b) then
return a;
else
return b;
end if;
END;
這是MySQL的一個(gè)函數(shù)定義,在SQL映射文件中,必須使用select來標(biāo)記,而不是procedure,這個(gè)要記住。<parameterMap class="java.util.Map" id="pm_in_example">
<parameter property="a"/>
<parameter property="b"/>
</parameterMap>
<select id="in_example" parameterMap="pm_in_example" resultClass="int">
select max_in_example(?,?)
</select>
程序中,這樣寫就行了: Map m = new HashMap(2);
m.put("a", new Integer(7));
m.put("b", new Integer(5));
Integer val = (Integer) sqlMap.queryForObject("User.in_example", m);
System.out.println(val);
更多建議: