存儲過程在數據庫中比較常見,雖然大多數存儲過程比較復雜,但是使用 MyBatis 調用時,用法都一樣,因此我們這一節使用一個簡單的存儲過程來了解 MyBatis 中存儲過程的使用方法。
基本准備
存儲過程涉及表 sys_user,建表語句如下。
DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用戶ID', `user_name` varchar(50) DEFAULT NULL COMMENT '用戶名', `user_password` varchar(50) DEFAULT NULL COMMENT '密碼', `user_email` varchar(50) DEFAULT 'test@mybatis.tk' COMMENT '郵箱', `user_info` text COMMENT '簡介', `head_img` blob COMMENT '頭像', `create_time` datetime DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1035 DEFAULT CHARSET=utf8 COMMENT='用戶表';
准備測試數據如下。
INSERT INTO `sys_user` VALUES ('1', 'admin', '123456', 'admin@mybatis.tk', '管理員用戶', 0x1231231230, '2016-06-07 01:11:12'); INSERT INTO `sys_user` VALUES ('1001', 'test', '123456', 'test@mybatis.tk', '測試用戶', 0x1231231230, '2016-06-07 00:00:00');
對應實體類SysUser
如下:
/** * 用戶表 */ public class SysUser implements Serializable { private static final long serialVersionUID = 1L; /** * 用戶ID */ private Long id; /** * 用戶名 */ private String userName; /** * 密碼 */ private String userPassword; /** * 郵箱 */ private String userEmail; /** * 簡介 */ private String userInfo; /** * 頭像 */ private byte[] headImg; /** * 創建時間 */ private Date createTime; //省略 getter 和 setter }
建存儲過程
我們先創建如下的存儲過程。
# 第一個存儲過程
# 根據用戶 id 查詢用戶其他信息
# 方法看着很奇葩,但是展示了多個輸出參數
DROP PROCEDURE IF EXISTS `select_user_by_id`; DELIMITER ;; CREATE PROCEDURE `select_user_by_id`( IN userId BIGINT, OUT userName VARCHAR(50), OUT userPassword VARCHAR(50), OUT userEmail VARCHAR(50), OUT userInfo TEXT, OUT headImg BLOB, OUT createTime DATETIME) BEGIN # 根據用戶 id 查詢其他數據 select user_name,user_password,user_email,user_info,head_img,create_time INTO userName,userPassword,userEmail,userInfo,headImg,createTime from sys_user WHERE id = userId; END ;; DELIMITER ;
創建XML方法
<select id="selectUserById" statementType="CALLABLE" useCache="false"> {call select_user_by_id( #{id, mode=IN}, #{userName, mode=OUT, jdbcType=VARCHAR}, #{userPassword, mode=OUT, jdbcType=VARCHAR}, #{userEmail, mode=OUT, jdbcType=VARCHAR}, #{userInfo, mode=OUT, jdbcType=VARCHAR}, #{headImg, mode=OUT, jdbcType=BLOB, javaType=_byte[]}, #{createTime, mode=OUT, jdbcType=TIMESTAMP} )} </select>
在調用存儲過程的方法中,我們需要把 statementType
設置為 CALLABLE
,在使用 select
元素中調用存儲過程時,由於存儲過程方式不支持 MyBatis 的二級緩存(后面章節會介紹),為了避免緩存配置導致出錯,我們直接將 select
元素的 useCache
屬性設置為 false
。
在存儲過程中使用參數時,除了寫上必要的屬性名外,還必須指定參數的 mode
(模式),可選值為 IN、OUT、INOUT 三種,入參使用 IN,出參使用 OUT,輸入輸出參數使用 INOUT。從上面代碼可以輕易看出 IN 和 OUT 的兩種模式的區別,那就是 OUT 模式的參數,必須指定 jdbcType。這是因為在 IN 模式下,MyBatis 提供了默認的 jdbcType
,在 OUT 模式下沒有提供,因此必須指定 jdbcType
,另外在使用 Oracle 數據庫時,如果入參存在 null 的情況,那么也必須指定 jdbcType
。
除了上面提到的這幾點外,headImg
還特別設置了 javaType
。在 MyBatis 映射的 Java 類中我們都不推薦使用基本類型,但是數據庫 BLOB 類型對應的 Java 類型我們通常都是寫成 byte[] 字節數組,因為 byte[] 數組不會有默認值的問題,所以不會影響我們一般的使用。但是在不指定 javaType
的情況下,MyBatis 默認使用 Byte
類型。由於我們使用的 byte
是基本類型,所以設置 javaType
的時候,基本類型要使用帶下划線方式的類型,在這里就是 byte[]
,_byte
對應的是基本類型,byte
對應的是 Byte
類型,在使用 javaType
時一定要注意。
創建接口
/** * 使用存儲過程查詢用戶信息 * * @param user * @return */ void selectUserById(SysUser user);
因為我們這個存儲過程沒有返回值(不要和出參混淆),所以我們返回值類型使用 void,如果你把返回值設置為 SysUser
或 List<SysUser>
也不會報錯,但是任何時候返回值都是 null
。
編寫測試
@Test public void testSelectUserById(){ SqlSession sqlSession = //獲取SqlSession的方法 try { //這個例子的XML和接口都定義在UserMapper中 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); SysUser user = new SysUser(); user.setId(1L); userMapper.selectUserById(user); Assert.assertNotNull(user.getUserName()); System.out.println("用戶名:" + user.getUserName()); } finally { sqlSession.close(); } }
執行測試,輸出如下日志:
DEBUG [main] - ==> Preparing: {call select_user_by_id( ?, ?, ?, ?, ?, ?, ? )} DEBUG [main] - ==> Parameters: 1(Long) 用戶名:admin
使用出參方式的時候,通常情況下我們會使用對象中的屬性接收出參的值,或者使用 Map
類型方法入參接收返回值。這兩種情況下有很大的區別。當我們使用 POJO 對象接收出參時,我們必須保證所有出參在 POJO 中都有對應的屬性存在,否則就會拋出類似 “Could not set property 'xxx'”
的錯誤,這是由於 POJO 對象中不存在出參對應的 setter 方法導致的。使用 Map 類型時就不需要必須存在該屬性,當 Map 接收了存儲過程的出參時,可以通過 Map
對象的 get("屬性名")
方法獲取出參的值。
錯誤提示
除了上面提到的錯誤外,當你在執行存儲過程時,還可能會遇到下面的錯誤:
Parameter number x is not an OUT parameter
這個錯誤可能的原因是因為你調用的存儲過程不存在,或者 MyBatis 中寫的出參和數據庫存儲過程的出參對應不上而導致的。