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,欢迎下载。

刘增辉《MyBatis从入门到精通》

IntelliJ IDEA中创建xml文件

原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。

如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。

MyBatis从入门到精通(二):MyBatis XML方式的基本用法之Select的更多相关文章

  1. MyBatis从入门到精通:使用XML方式(映射文件之类的)

    2.3节笔记部分: package tk.mybatis.simple; public class Temp { } /* 2.2 使用XML方式 MyBatis使用了Java的动态代理可以直接通过接 ...

  2. MyBatis从入门到精通(三):MyBatis XML方式的基本用法之多表查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. 多表查询 上篇博客中,我们示例的 ...

  3. MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. insert用法 1.1 简单的 ...

  4. MyBatis从入门到精通(十二):使用collection标签实现嵌套查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解使用collectio ...

  5. MyBatis从入门到精通(2):MyBatis XML方式的基本用法

    本章将通过完成权限管理的常见业务来学习 MyBatis XML方式的基本用法 2.1一个简单的权限控制需求 权限管理的需求: 一个用户拥有若干角色,一个角色拥有若干权限,权限就是对某个模块资源的某种操 ...

  6. MyBatis基础入门《十二》删除数据 - @Param参数

    MyBatis基础入门<十二>删除数据 - @Param参数 描述: 删除数据,这里使用了@Param这个注解,其实在代码中,不使用这个注解也可以的.只是为了学习这个@Param注解,为此 ...

  7. MyBatis从入门到精通(一):MyBatis入门

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. MyBatis简介 ​ 2001 ...

  8. MyBatis从入门到精通(五):MyBatis 注解方式的基本用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. @Select 注解 1.1 使 ...

  9. MyBatis从入门到精通(六):MyBatis动态Sql之if标签的用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用if标签生成动 ...

随机推荐

  1. Android Handler、Message完全解析,带你从源码的角度彻底理解

    之前也是由于周末通宵看TI3比赛,一直没找到时间写博客,导致已经有好久没更新了.惭愧!后面还会恢复进度,尽量保证每周都写吧.这里也是先恭喜一下来自瑞典的Alliance战队夺得了TI3的冠军,希望明年 ...

  2. Websphere设备、企业部署应用程序 【应用】

    Websphere设备.企业部署应用实例 环境 名称 版本号 Linux系统 CentOS-5.6-x86_64 Oracle软件 10201_database_linux_x86_64.cpio W ...

  3. #748 – 获得按下时对应位置点的大小(Getting the Size of a Contact Point during Raw Touch)

    原文:#748 – 获得按下时对应位置点的大小(Getting the Size of a Contact Point during Raw Touch) 原文地址:https://wpf.2000t ...

  4. ThreadPool类(线程池)

    原文:ThreadPool类(线程池) CLR线程池并不会在CLR初始化时立即建立线程,而是在应用程序要创建线程来运行任务时,线程池才初始化一个线程.线程池初始化时是没有线程的,线程池里的线程的初始化 ...

  5. .net core使用ef 6

    需要是core(with full .net framework)的项目,因为core本身不支持ef 6 注意新建的项目是.net framework+.net core的 有空来填坑,参考资料写的很 ...

  6. Call asynchronous method in constructor

    using System; using System.ComponentModel; using System.Threading.Tasks; public sealed class NotifyT ...

  7. 如何获取app配置文件内容

    App.config: <appSettings> <add key="FCPConnection" value="Data Source=192.16 ...

  8. swagger-editor

    前言 上一篇文章我们有提到Swagger做接口的定义是采用yaml语言的,当然,yaml是个啥,大家自行百度.阿福在此不做赘述了.但是,今天我们要来讲的是yaml支持比较好的Swagger-Edito ...

  9. android L 关机流程图

    下面是简单的流程图,从Java到kernel层. ShutdownThread.java文件 stop playing music,因为后面可能要playing shutdown music. 代码如 ...

  10. 规则“Microsoft Visual Studio 2008 的早期版本”失败。此计算机上安装了 Microsoft Visual Studio 2008 的早期版本。请在安装 SQL Server 2008 前将 Microsoft Visual Studio 2008 升级到 SP1。

    今天重装了一下系统后,需要装开发工具,我用的开发工具是Visual Studio2008 和SQL Server2008R2,装完Visual Studio2008的时候在装数据库的时候却出现这样的问 ...