最近在讀劉增輝老師所著的《MyBatis從入門到精通》一書,很有收穫,於是將自己學習的過程以部落格形式輸出,如有錯誤,歡迎指正,如幫助到你,不勝榮幸!
1. 明確需求
書中提到的需求是一個基於角色的許可權控制需求(RBAC,即Role-Based Access Control),提到許可權管理,相信大家都不陌生,因為大部分的系統都是需要許可權管理的,我在上家公司負責的系統之一就是許可權系統,設計思路和書中提到的差不多,大致描述如下:
1)許可權點用來管理要控制許可權的資源,比如某個頁面,某個按鈕。
2)建立一個角色,給這個角色分配某些許可權點,比如商品模組的所有頁面的許可權。
3)新建一個使用者,給這個使用者分配某些角色。
資料關係圖如下所示:
2. 資料準備
首先執行如下指令碼建立上圖中的5張表:使用者表,角色表,許可權表,使用者角色關聯表,角色許可權關聯表。
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 '角色許可權關聯表';
然後執行如下指令碼新增測試資料:
INSERT INTO sys_user VALUES (1,'admin','123456','admin@mybatis.tk','管理員',NULL,current_timestamp);
INSERT INTO sys_user VALUES (1001,'test','123456','test@mybatis.tk','測試使用者',NULL,current_timestamp);
INSERT INTO sys_role VALUES (1,'管理員',1,1,current_timestamp);
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);
3. 建立實體類
在包com.zwwhnly.mybatisaction.model下依次建立這5張表對應的實體類:
package com.zwwhnly.mybatisaction.model;
import java.util.Date;
/**
* 使用者表
*/
public class SysUser {
/**
* 使用者ID
*/
private Long id;
/**
* 使用者名稱
*/
private String userName;
/**
* 密碼
*/
private String userPassword;
/**
* 郵箱
*/
private String userEmail;
/**
* 簡介
*/
private String userInfo;
/**
* 頭像
*/
private byte[] headImg;
/**
* 建立時間
*/
private Date createTime;
// 按Alt+Insert快捷鍵生成get和set方法
}
package com.zwwhnly.mybatisaction.model;
import java.util.Date;
/**
* 角色表
*/
public class SysRole {
/**
* 角色ID
*/
private Long id;
/**
* 角色名
*/
private String roleName;
/**
* 有效標誌
*/
private Integer enabled;
/**
* 建立人
*/
private Long createBy;
/**
* 建立時間
*/
private Date createTime;
// 按Alt+Insert快捷鍵生成get和set方法
}
可以參考類似的命名方式建立SysPrivilege.java,SysUserRole.java,SysRolePrivilege.java。
也可以按照文末提供的原始碼地址下載下原始碼。
注意事項:
1)MyBatis預設遵循“下劃線轉駝峰”命名方式。
如sys_user表對應的實體類名是Sys_User,資料庫欄位user_name對應的實體類欄位是userName。
2)在實體類中不要使用Java的基本型別,基本型別包括byte、int、short、long、float、doubule、char、boolean。
因為Java中的基本型別會有預設值,例如當某個類中存在private int age;欄位時,age的預設值為0,所以無法滿足age為null的情況,如果使用age !=null,結果總為ture,會導致一些隱藏的bug。
4. 建立Mapper.xml檔案
在src/main/resources下的com/zwwhnly/mybatisaction/mapper目錄下依次建立5張表對應的Mapper.xml檔案。
為了後續更快速的建立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>
</mapper>
然後選中目錄,右鍵新增檔案,如下圖所示:
剛生成的SysUserMapper.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>
</mapper>
我們只需要給mapper標籤新增個namespace屬性即可:
<?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.zwwhnly.mybatisaction.mapper.SysUserMapper">
</mapper>
按照同樣的方式依次建立SysRoleMapper.xml,SysPrivilegeMapper.xml,SysUserRoleMapper.xml和SysRolePrivilegeMapper.xml。
建立完成後,開啟我們在上篇部落格中建立的mybatis-config.xml檔案,修改
<mappers>
<mapper resource="com/zwwhnly/mybatisaction/mapper/CountryMapper.xml"/>
<mapper resource="com/zwwhnly/mybatisaction/mapper/SysUserMapper.xml"/>
<mapper resource="com/zwwhnly/mybatisaction/mapper/SysRoleMapper.xml"/>
<mapper resource="com/zwwhnly/mybatisaction/mapper/SysPrivilegeMapper.xml"/>
<mapper resource="com/zwwhnly/mybatisaction/mapper/SysUserRoleMapper.xml"/>
<mapper resource="com/zwwhnly/mybatisaction/mapper/SysRolePrivilegeMapper.xml"/>
</mappers>
使用這種方式,最明顯的缺點就是,我們後續如果新增了Mapper.xml檔案,仍然需要來修改檔案,非常不好維護,因此我們修改成如下配置方式,配置一個包名:
<mappers>
<package name="com.zwwhnly.mybatisaction.mapper"/>
</mappers>
修改完成後,執行上篇部落格中的單元測試CountryMapperTest,發現執行報如下錯誤:
報錯的原因是上篇部落格中,我們並沒有為CountryMapper.xml檔案建立對應的介面,使用包名配置方式後,就需要建立,所以解決方案就是在src/main/java下新建包com.zwwhnly.mybatisaction.mapper下,然後在該包下新建介面CountryMapper,然後在介面中新增方法selectAll()。
package com.zwwhnly.mybatisaction.mapper;
import com.zwwhnly.mybatisaction.model.Country;
import java.util.List;
public interface CountryMapper {
/**
* 查詢全部國家
*
* @return
*/
List<Country> selectAll();
}
5. 建立Mapper介面
找到src/main/java目錄下的包com.zwwhnly.mybatisaction.mapper,在該包下建立XML檔案對應的介面類,分別為SysUserMapper.java,SysRoleMapper.java,SysPrivilegeMapper.java,SysUserRoleMapper.java,SysRolePrivilegeMapper.java。
這裡只展示下SysUserMapper.java的程式碼:
package com.zwwhnly.mybatisaction.mapper;
public interface SysUserMapper {
}
注意事項:當Mapper介面和XML檔案關聯的時候,名稱空間namespace的值需要配置成介面的全限定名稱,MyBatis內部就是通過這個值將介面和XML關聯起來的。
例如SysUserMapper.xml中配置的namespace就com.zwwhnly.mybatisaction.mapper.SysUserMapper
6. select用法
6.1 查詢單條資料
假設我們需要通過id查詢使用者的資訊,首先,我們需要開啟SysUserMapper.java介面定義方法:
/**
* 通過id查詢使用者
*
* @param id
* @return
*/
SysUser selectById(Long id);
然後開啟對應的SysUserMapper.xml檔案新增如下內容:
<resultMap id="sysUserMap" type="com.zwwhnly.mybatisaction.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="sysUserMap">
SELECT * FROM sys_user WHERE id = #{id}
</select>
說明:
1)MyBatis通過select標籤的id屬性值和介面的名稱進行關聯。
2)標籤的id屬性值不能出現英文句號"."。
3)標籤的id屬性值在同一個名稱空間下不能重複。
4)因為介面方法是可以過載的,所以介面中可以出現多個同名但引數不同的方法,但是XML中id的值不能重複,因此介面中的所有同名方法會對應著XML中的同一個id的方法。
為了驗證第2點,我們將selectById修改成select.ById:
<select id="select.ById" resultMap="sysUserMap">
SELECT * FROM sys_user WHERE id = #{id}
</select>
此時如果呼叫該方法,會報如下錯誤:
為了驗證第3點,我們將XML內容修改為如下:
<select id="selectById" resultMap="sysUserMap">
SELECT * FROM sys_user WHERE id = #{id}
</select>
<select id="selectById" resultMap="sysUserMap">
SELECT * FROM sys_user WHERE id = #{id}
</select>
此時如果呼叫該方法,會報如下錯誤:
XML 程式碼講解:
- select:對映查詢語句使用的標籤。
- id:查詢語句的唯一識別符號,可用來代表這條語句。
- resultMap:用於設定資料庫返回列和Java物件的對映關係。
- SELECT * FROM sys_user WHERE id = #{id}是查詢語句。
- {id}:MyBatis SQL中使用預編譯引數的一種方式,大括號中的id代表傳入的引數名。
resultMap標籤用於配置Java物件的屬性和查詢結果列的對應關係,通過resultMap中配置的column和property可以將查詢列的值對映到type物件的屬性上。
上面查詢語句用到的resultMap標籤講解:
- id:必填且唯一。select標籤resultMap屬性的值為此處id設定的值。
- type:必填。用於配置查詢列所對映到的Java物件模型。
- column:從資料庫中得到的列名或者列的別名。
- property:要對映到的列結果的屬性,即Java物件模型的屬性。
- jdbcType:列對應的資料庫型別。
6.2 查詢多條資料
假設我們需要查詢所有使用者的資訊,首先,我們需要開啟SysUserMapper.java介面定義方法:
/**
* 查詢全部使用者
*
* @return
*/
List<SysUser> selectAll();
然後開啟對應的SysUserMapper.xml檔案新增如下內容:
<select id="selectAll" resultType="com.zwwhnly.mybatisaction.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>
注意事項:這裡我們並沒有使用resultMap屬性來設定要返回結果的型別,而是通過resultType屬性直接指定
要返回結果的型別,使用此種方式需要設定查詢列的別名,別名要和resultType指定物件的屬性名保持一致,
進而實現自動對映。
MyBatis提供了一個全域性屬性mapUnderscoreToCamelCase,將這個屬性的值設定為ture可以自動將以下劃線命名的資料庫列對映到Java物件的駝峰式命名屬性中。
那麼如何開啟呢?
方法是開啟我們在上篇部落格中新建的mybatis-config檔案,在settings節點新增如下配置:
<settings>
<!--其他配置-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
此時,前面的selectAll語句可以簡化為如下。
<select id="selectAll" resultType="com.zwwhnly.mybatisaction.model.SysUser">
SELECT id,
user_name,
user_password,
user_email,
user_info,
head_img,
create_time
FROM sys_user
</select>
7. 單元測試
新建基礎測試類BaseMapperTest,程式碼如下。
package com.zwwhnly.mybatisaction.mapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.BeforeClass;
import java.io.IOException;
import java.io.Reader;
/**
* 基礎測試類
*/
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 e) {
e.printStackTrace();
}
}
public SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
將上篇部落格中的CountryMapperTest類程式碼修改為如下。
package com.zwwhnly.mybatisaction.mapper;
import com.zwwhnly.mybatisaction.model.Country;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class CountryMapperTest extends BaseMapperTest {
@Test
public void testSelectAll() {
SqlSession sqlSession = getSqlSession();
try {
List<Country> countryList = sqlSession.selectList("com.zwwhnly.mybatisaction.mapper.CountryMapper.selectAll");
printCountryList(countryList);
} finally {
sqlSession.close();
}
}
private void printCountryList(List<Country> countryList) {
for (Country country : countryList) {
System.out.printf("%-4d%4s%4s\n", country.getId(), country.getCountryname(), country.getCountrycode());
}
}
}
修改點:
1)繼承基礎測試類BaseMapperTest,呼叫基類getSqlSession()方法即可獲取SqlSession物件,實現程式碼重用。
2)selectList()方法的引數值由selectAll修改為com.zwwhnly.mybatisaction.mapper.CountryMapper.selectAll,
因為在SysUserMapper中也新增了一個selectAll()方法,selectAll不再唯一,因此呼叫時必須帶上namespace。
參考CountryMapperTest測試類新建SysUserMapperTest測試類,程式碼如下。
package com.zwwhnly.mybatisaction.mapper;
import com.zwwhnly.mybatisaction.model.SysUser;
import org.apache.ibatis.session.SqlSession;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class SysUserMapperTest extends BaseMapperTest {
@Test
public void testSelectById() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
SysUser sysUser = sysUserMapper.selectById(1L);
Assert.assertNotNull(sysUser);
Assert.assertEquals("admin", sysUser.getUserName());
} finally {
sqlSession.close();
}
}
@Test
public void testSelectAll() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
List<SysUser> sysUserList = sysUserMapper.selectAll();
Assert.assertNotNull(sysUserList);
Assert.assertTrue(sysUserList.size() > 0);
} finally {
sqlSession.close();
}
}
}
執行測試類程式碼,測試通過,輸出日誌如下所示。
DEBUG [main] - ==> Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <== Row: 1, admin, 123456, admin@mybatis.tk, <
>, < >, 2019-06-27 18:21:07.0 TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, <
>, < >, 2019-06-27 18:21:07.0 DEBUG [main] - <== Total: 2
DEBUG [main] - ==> Preparing: SELECT * FROM sys_user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <== Row: 1, admin, 123456, admin@mybatis.tk, <
>, < >, 2019-06-27 18:21:07.0 DEBUG [main] - <== Total: 1
8. 原始碼
原始碼地址:https://github.com/zwwhnly/mybatis-action.git,歡迎下載。
9. 參考
劉增輝《MyBatis從入門到精通》