最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸!
书中提到的需求是一个基于角色的权限控制需求(RBAC,即Role-Based Access Control),提到权限管理,相信大家都不陌生,因为大部分的系统都是需要权限管理的,我在上家公司负责的系统之一就是权限系统,设计思路和书中提到的差不多,大致描述如下:
1)权限点用来管理要控制权限的资源,比如某个页面,某个按钮。
2)创建一个角色,给这个角色分配某些权限点,比如商品模块的所有页面的权限。
3)新建一个用户,给这个用户分配某些角色。
数据关系图如下所示:
首先执行如下脚本创建上图中的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);
在包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。
也可以按照文末提供的源码地址下载下源代码。
如sys_user表对应的实体类名是Sys_User,数据库字段user_name对应的实体类字段是userName。
因为Java中的基本类型会有默认值,例如当某个类中存在private int age;字段时,age的默认值为0,所以无法满足age为null的情况,如果使用age !=null,结果总为ture,会导致一些隐藏的bug。
在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(); }
在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
假设我们需要通过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>
为了验证第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 代码讲解:
resultMap标签用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到type对象的属性上。
上面查询语句用到的resultMap标签讲解:
假设我们需要通过id查询用户的信息,首先,我们打开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>
新建基础测试类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, «BLOB», «BLOB», 2019-06-27 18:21:07.0 TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, «BLOB», «BLOB», 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, «BLOB», «BLOB», 2019-06-27 18:21:07.0 DEBUG [main] - <== Total: 1
源码地址: https://github.com/zwwhnly/mybatis-action.git ,欢迎下载。
刘增辉《MyBatis从入门到精通》
IntelliJ IDEA中创建xml文件
欢迎扫描下方二维码关注微信公众号:「申城异乡人」,博客内容会同步更新。