之前我们说过,MyBatis是一款优秀的支持自定义SQL查询、存储过程和高级映射的持久层框架,而且,与JDBC相比,MyBatis简化了相关的代码,使得代码更加简洁。所以,MyBatis最常用的地方就是结合数据库一起进行相关CRUD操作。在正式进行增删改查操作前,我们需要准备一下需要的数据库和其他文件。
我们假设一个场景,来设定一个简单的权限控制需求,采用RBAC(Role-Based Access Control,基于角色的访问控制方式),这个简单的权限管理将会是我后续文章的基础示例。那么这个权限管理都有哪些需求呢?我们结合现实考虑一下,一个用户通常拥有若干个角色,一个角色通常也拥有若干个权限(即对某种资源的某种基于但不限于增删改查的操作),于是就构成了“用户-角色-权限”的模型。在这种模型中,用户与角色之间是一对多的关系,即一个用户对应拥有多种角色,角色与权限之间也是一对多的关系。在现实中也常有这样的场景,普通用户一般拥有登录和查询等基础权限,而管理员除了拥有普通用户拥有的登录和查询等基础权限外,它还可以对普通用户进行管理,即有权决定谁是普通用户,或是可以对普通用户进行增删操作。
根据我们描述的需求,显而易见会有五张数据表产生,即用户表、角色表和权限表,以及用户角色联系表和角色权限联系表。那么接下来我们就在数据库中创建这五张表,创建代码如下:
//创建数据库study
CREATE DATABASE study;
USE study;
//创建用户表t_user
CREATE TABLE t_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 t_user COMMENT '用户表';
//创建角色表t_role
CREATE TABLE t_role(
id BIGINT NOT NULL auto_increment COMMENT '角色ID',
role_name VARCHAR(50) COMMENT '角色名',
enabled INT COMMENT '有效标志',
create_author BIGINT COMMENT '创建者',
create_time datetime COMMENT '创建时间',
PRIMARY KEY(id)
);
ALTER TABLE t_role COMMENT '角色表';
//创建权限表t_privilege
CREATE TABLE t_privilege(
id BIGINT NOT NULL auto_increment COMMENT '权限ID',
privilege_name VARCHAR(50) COMMENT '权限名称',
privilege_url VARCHAR(50) COMMENT '权限url',
PRIMARY KEY(id)
);
ALTER TABLE t_privilege COMMENT '权限表';
//创建用户角色联系表t_user_role
CREATE TABLE t_user_role(
user_id BIGINT COMMENT '用户ID',
role_id BIGINT COMMENT '角色ID'
);
ALTER TABLE t_user_role COMMENT '用户角色关联表';
//创建角色权限联系表t_role_privilege
CREATE TABLE t_role_privilege(
role_id BIGINT COMMENT '角色ID',
privilege_id BIGINT COMMENT '权限ID'
);
ALTER TABLE t_role_privilege COMMENT '角色权限关联表';
我们要习惯使用SQL语句来进行数据库表的创建,尽量减少那种直接在数据库中创建表的方式。我们看到,上面创建数据库表的代码中,很明显有COMMENT,其实COMMENT就相当于注释、备注的意思,即对数据库表中的各个字段进行解释,我们以后在创建数据库表的时候也要多多使用这种有好的创建数据库表方式。
创建完成后的数据库中的五张表的结构是这样的:
以上这五张表示的字段可以根据自身情况自行决定,包括表明,各字段的命名都可自行改变。本人的这五张表的结构完全参考自刘增辉老师的《MyBatis从入门到精通》一书。
接下来我们给这五张表插入一些数据,为了后续程序运行需要,每张表插入一两条数据即可,不必过多。当然如果有自己的想法,则可以根据需要多多插入几条数据。
//给t_user用户表插入数据
INSERT INTO t_user VALUES
('1001','张三','123123','123456@study','我是张三',NULL,NOW());
INSERT INTO t_user VALUES
('1002','李四','123456','456456@study','我是李四',NULL,NOW());
//给t_role角色表插入数据
INSERT INTO t_role VALUES('1','管理员','1','1',NOW());
INSERT INTO t_role VALUES('2','普通用户','1','1',NOW());
//给t_privilege权限表插入数据
INSERT INTO t_privilege VALUES('1','用户管理','/users');
INSERT INTO t_privilege VALUES('2','角色管理','/roles');
INSERT INTO t_privilege VALUES('3','系统日志','/logs');
INSERT INTO t_privilege VALUES('4','人员维护','/persons');
INSERT INTO t_privilege VALUES('5','单位维护','/companiess');
//给t_user_role用户角色关联表插入数据
INSERT INTO t_user_role VALUES('1001','1');
INSERT INTO t_user_role VALUES('1001','2');
INSERT INTO t_user_role VALUES('1002','2');
//给t_role_privilege角色权限关联表插入数据
INSERT INTO t_role_privilege VALUES('1','1');
INSERT INTO t_role_privilege VALUES('1','3');
INSERT INTO t_role_privilege VALUES('1','2');
INSERT INTO t_role_privilege VALUES('2','4');
INSERT INTO t_role_privilege VALUES('2','5');
插入若干条数据之后的五张表展示如下:
上述插入数据的方法在后续工作中也是用得到的,之前说过,要习惯使用SQL语句!!!结合插入的数据,我们有以下信息:
- 用户ID为1001的张三,既是管理员,也是普通用户,所以拥有用户管理、角色管理、系统日志、人员维护、单位维护的所有权限;
- 用户ID为1002的李四,只是普通用户,所以只拥有人员维护和单位维护两个权限。
以上数据仅为后续工作工作,并不具有实际意义,请不要对号入座。
接下来,我们在IDEA中新建一个maven项目,命名方式自选,本人在这里命名为Mybatis-CRUD。然后,先在pom.xml文件中导入相关依赖。完整的pom.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Mybatis-CRUD</artifactId>
<version>1.0-SNAPSHOT</version>
<!--导入依赖-->
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--JUnit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
然后,我们还需要完成上面说过的五个数据库表的实体类。为便于管理,我们在src/main/java目录下先创建一个包,命名为pojo,然后将五个数据库表的实体类(分别命名为User、Role、Privileg、UserRole、RolePrivilege)放在这个pojo包下。需要说明的是,MyBatis默认是遵循“下划线转驼峰”的命名方式的。怎么说呢,就是如果我们在数据库中创建表的时候各字段的命名是以下划线的方式来命名的,那么在MyBatis中,我们应该遵循MyBatis的命名方式,使用驼峰命名的方法。举个例子,我们在t_user表中对于用户名的命名方式是user_name的下划线方式的,那么在实体类中,我们使用userName小驼峰的命名方式。其实,为了方便和统一,我们也可以将数据库表中的字段以小驼峰的方式命名,避免在后续工作中混淆。
User实体类的代码如下,包含了User的属性和各属性的get()和set()方法,以及toString()方法。写toString()方法是为了将查询结果成功输出到控制台。
package pojo;
import java.util.Arrays;
import java.util.Date;
public class User {
private Long id; //用户ID
private String userName; //用户名
private String userPassword; //密码
private String userEmail; //Email
private String userInfo; //简介
private byte[] headImg; //头像
private Date createTime; //创建时间
public User() {
}
public User(Long id, String userName, String userPassword,
String userEmail, String userInfo, byte[] headImg, Date createTime) {
this.id = id;
this.userName = userName;
this.userPassword = userPassword;
this.userEmail = userEmail;
this.userInfo = userInfo;
this.headImg = headImg;
this.createTime = createTime;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
public String getUserInfo() {
return userInfo;
}
public void setUserInfo(String userInfo) {
this.userInfo = userInfo;
}
public byte[] getHeadImg() {
return headImg;
}
public void setHeadImg(byte[] headImg) {
this.headImg = headImg;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", userPassword='" + userPassword + '\'' +
", userEmail='" + userEmail + '\'' +
", userInfo='" + userInfo + '\'' +
", headImg=" + Arrays.toString(headImg) +
", createTime=" + createTime +
'}';
}
}
Role实体类的代码如下:
package pojo;
import java.util.Date;
public class Role {
private Long id; //角色ID
private String roleName; //角色名
private int enabled; //有效标志
private Long createAuthor; //创建者
private Date createTime; //创建时间
public Role() {
}
public Role(Long id, String roleName, int enabled,
Long createAuthor, Date createTime) {
this.id = id;
this.roleName = roleName;
this.enabled = enabled;
this.createAuthor = createAuthor;
this.createTime = createTime;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public int getEnabled() {
return enabled;
}
public void setEnabled(int enabled) {
this.enabled = enabled;
}
public Long getCreateAuthor() {
return createAuthor;
}
public void setCreateAuthor(Long createAuthor) {
this.createAuthor = createAuthor;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", roleName='" + roleName + '\'' +
", enabled=" + enabled +
", createAuthor=" + createAuthor +
", createTime=" + createTime +
'}';
}
}
Privilege实体类代码如下:
package pojo;
public class Privilege {
private Long id; //权限ID
private String privilegeName; //权限名称
private String privilegeUrl; //权限url
public Privilege() {
}
public Privilege(Long id, String privilegeName, String privilegeUrl) {
this.id = id;
this.privilegeName = privilegeName;
this.privilegeUrl = privilegeUrl;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPrivilegeName() {
return privilegeName;
}
public void setPrivilegeName(String privilegeName) {
this.privilegeName = privilegeName;
}
public String getPrivilegeUrl() {
return privilegeUrl;
}
public void setPrivilegeUrl(String privilegeUrl) {
this.privilegeUrl = privilegeUrl;
}
@Override
public String toString() {
return "Privilege{" +
"id=" + id +
", privilegeName='" + privilegeName + '\'' +
", privilegeUrl='" + privilegeUrl + '\'' +
'}';
}
}
UserRole实体类代码如下:
package pojo;
public class UserRole {
private Long userId; //用户ID
private Long roleId; //角色ID
public UserRole() {
}
public UserRole(Long userId, Long roleId) {
this.userId = userId;
this.roleId = roleId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
@Override
public String toString() {
return "UserRole{" +
"userId=" + userId +
", roleId=" + roleId +
'}';
}
}
RolePrivilege实体类代码如下:
package pojo;
public class RolePrivilege {
private Long roleId; //角色ID
private Long privilegeId; //权限ID
public RolePrivilege() {
}
public RolePrivilege(Long roleId, Long privilegeId) {
this.roleId = roleId;
this.privilegeId = privilegeId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getPrivilegeId() {
return privilegeId;
}
public void setPrivilegeId(Long privilegeId) {
this.privilegeId = privilegeId;
}
@Override
public String toString() {
return "RolePrivilege{" +
"roleId=" + roleId +
", privilegeId=" + privilegeId +
'}';
}
}
创建实体类的过程很枯燥,只是简单的工作重复做的过程。但是为了节省时间,我们可以使用Alt+Insert快捷键来快速选择我们要生成的构造函数以及get()和set()方法,以及toString()方法。具体操作方法为:在实体类中先写完各个属性,之后在实体类中(注意一定要在实体类中)鼠标右击选择Generate或者直接使用Alt+Insert快捷键进入选择界面,之后选择我们需要生成的函数类型就行。
当五个实体类都创建完成之后,我们来讨论一下MyBatis的映射语句,为后续程序编写做好准备。MyBatis的真正强大之处就在于它的映射语句,这也是MyBatis的魔力所在。由于它的映射语句异常强大,映射器的XML文件就显得相对简单。如果将其与具有相同功能的JDBC代码进行对比,就会发现,使用这种方法可节省大约95%的代码量。后续我们会使用到更多的xxxMapper文件和xxxMapper.xml文件。MyBatis通常使用接口配合XML文件使用,后续我们会体会到这种方式的工作流程的顺畅。不过在此之前呢,我们需要先配置一下MyBatis的核心配置文件,通常给它命名为Mybatis-config.xml,并放在src/main/resources文件下。其核心配置文件的内容如下:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
其实我们不难发现,Mybatis核心配置文件中主要就是连接数据库的内容,什么连接数据库的驱动、用户名和密码之类的。
接下来,我们创建与数据库中的五个表相对应的各自的XML文件,分别命名为UserMapper.xml、RoleMapper.xml、PrivilegeMapper.xml、UserRoleMapper.xml、RolePrivilegeMapper.xml。同样,为了便于管理,我们选择将这五个xml文件放在一个Mapper包下统一管理,Mapper包需要我们自己创建,我们选择让其位于src/main/resources文件夹下。然后我们还需要这五个XML文件的对应的接口类,还是和上面一样,为了便于管理,我们将其放在新建包Interface下,位于src/main/java下,分别命名为UserMapper、RoleMapper、PrivilegeMapper、UserRoleMapper、RolePrivilegeMapper,全部都是接口类文件,.java类型的。可能这样命名为xxxMapper的命名方式不是很规范,放在名为Interface的包下也显得不是很规范,其实正确的应该放在dao文件夹下,但是由于我目前还未接触到数据持久层Dao层,所以暂时就命名一个不太规范的包吧。
那么此时这五个接口类的内容都是空的,后续我们会写一些增删改查的方法在里边。
//接口类UserMapper
package Interface;
public interface UserMapper{
}
//接口类RoleMapper
package Interface;
public interface RoleMapper{
}
//接口类PrivilegeMapper
package Interface;
public interface PrivilegeMapper{
}
//接口类UserRoleMapper
package Interface;
public interface UserRoleMapper{
}
//接口类RolePrivilegeMapper
package Interface;
public interface RolePrivilegeMapper {
}
接下来,我们再看看五个XML文件。其实此时我们还没有进行相关的查询呢,所以此时XML文件其实也是空的。
//UserMapper.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="Interface.UserMapper">
</mapper>
//RoleMapper.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="Interface.RoleMapper">
</mapper>
//PrivilegeMapper.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="Interface.PrivilegeMapper">
</mapper>
//UserRoleMapper.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="Interface.UserRoleMapper">
</mapper>
//RolePrivilegeMapper
<?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="Interface.RolePrivilegeMapper">
</mapper>
我们需要注意的是<mapper>根标签的namespace属性。当Mapper接口和XML文件关联的时候,命名空间namespace的值就需要配置成接口的全限定名称,例如UserMapper对应的Interface.UserMapper,Mybatis内部就是通过这个值将接口和XML关联起来的。
接下来,我们需要在Mybatis的配置文件Mybatis-config.xml中的mappers元素中配置所有的刚才写了的XML文件。
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/RoleMapper.xml"/>
<mapper resource="mapper/PrivilegeMapper.xml"/>
<mapper resource="mapper/UserRoleMapper.xml"/>
<mapper resource="mapper/RolePrivilegeMapper.xml"/>
</mappers>
<!--
<mappers>
<package name = "mapper"/>
</mappers>
-->
注意将<mappers>写在<configuration>里边,但写在<environments>外边。以上两种书写方式都能让XML文件和接口对应。但是第一种方式需要将所有映射文件一一列举出来,操作起来比较麻烦。第二种书写方式就看起来挺简单的,这种配置方式会先查找mapper包下所有的接口,并循环对接口以下操作:(1)判断接口对应的命名空间是否已经配置过,如果配置过就抛出异常,没有配置过就继续;(2)加载接口对应的XML文件,将接口的全限定名称转换为路径,即将接口Interface/UserMapper转换为mapper.UserMapper.xml,以.xml为后缀搜索XML资源,如果找到就解析XML。
增加内容之后的Mybatis-config.xml的全部内容为:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/RoleMapper.xml"/>
<mapper resource="mapper/PrivilegeMapper.xml"/>
<mapper resource="mapper/UserRoleMapper.xml"/>
<mapper resource="mapper/RolePrivilegeMapper.xml"/>
</mappers>
</configuration>
此时,我们对于正式的增删改查的准备工作已经完成。接下来,我们开始着手进行正式的增删改查工作。
需要注意的,本人在进行后续博文更新的时候,参照的内容和代码完全来自于刘增辉老师的《MyBatis从入门到精通》一书,书上的关于数据库的操作是非常连贯的,所以需要及时做好记录和注释,避免代码过多之后容易混乱。