本章将经过完成权限管理的常见业务来学习 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字段中的属性。
参考资料:
权限点用来管理要控制权限的资源