MyBatis,對象關系映射(ORM),是將程序中的對象與關系數據庫相互映射。存儲過程是為了實現特定功能的sql語句集合。本文主要介紹:“在項目中配置MyBatis的使用”、“存儲過程的編寫”、“測試存儲過程”。
1、在項目中配置MyBatis的使用。
(1)配置掃描Dao接口。
結合Spring框架,在ApplicationContext.xml中配置bean:MapperScannerConfigurer,掃描basePackage包下的所有dao層接口:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bx.erp.dao" />
</bean>
(2)配置訪問mybatis配置文件和數據源。
配置bean:SqlSessionFactoryBean,設置數據源和mybatis的配置文件:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
數據源:
<!--動態數據源的配置 -->
<bean id="dynamicDataSource" class="com.bx.erp.action.interceptor.DynamicDataSource" primary="true">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="jdbcDataSource_nbr_bx" key="jdbcDataSource_nbr_bx" />
</map>
</property>
<property name="defaultTargetDataSource" ref="jdbcDataSource_nbr_bx" />
</bean>
這里配置了動態數據源,默認數據源為:
<!-- 配置數據源3 -->
<bean id="jdbcDataSource_nbr_bx" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>${driverClassName}</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/nbr_bx?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL</value>
</property>
<property name="username">
<value>${db.nbx.mysql.username}</value>
</property>
<property name="password">
<value>${db.nbx.mysql.password}</value>
</property>
</bean>
(3)Mybatis配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC
"-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<mappers>
<mapper resource="com/bx/erp/dao/commodity/CommodityMapper.xml" />
<mapper resource="com/bx/erp/dao/commodity/CategoryMapper.xml" />
<mapper resource="com/bx/erp/dao/commodity/BrandMapper.xml" />
<mapper resource="com/bx/erp/dao/warehousing/InventorySheetMapper.xml" />
<mapper resource="com/bx/erp/dao/warehousing/InventoryCommodityMapper.xml" />
……
(4)通過注解將Dao接口注入Spring容器。
Dao接口使用@Component注解,注入Spring:
@Component("staffMapper")
public interface StaffMapper extends BaseMapper {
public Staff resetPassword(Map<String, Object> params);
public Staff updateOpenidAndUnionid(Map<String, Object> params);
public void checkICID(Map<String, Object> params);
public void checkUnionid(Map<String, Object> params);
public void checkWeChat(Map<String, Object> params);
public void checkOpenID(Map<String, Object> params);
public void checkStatus(Map<String, Object> params);
public void checkPhone(Map<String, Object> params);
public void checkIsFirstTimeLogin(Map<String, Object> params);
public void checkName(Map<String, Object> params);
public Staff updateUnsubscribe(Map<String, Object> params);
}
(5)配置Dao接口xml文件。
在同一目錄下,配置dao接口對應的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bx.erp.dao.StaffMapper">
<resultMap id="staffMap" type="com.bx.erp.model.Staff">
<id property="ID" column="F_ID" />
<result property="name" column="F_Name" />
<result property="phone" column="F_Phone" />
<result property="weChat" column="F_WeChat" />
<result property="openid" column="F_OpenID" />
<result property="unionid" column="F_Unionid" />
<result property="ICID" column="F_ICID" />
<result property="pwdEncrypted" column="F_pwdEncrypted" />
<result property="salt" column="F_Salt" />
<result property="passwordExpireDate" column="F_PasswordExpireDate" />
<result property="isFirstTimeLogin" column="F_IsFirstTimeLogin" />
<result property="shopID" column="F_ShopID" />
<result property="departmentID" column="F_DepartmentID" />
<result property="status" column="F_Status" />
<result column="F_CreateDatetime" property="createDatetime" />
<result column="F_UpdateDatetime" property="updateDatetime" />
<result column="F_RoleID" property="roleID" />
<result column="roleName" property="roleName" />
<result column="companyID" property="companyID"/>
<result column="shopName" property="shopName"/>
</resultMap>
Mapper namespace聲明了要關聯的dao接口為StaffMapper。
ResultMap結果映射聲明了type為Staff,Staff對象的屬性與數據庫表列關聯起來。
(6)MyBatis調用存儲過程。
<select id="create" statementType="CALLABLE" useCache="false" resultMap="staffMap">
{CALL SP_Staff_Create(
#{iErrorCode, jdbcType=INTEGER, mode=OUT},
#{sErrorMsg, jdbcType=VARCHAR, mode=OUT},
#{phone, mode=IN},
#{name, mode=IN},
#{ICID,mode=IN},
#{weChat, mode=IN},
#{salt, mode=IN},
#{passwordExpireDate, mode=IN},
#{isFirstTimeLogin, mode=IN},
#{shopID,mode=IN},
#{departmentID, mode=IN},
#{roleID, mode=IN},
#{status, mode=IN},
#{returnSalt,mode=IN}
)}
</select>
Select標簽的id屬性對應了Dao接口的create方法。
StatementType屬性聲明了使用存儲過程操作SQL語句。
ResultMap屬性聲明了結果集映射,即定義好的staffMap。
使用call命令調用了存儲過程SP_Staff_Create,傳遞給存儲過程的參數要使用#{}括起來。
參數需要設置mode屬性,值為IN說明是需要傳遞給存儲過程,值為OUT說明是需要存儲過程返回的。參數間使用逗號隔開。
Dao接口使用map傳遞參數:
public BaseModel create(Map<String, Object> params);
Map存放參數:
params.put(field.getFIELD_NAME_phone(), s.getPhone() == null ? "" : s.getPhone());
params.put(field.getFIELD_NAME_name(), s.getName() == null ? "" : s.getName());
params.put(field.getFIELD_NAME_ICID(), s.getICID() == "" ? null : s.getICID());
2、存儲過程的編寫。
(1)創建存儲過程。
DROP PROCEDURE IF EXISTS `SP_Staff_Create`;
CREATE DEFINER=`root`@`localhost` PROCEDURE `SP_Staff_Create` (
OUT iErrorCode INT,
OUT sErrorMsg VARCHAR(64),
IN sPhone VARCHAR(32),
IN sName VARCHAR(12),
IN sICID VARCHAR(20),
IN sWeChat VARCHAR(20),
IN sSalt VARCHAR(32),
IN dPasswordExpireDate DATETIME,
IN iIsFirstTimeLogin INT,
IN iShopID INT,
IN iDepartmentID INT,
IN iRoleID INT,
IN iStatus INT,
IN iReturnSalt INT
)
BEGIN
……
DEFINER聲明了存儲過程訪問數據庫的權限,root角色為最高權限,可以對數據庫做任何操作。
參數為OUT類型說明是需要存儲過程返回給調用者的,參數為IN類型說明是需要調用者傳遞給存儲過程的。
存儲過程以BEGIN關鍵字開頭,END關鍵字結尾
BEGIN
END;
執行SQL語句出現異常時,可以自定義錯誤碼和錯誤信息,回滾數據:
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET iErrorCode := 3;
SET sErrorMsg := '數據庫錯誤';
ROLLBACK;
END;
SQL語句集合:
IF EXISTS (SELECT 1 FROM t_staff WHERE F_ICID = sICID AND F_status = 0) THEN
SET iErrorCode := 7;
SET sErrorMsg := '在職員工中已經存在相同身份證,不能再創建這樣的員工';
ELSEIF EXISTS (SELECT 1 FROM t_staff WHERE F_WeChat = sWeChat AND F_status = 0) THEN
SET iErrorCode := 7;
SET sErrorMsg := '在職員工中已經存在相同微信員工,不能再創建這樣的員工';
ELSEIF EXISTS (SELECT 1 FROM t_staff WHERE F_Phone = sPhone AND F_status = 0) THEN
SET iErrorCode := 7;
SET sErrorMsg := '在職員工中已經存在相同電話的員工,不能再創建這樣的員工';
ELSEIF NOT EXISTS(SELECT 1 FROM t_shop WHERE F_ID = iShopID) THEN
SET iErrorCode := 7;
SET sErrorMsg := '不能使用不存在的門店進行創建';
ELSEIF NOT EXISTS (SELECT 1 FROM t_department WHERE F_ID = iDepartmentID) THEN
SET iErrorCode := 7;
SET sErrorMsg := '不能使用不存在的部門進行創建';
ELSEIF NOT EXISTS (SELECT 1 FROM t_role WHERE F_ID = iRoleID) THEN
SET iErrorCode := 7;
SET sErrorMsg := '不能使用不存在的角色進行創建';
ELSE
INSERT INTO t_staff (
F_Phone,
F_Name,
F_ICID,
……
(2)存儲過程開啟事務。
Mysql開啟事務,以START TRANSACTION開頭,COMMIT結尾,中間包裹SQL語句:
START TRANSACTION;
SQL語句;
COMMIT;
(3)存儲過程數據的返回和Dao接口接收數據。
數據庫返回結果集,DAO接口接收返回數據,一個select對應一個java對象或一個list集合。返回一行數據對應一個java對象,返回多行數據對應一個list集合:
SELECT
F_ID,
F_Name,
F_Phone,
F_ICID,
F_WeChat,
F_OpenID,
F_Unionid,
F_pwdEncrypted,
IF(iReturnSalt = 0, NULL, F_Salt) AS F_Salt,
F_PasswordExpireDate,
F_IsFirstTimeLogin,
F_ShopID,
F_DepartmentID,
F_Status,
F_CreateDatetime,
F_UpdateDatetime,
iRoleID AS F_RoleID
FROM t_staff WHERE F_ID = staffID;
public BaseModel create(Map<String, Object> params);
public BaseModel retrieve1(Map<String, Object> params);
public List<BaseModel> retrieveN(Map<String, Object> params);
如果有多個結果接即多個select語句,那么DAO接口需要一個多個list集合接收返回的數據:
SELECT
F_ID,
F_ShopID,
F_SN,
F_WarehouseID,
F_Scope,
F_Status,
F_StaffID,
F_ApproverID,
F_CreateDatetime,
F_Remark
FROM t_inventorysheet WHERE F_ID = iID;
SELECT
F_ID,
F_InventorySheetID,
F_CommodityID,
F_CommodityName,
F_Specification,
F_BarcodeID,
F_PackageUnitID,
F_NOReal,
F_NOSystem,
F_CreateDatetime,
F_UpdateDatetime
FROM t_inventorycommodity
WHERE F_InventorySheetID = iID ;
public List<List<BaseModel>> retrieve1Ex(Map<String, Object> params);
(4)存儲過程調用函數
對於一些比較復雜的檢查,會使用函數來減輕存儲過程的工作,也便於維護。通過返回錯誤信息來判斷格式是否正常:
-- 檢查該商品是否有銷存(有依賴)等記錄。
-- 返回值:
-- 1,有依賴,不能刪除該商品。
-- 0,無依賴,可以刪除該商品。
drop function IF EXISTS Func_CheckCommodityDependency;
CREATE DEFINER=`root`@`localhost` FUNCTION `Func_CheckCommodityDependency`(
iID INT,
sErrorMsg VARCHAR(32)
) RETURNS VARCHAR(32)
BEGIN
DECLARE NO INT;
DECLARE iType INT;
SELECT F_Type INTO iType FROM t_commodity WHERE F_ID = iID;
-- SELECT F_Type INTO iType FROM t_commodity WHERE F_ID = iID;
-- IF NO <> 0
IF EXISTS(SELECT 1 FROM t_commodityshopinfo WHERE F_CommodityID = iID AND F_NO > 0) THEN
SET sErrorMsg := '該商品還有庫存,不能刪除';
ELSEIF EXISTS(SELECT 1 FROM t_retailtradecommoditysource WHERE F_ReducingCommodityID = iID) THEN
SET sErrorMsg := '該商品有商品來源表依賴,不能刪除';
……
(5)存儲過程使用游標
使用游標可以很方便的編譯一個數據庫表或多行表數據,類似於Java的for循環。
定義游標結束標記:
DECLARE done INT DEFAULT false;
定義一個游標,包含多條表數據:
DECLARE list CURSOR FOR(
SELECT
F_ID,
F_DBName
FROM nbr_bx.t_company
WHERE F_Status = 0
);
打開游標,循環遍歷游標:
OPEN list;--
read_loop1: LOOP
獲取遍歷的值賦值給變量:
FETCH list INTO ID, dbName;
結束循環和關閉游標:
END LOOP read_loop1;
CLOSE list;
完整SQL語句:
OPEN list;--
read_loop1: LOOP
FETCH list INTO ID, dbName;
IF done THEN
LEAVE read_loop1;
END IF;
--
IF dbName <> 'nbr_bx' THEN
--
SET createTmpTable_sql = concat('create temporary table tmp_table(select F_Mobile from ',dbName,'.t_vip', ' where F_Mobile = ''', sMobile, ''')');
SET @createTmpTable_sql = createTmpTable_sql; -- 將連成成的字符串賦值給一個變量
prepare statement from @createTmpTable_sql; -- 預處理需要執行的動態SQL,其中statement是一個變量
EXECUTE statement; -- 執行SQL語句
deallocate prepare statement; -- 釋放掉預處理段
--
IF EXISTS(SELECT 1 FROM tmp_table) THEN -- 如果該公司有這個VIP, 插入臨時公司表
INSERT INTO tmp_t_company (F_ID) VALUES (ID);
END IF;
DROP TEMPORARY TABLE tmp_table; -- 刪除臨時表
--
END IF;
--
END LOOP read_loop1;
CLOSE list;
3、測試存儲過程。
和Java功能代碼一樣,存儲過程應該需要單元測試來測試功能的穩定性。
測試和驗證測試結果如下:
SELECT '-----------------CASE1: 正常添加------------------' AS 'CASE1';
SET @iErrorCode = 0;
SET @sErrorMsg = '';
SET @sName = '123132113';
SET @sPhone = '12341567891';
SET @sName = '123131213';
SET @sName = '店長1123';
SET @sICID = '4310251971841233';
SET @sWeChat = '';
SET @sPassword = '12341167';
SET @sSalt = 'E10ADC3949BA59ABBE56E057F20F883E';
SET @dPasswordExpireDate = '2017-05-03';
SET @iIsFirstTimeLogin = 1;
SET @iShopID = 1;
SET @iDepartmentID = 1;
SET @iRoleID = 1;
SET @iStatus = 0;
SET @iReturnSalt = 1;
CALL SP_Staff_Create(@iErrorCode, @sErrorMsg, @sPhone, @sName, @sICID, @sWeChat, @sSalt, @dPasswordExpireDate, @iIsFirstTimeLogin,
@iShopID, @iDepartmentID,@iRoleID, @iStatus, @iReturnSalt);
SELECT @sErrorMsg;
SELECT IF(found_rows() = 1 AND @iErrorCode = 0, '測試成功', '測試失敗') AS 'Case1 Testing Result';
DELETE FROM t_staffrole WHERE F_ID = last_insert_id();
DELETE FROM t_staff WHERE F_Phone = '12341567891';