MyBatis從入門到精通(第2章):MyBatis XML方式的基本用法

本章將經過完成權限管理的常見業務來學習 MyBatis XML方式的基本用法html

2.1 一個簡單的權限控制需求

權限管理的需求: 一個用戶擁有若干角色,一個角色擁有若干權限,權限就是對某個模塊資源的某種操做(增、刪、改、查),這即是「用戶-角色-權限」的受權模型。java

採用RBAC(Role-Based Access Control,基於角色的訪問控制)方式。sql

 

2.1.1 建立數據庫表

 在已經建立好的 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

 

2.1.2  建立實體類

在包(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 }

MyBatis從入門到精通:各個實體類建立的源碼

能夠參考上面建立Java實體類[JavaBean]的方式依次完成   SysUserRole 、SysRole 、SysPrivilege   、SysRolePrivilege 四個類的代碼。工具

另外還能夠根據本書在第5章介紹使用MyBatis官方提供的工具 MyBatis Generator: mybatis-generator-core-1.3.7  ,根據數據庫表的信息自動生成這些實體類。post

注:

  •    MyBatis默認遵循(從SQL到JAVA)「下劃線轉駝峯的命名方式。如sys_user表對應的實體類名是SysUser,數據庫字段user_name對應的實體類的變量名是userName。
  •    在實體類中不要使用Java的基本數據類型,基本類型包括 byte、int、short、long、float、doubule、char、boolean。由於Java基本類型會有默認值,例如當某個實體類(對應着一個數據庫表)中存在private int age;若是使用age != null進行判斷,結果總會爲true     會致使不少隱藏的問題。 一個特殊的類型「byte[]」不是Java基本數據類型。

2.2  使用接口+XML方式

注:   接口能夠配合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();
}

 

 2.3  SELECT用法

查詢單條數據

先寫一個根據用戶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和接口的命名須要符合以下規則:

  • 標籤的id屬性值在任什麼時候候都不能出現英文句號「.」,而且同一個命名空間下不能出現重複的id。
  • 接口的方法是能夠重載的,因此接口中能夠出現多個同名但參數不一樣的方法,可是XML中id的值不能重複,於是接口中的全部同名方法只會對應着XML中的同一個id的select方法。最多見的用法就是,同名方法中其中一個方法增長一個 RowBound 類型的參數用於實現分頁查詢

XML 標籤和屬性講解:

  • <select>:映射查詢語句使用的標籤。
  • id:命名空間中的惟一標識符,可用來表明這條語句。
  • resultMap:用於設置數據庫返回值(列)的類型 和Java對象屬性的映射關係。
  • SELECT * FROM sys_user WHERE id = #{id}是查詢語句。
  • #{id}:MyBatis SQL中使用預編譯參數的一種方式,大括號中的id是傳入的參數名。

resultMap標籤用於配置Java對象的屬性和查詢結果列的對應關係,經過resultMap中配置的columnproperty能夠將查詢列的值映射到type對象的屬性上。

上面查詢語句用到的resultMap包含的屬性和標籤講解:

  • id:必填,而且惟一。在select標籤中,resultMap屬性的值爲此處id所設置的值。
  • type:必填,用於配置查詢列所映射到的Java實體對象類型。
  • extends: 選填,
  • autoMapping: 選填(true/false),該配置能夠覆蓋全局的 autoMappingBehavior配置。 

接着看一下id和result標籤包含的意義:

id: 一個id結果,標記結果做爲id(惟一值),能夠幫助提升總體性能。id表明主鍵的字段(能夠有多個),它們的屬性值是經過setter方法注入的。

result :  注入到JAVA對象屬性的普通結果。

  • column:  從數據庫中獲得的列名,或者列的別名。
  • property:映射到列結果的屬性。支持經過 . 方式的屬性嵌套賦值。
  • javaType:  一個Java類的徹底限定名,或經過typeAlias配置的類型別名。 

接口中定義的返回值類型必須和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字段中的屬性。

 

相關文章
相關標籤/搜索