本章將經過完成權限管理的常見業務來學習 MyBatis XML方式的基本用法html
權限管理的需求: 一個用戶擁有若干角色,一個角色擁有若干權限,權限就是對某個模塊資源的某種操做(增、刪、改、查),這即是「用戶-角色-權限」的受權模型。java
採用RBAC(Role-Based Access Control,基於角色的訪問控制)方式。sql
在已經建立好的 mybatis數據庫中執行以下SQL腳本。( 如何經過SQL腳本用Navicat管理數據庫,請參考我上一篇博客的 1.3.1 準備數據庫 )數據庫
執行以下腳本建立上圖中的5張表:用戶表,角色表,權限表,用戶角色關聯表,角色權限關聯表。apache
CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '用戶ID', user_name VARCHAR(50) COMMENT '用戶名', user_password VARCHAR(50) COMMENT '密碼', user_email VARCHAR(50) COMMENT '郵箱', user_info TEXT COMMENT '簡介', head_img BLOB COMMENT '頭像', create_time DATETIME COMMENT '建立時間', PRIMARY KEY (id) ); ALTER TABLE sys_user COMMENT '用戶表'; CREATE TABLE sys_role ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '角色ID', role_name VARCHAR(50) COMMENT '角色名', enabled INT COMMENT '有效標誌', create_by BIGINT COMMENT '建立人', create_time DATETIME COMMENT '建立時間', PRIMARY KEY (id) ); ALTER TABLE sys_role COMMENT '角色表'; CREATE TABLE sys_privilege ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '權限ID', privilege_name VARCHAR(50) COMMENT '權限名稱', privilege_url VARCHAR(200) COMMENT '權限URL', PRIMARY KEY (id) ); ALTER TABLE sys_privilege COMMENT '權限表'; CREATE TABLE sys_user_role ( user_id BIGINT COMMENT '用戶ID', role_id BIGINT COMMENT '角色ID' ); ALTER TABLE sys_user_role COMMENT '用戶角色關聯表'; CREATE TABLE sys_role_privilege ( role_id BIGINT COMMENT '角色ID', privilege_id BIGINT COMMENT '權限ID' ); ALTER TABLE sys_role_privilege COMMENT '角色權限關聯表';
爲了方便後面的測試,接着在表中用SQL腳本插入一些測試數據。瀏覽器
INSERT INTO `sys_user` VALUES ('1','admin','123456','admin@mybatis.tk','管理員',NULL,'2019-07-12 22:06:25'); INSERT INTO `sys_user` VALUES ('1001','test','123456','test@mybatis.tk','測試用戶',NULL,'2019-07-12 22:10:45'); INSERT INTO `sys_role` VALUES ('1','管理員','1','1','2019-07-12 23:30:00'); INSERT INTO `sys_role` VALUES ('2','普通用戶','1','1',current_timestamp); INSERT INTO `sys_user_role` VALUES ('1','1'); INSERT INTO `sys_user_role` VALUES ('1','2'; INSERT INTO `sys_user_role` VALUES ('1001','2'); INSERT INTO `sys_privilege` VALUES ('1','用戶管理','/users'); INSERT INTO `sys_privilege` VALUES ('2','角色管理','/roles'); INSERT INTO `sys_privilege` VALUES ('3','系統日誌','/logs'); INSERT INTO `sys_privilege` VALUES ('4','人員維護','/persons'); INSERT INTO `sys_privilege` VALUES ('5','單位維護','/companies'); INSERT INTO `sys_role_privilege` VALUES ('1','1'); INSERT INTO `sys_role_privilege` VALUES ('1','2'); INSERT INTO `sys_role_privilege` VALUES ('1','3'); INSERT INTO `sys_role_privilege` VALUES ('2','4'); INSERT INTO `sys_role_privilege` VALUES ('2','5');
此處沒有建立表之間的 外鍵關係 ,爲了方便對錶進行直接操做。對於表之間的關係,會經過業務邏輯來進行限制。mybatis
在包(package) cn.bjut.example.model 下依次建立這5張數據庫表 將來(查詢結果)映射的實體類。app
1 package cn.bjut.example.model; 2 3 import java.util.Date; 4 5 /** 6 * 用戶表 7 */ 8 public class SysUser { 9 /** 10 * 用戶ID 11 */ 12 private Long id; 13 14 /** 15 * 用戶名 16 */ 17 private String userName; 18 19 /** 20 * 密碼 21 */ 22 private String userPassword; 23 24 /** 25 * 郵箱 26 */ 27 private String userEmail; 28 29 /** 30 * 簡介 31 */ 32 private String userInfo; 33 34 /** 35 * 頭像 36 */ 37 private byte[] headImg; 38 39 /** 40 * 建立時間 41 */ 42 private Date createTime; 43 44 // 按Alt+Insert快捷鍵生成geter和seter方法 45 }
能夠參考上面建立Java實體類[JavaBean]的方式依次完成 SysUserRole 、SysRole 、SysPrivilege 、SysRolePrivilege 四個類的代碼。工具
另外還能夠根據本書在第5章介紹使用MyBatis官方提供的工具 MyBatis Generator: mybatis-generator-core-1.3.7 ,根據數據庫表的信息自動生成這些實體類。post
注:
注: 接口能夠配合XML使用,也能夠配合註解來使用。XML能夠單獨使用,可是註解必須在接口中使用。
首先,在 src/main/resources 的 cn.bjut.example.mapper目錄下建立5個表各自對應的XML映射文件,分別爲 UserMapper.xml 、RoleMapper 、PrivilegeMapper 、 UserRoleMapper 和 RolePrivilegeMapper.xml 。
而後,在 src/main/java 下面建立包 cn.bjut.example.mapper 。接着在該包下建立XML文件對應的接口類,分別爲 UserMapper.java、RoleMapper、PrivilegeMapper、 UserRoleMapper 和 RolePrivilegeMapper.java 。
TIPS: 在IDEA開發環境中,我作法是用複製+粘貼+自動提示重命名能夠在當前包路徑中快速建立XML文件。若是命名錯誤,能夠經過Show in Explorer到Windows文件瀏覽器裏更改。
爲了後續更快速的建立Mapper.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="">
</mapper>
此處Mapper映射.xml文件的命名空間 namespace 的值須要配置成接口文件的全限定名稱,例如 UserMapper接口對應的 cn.bjut.example.mapper.UserMapper ,MyBatis內部就是經過這個值將接口和XML關聯起來的。
準備好這幾個XML映射文件後,還須要在1.3.2節中建立的 mybatis-config.xml配置文件中的 mappers元素中 配置全部的mapper文件路徑。
<mappers> <mapper resource="cn/bjut/example/mapper/CountryMapper.xml"/> <mapper resource="cn/bjut/example/mapper/SysUserMapper.xml"/> <mapper resource="cn/bjut/example/mapper/SysRoleMapper.xml"/> <mapper resource="cn/bjut/example/mapper/SysPrivilegeMapper.xml"/> <mapper resource="cn/bjut/example/mapper/SysUserRoleMapper.xml"/> <mapper resource="cn/bjut/example/mapper/SysRolePrivilegeMapper.xml"/> </mappers>
使用這種配置方式,最明顯的缺點就是,後續若是新增了Mapper.xml映射文件,仍然須要來此處修改mybatis-config文件,很差維護操做麻煩。所以咱們修改爲以下配置方式,配置一個包名,代碼以下。
<mappers>
<package name="cn.bjut.example.mapper"/>
</mappers>
注:使用第二種方式修改完成後,運行上篇博客中的單元測試CountryMapperTest,發現執行報以下錯誤:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for selectAll
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for selectAll
報錯的緣由是上篇博客中,咱們並無爲CountryMapper.xml文件建立對應的接口,使用包名配置方式後,就須要建立,因此解決方案就是在src/main/java下新建包
cn.bjut.example.mapper的目錄下新建接口CountryMapper,而後在接口中添加方法 selectAll()。
package cn.bjut.example.mapper; import cn.bjut.example.model.Country; import java.util.List; public interface CountryMapper { List<Country> selectAll(); }
查詢單條數據
先寫一個根據用戶id查詢用戶信息的方法。在 UserMapper 接口中添加一個 selectById方法,代碼以下。
package cn.bjut.example.mapper; //導包,與接口對應的實體類 import cn.bjut.example.model.SysUser; public interface UserMapper { /** * 經過id查詢用戶 * * @param id * @return */ SysUser selectById(Long id); }
而後在對應的UserMapper.xml中添加以下的 <resultMap>和<select>部分的代碼。
<?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="cn.bjut.example.mapper.UserMapper"> <resultMap id="userMap" type="cn.bjut.example.model.SysUser"> <id property="id" column="id"/> <result property="userName" column="user_name"/> <result property="userPassword" column="user_password"/> <result property="userEmail" column="user_email"/> <result property="userInfo" column="user_info"/> <result property="headImg" column="head_img" jdbcType="BLOB"/> <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/> </resultMap> <select id="selectById" resultMap="userMap"> //XML中的select標籤的id屬性值 = 「與之對應接口的方法名」 SELECT * FROM sys_user WHERE id = #{id} </select> </mapper>
映射XML和接口的命名須要符合以下規則:
XML 標籤和屬性講解:
resultMap標籤用於配置Java對象的屬性和查詢結果列的對應關係,經過resultMap中配置的column和property能夠將查詢列的值映射到type對象的屬性上。
上面查詢語句用到的resultMap包含的屬性和標籤講解:
接着看一下id和result標籤包含的意義:
id: 一個id結果,標記結果做爲id(惟一值),能夠幫助提升總體性能。id表明主鍵的字段(能夠有多個),它們的屬性值是經過setter方法注入的。
result : 注入到JAVA對象屬性的普通結果。
接口中定義的返回值類型必須和XML映射Mapper文件中配置的resultType類型一致。
返回值類型是由XML中的resultType(或resultMap中的type)決定的,不是由接口中寫的返回值類型決定的。(本章講XML方式,因此先忽略註解的狀況)
查詢返回多條數據:
在UserMapper接口中添加 selectAll方法,代碼以下。
package cn.bjut.example.mapper; //下面selectAll()方法用到了List須要導包 import java.util.List; //導包與接口對應的實體類的包 import cn.bjut.example.model.SysUser; public interface UserMapper { /** * 經過id查詢用戶 * * @param id * @return */ SysUser selectById(Long id); /** * 查詢所有用戶 * * @return */ List<SysUser> selectAll();
在對應的 UserMapper .xml中添加以下的<select>部分的代碼。
<select id="selectAll" resultType="cn.bjut.example.model.SysUser"> SELECT id, user_name userName, user_password userPassword, user_email userEmail, user_info userInfo, head_img headImg, create_time createTime FROM sys_user </select>
在定義接口中方法的返回值時,必須注意查詢SQL可能返回的結果數量。若執行的SQL返回多個結果時,必須使用 List<SysUser>
selectById 設置結果映射使用了resultMap標籤 中的id值。
selectAll 經過 resultType 直接指定了返回結果的類型。
若是使用resultType來設置返回結果的類型,須要在SQL中爲全部列名和(java實體類的)屬性名不一致的列 設置別名。->經過設置別名使得最終的查詢結果列和 resultType 指定對象的屬性名保持一致,進而實現自動映射。
<select id="selectAll" resultType="cn.bjut.example.model.SysUser"> SELECT id, user_name userName, user_password userPassword, user_email userEmail, user_info userInfo, head_img headImg, create_time createTime FROM sys_user </select>
===============================================================================================================================
接下來經過測試用例來驗證上面的兩個查詢。爲了方便學習後面的大量測試,此處先根據第1章中的測試提取一個(後面測試類的父類)基礎測試類 BaseMapperTest
public class BaseMapperTest { private static SqlSessionFactory sqlSessionFactory; @BeforeClass public static void init() { try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); } catch (IOException ignore) { ignore.printStackTrace(); } } public SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
模仿着編寫一個 UserMapperTest測試類,代碼以下:
1 public class UserMapperTest extends BaseMapperTest { 2 3 @Test 4 public void testSelectById(){ 5 //獲取 sqlSession 6 SqlSession sqlSession = getSqlSession(); 7 try { 8 //獲取 UserMapper 接口 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 //調用 selectById 方法,查詢 id = 1 的用戶 11 SysUser user = userMapper.selectById(1); 12 //user 不爲空 13 Assert.assertNotNull(user); 14 //userName = admin 15 Assert.assertEquals("admin", user.getUserName()); 16 } finally { 17 //不要忘記關閉 sqlSession 18 sqlSession.close(); 19 } 20 } 21 22 @Test 23 public void testSelectAll(){ 24 SqlSession sqlSession = getSqlSession(); 25 try { 26 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 27 List<SysUser> userList = userMapper.selectAll(); 28 //結果不爲空 29 Assert.assertTrue(userList.size() >0 ); 30 } finally { 31 sqlSession.close(); //不要忘記關閉sqlSession 32 } 33 } 34 }
與上面2個SELECT單表查詢不一樣,在實際業務中還須要多表關聯查詢。下面舉例一些更爲複雜的用法。
第一種簡單的情形:根據用戶id獲取用戶擁有的全部角色,返回的結果爲角色集合,結果只有角色的信息,不包含額外的其餘字段信息。
這個方法會涉及 sys_user sys_role sys_user_role 這3個表,而且該方法寫在上述任何一個對應的Mapper接口中均可以。
例如,咱們把這個方法寫到(位於src/main/java/XXX.mapper包中的) UserMapper接口中,代碼以下。
/** * 根據用戶id獲取角色信息 * * @param userID * @return */ List<SysRole> selectRolesByUserId(Long userID);
在對應的 UserMapper.xml中添加以下代碼。
1 <select id="selectRolesByUserId" resultType="cn.bjut.example.model.SysRole"> 2 select 3 r.id, 4 r.role_name roleName, 5 r.enabled, 6 r.create_by createBy, 7 r.create_time createTime 8 from sys_user u 9 inner join sys_user_role ur on u.id = ur.user_id 10 inner join sys_role r on ur.role_id = r.id 11 where u.id = #{userId} 12 </select>
雖然這個多表關聯的查詢中涉及了3個表,可是返回的結果只有sys_role一個表中的信息,因此直接使用 SysRole 做爲返回值類型便可。
咱們來編寫代碼對此方法進行測試。(src/main/test/java ......mapper包中 UserMapperTest增添)
1 @Test 2 public void testSelectRolesByUserID(){ 3 SqlSession sqlSession = getSqlSession(); 4 try { 5 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 6 List<SysRole> RoleList = userMapper.selectRolesByUserId(1L); 7 Assert.assertNotNull(RoleList); //結果不爲空 8 Assert.assertTrue(RoleList.size() >0 ); 9 } finally { 10 sqlSession.close(); //不要忘記關閉sqlSession 11 } 12 }
注:若是報錯,頗有多是以下圖片中的select語句中‘英文逗號 ,的位置和有無形成的。
若是我但願這個查詢語句同時返回SysUser表的user_name字段呢,該如何設置resultType?請參考連接文章。(不考慮嵌套的狀況)
方法2:新建擴展類,在擴展類中添加userName字段。
package cn.bjut.example.model; public class SysRoleExtend extends SysRole { private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
此時須要將映射文件中SELECT語句中的 resultType修改成:cn.bjut.example.model.SysRoleExtend。
這種方式比較適合須要少許額外字段的場景。若是須要其餘表的大量字段,可使用下面的方式4
方法4(推薦使用):新建擴展類,在擴展類中添加SysUser類型的字段。
package cn.bjut.example.model; public class SysRoleExtend extends SysRole {
//引用數據類型是另外一個 實體類
private SysUser user; public SysUser getSysUser() { return sysUser; } public void setSysUser(SysUser sysUser) { this.sysUser = sysUser; } }
此時須要將resultType修改成:cn.bjut.example.model.SysRoleExtend。
在XML映射Mapper文件中
這裏設置別名的時候,使用的是 user.屬性名 ,user是SysRole中剛剛增長的屬性 ,userName和userEmail是SysUser對象中的屬性 ,經過該方法能夠直接將值賦給user字段中的屬性。
參考資料:
權限點用來管理要控制權限的資源