在進行業務開發時,遇見一個需求,從前端返回一個list的ids,要按照ids去從數據庫中查找,然後將它的列屬性-優先級更新爲該id在ids的位置。
例如:ids=[3, 6 ,2],則將id爲3、6、2的條目的優先級列設置成對應位置的1、2、3。
同時不能在代碼中有循環,只能與數據庫有一次連接。這也就意味着只能把循環寫在mapper.xml中。然後就有了以下兩種解決方案:
一次發多條sql語句
這種方法就是在一個<update>標籤內對update的sql使用<foreach>,如下:
<update id="updatePriorityByOrderMultiQueries" parameterType="java.util.List">
<foreach collection="ids" item="item" index="index" separator=";">
UPDATE table
SET strategy = #{index} + 1
WHERE id = #{item}
</foreach>
</update>
這樣每次循環都構建一個update,然後一下發送出去。問題解決~可是在測試的時候發現總是報錯,但是發送的sql語句在Navicat裏直接執行都是ok的。後來發現,原來是jdbc驅動默認不支持多條sql語句,需要在連接的url後面加上&allowMultiQueries=true,也就是需要這麼寫:
jdbc.url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
興致沖沖地趕緊去試了試,發現項目中的mybtis配置中已經打開了。。又去新建一個工程,嘗試發現是完全行得通的。
(經過一下午的努力,終於找到問題所在了。原因就是我們項目中用的mysql-connector版本是5.1.36,而我自己創建的項目用的版本是5.1.6。 5.1.36這個版本還不支持多條sql執行,也就是不支持參數allowMultiQueries=true,所以設置等於沒設置,但是它也不告訴你這個沒生效,真是大坑!)
行吧,上面的問題暫時告一段落。但sql語句還沒算結束,雖然目的是達到了,但在實際中,這樣寫很是問題。因爲雖然是沒在java的循環中完成,只是省去了每次連接數據庫的時間,但發送的多條sql語句每條語句都會把數據庫遍歷一遍,明明可以一次遍歷就能完成的事情,這性能也太低了。於是乎就有了第二種方法。
Join + Union方法
這種方法首先把查詢的條件和需要修改的結果構造成一張表(假設叫update_data)。比如對於上例中,需要構造如下一張表:
id |
strategy_priority |
---|---|
3 | 1 |
6 | 2 |
2 | 3 |
然後將原表與update_data表根據id字段進行Join(內連接),對於匹配到的列,設置原表的列值爲update_data表的列值即可達到更新的效果。修改後的<update>標籤爲:
<update id="updatePriorityByOrder" parameterType="java.util.List">
UPDATE `table` AS `raw_table` JOIN
<foreach collection="ids" item="item" index="index" open="(" close=")" separator="UNION">
SELECT ${item} AS `id`, ${index} + 1 AS `priority`
</foreach>
AS `update_data` USING(id)
SET `raw_table`.`priority` = `update_data`.`priority`
</update>
使用這種方法不僅美觀簡潔,同時對於性能有大大提升!有實測不管是少記錄更新還是多記錄更新的平均耗時都比第一種方法少耗時將近一半!