iBatis開發(fā)詳解(7)---執(zhí)行非查詢語句(CRUD,函數(shù)和過程

2018-10-14 10:01 更新

    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)即可。

iBatis

  這是訂單項(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ǔ)過程: 

iBatis

  在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);  
打印的結(jié)果就是7。至此iBatis的非查詢語句就介紹完了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)