一、一个简单的权限控制需求配置
设定了一个简单的权限控制需求,采用RBAC
(Role-Based Access Control ,基于角色的访问控制)方式。
步骤1:配置环境
步骤1:
- 创建数据库表
- 创建实体类
- 创建接口类(eq:UserMapper.class)
- 创建接口类的mapper XML(eq:UserMapper.xml)
- 配置MybatisUtils 配置类
- 编写select、insert、update、delete等CRUD语句
1. 创建数据库表
-- 用户表
create table `sys_user`(
id bigint not null auto_increment primary key 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 '创建时间'
)engine = innodb charset=utf8
insert into `sys_user` values('1','admin','123456','admin@mybatis','管理员',null,'2022-08-11 15:00:00');
insert into `sys_user` values('1001','test','123456','test@mybatis','测试用户',null,'2022-08-11 15:04:00');
-- 角色表
create table sys_role (
id bigint not null auto_increment primary key comment '角色ID',
role_name varchar(50) comment '用户名',
enabled int comment '有效标志',
create_by bigint comment '创建人',
create_time datetime comment '创建时间'
)engine = innodb charset=utf8
insert into `sys_role` values('1','管理员','1','1','2022-08-11 15:10:00');
insert into `sys_role` values('2','普通用户','1','1','2022-08-11 15:14:00');
-- 权限表
create table sys_privilege (
id bigint not null auto_increment primary key comment '权限ID',
privilege_name varchar(50) comment '权限名称',
privilege_url varchar(200) comment '权限URL'
)engine = innodb charset=utf8
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');
-- 用户角色关联表
create table sys_user_role(
user_id bigint comment '用户ID',
role_id bigint comment '角色ID'
)
insert into `sys_user_role` values('1','1');
insert into `sys_user_role` values('1','2');
insert into `sys_user_role` values('1001','2');
-- 角色权限关联表
create table sys_role_privilege (
role_id bigint comment '角色ID',
privilege_id bigint comment '权限ID'
)
insert into `sys_role_privilege` values('1','1');
insert into `sys_role_privilege` values('1','3');
insert into `sys_role_privilege` values('1','2');
insert into `sys_role_privilege` values('2','4');
insert into `sys_role_privilege` values('2','5');
2. 创建实体类
因为Mybatis 默认是遵循 下划线转驼峰命名
方式的,所以在创建实体类时一般都按照这种方式进行创建。
共有5个实体表
- 用户表:SysUser.class
- 角色表:SysRole.class
- 权限表:SysPrivilege.class
- 用户角色表:SysUserRole.class
- 角色权限表:SysRolePrivilege.class
用户表:SysUser.class
public class SysUser {
private Long id;
private String userName;
private String userPassword;
private String userEmail;
private String userInfo;
private byte[] headImg;
private Date createTime;
// 省略get()set() toString()方法
}
角色表:SysRole.class
public class SysRole {
private Long id;
private String roleName;
private Long createBy;
private Date createTime;
private SysUser user;
// 省略get()set() toString()方法
}
权限表:SysPrivilege.class
public class SysPrivilege {
private Long id;
private String privilegeName;
private String privilegeUrl;
// 省略get()set() toString()方法
}
用户角色表:SysUserRole.class
public class SysUserRole {
private Long userId;
private Long roleId;
// 省略get()set() toString()方法
}
角色权限表:SysRolePrivilege.class
public class SysRolePrivilege {
private Long roleId;
private Long privilegeId;
// 省略get()set() toString()方法
}
3. 创建接口类
创建五个接口类
public interface UserMapper {
}
public interface RoleMapper {
}
public interface PrivilegeMapper {
}
public interface UserRoleMapper {
}
public interface RolePrivilegeMapper {
}
4. 创建对应mapper
创建对应mapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyf.dao.UserMapper">
</mapper>
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyf.dao.RoleMapper">
</mapper>
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyf.dao.PrivilegeMapper">
</mapper>
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyf.dao.UserRoleMapper">
</mapper>
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyf.dao.RolePrivilegeMapper">
</mapper>
5. 配置MybatisUtils
MybatisUtils.class
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用mybatis第一步获取SessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
步骤2:编写业务流程
步骤2-业务流程:
- 接口添加方法(在UserMapper.class接口中,添加selectById方法)
- mapper配置映射(在UserMapper.xml中,添加resultMap和select)
- 测试类:测试
XML字段映射主要有三种方式:
- SQL别名
- 配置驼峰命名mapUnderscoreToCamelCase(常用)
- 使用ResultMap
1. 接口添加方法
public interface UserMapper {
/**
* 通过 id 查询用户
* @param id
* @return
*/
SysUser selectById(Long id);
}
2. mapper配置映射
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyf.dao.UserMapper">
<resultMap id="userMap" type="com.lyf.entity.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>
<!--#{id} 不能出现空格,否则报SQL语法错误-->
<select id="selectById" resultMap="userMap">
select * from sys_user where id = #{id}
</select>
</mapper>
3. 测试
@Test
public void testSelectById(){
// 获取SqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
//获取 UserMapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用 selectById 方法 ,查询Id 为 1 的用户
SysUser user = mapper.selectById(1l);
// user不为空
Assert.assertNotNull(user);
// userName = admin
Assert.assertEquals("admin",user.getUserName());
System.out.println(user);
}finally {
sqlSession.close();
}
}
测试结果:查询成功!!!
二、编写CRUD方法
1. Select
业务需求:查询所有用户
业务需求:查询所有用户
业务过程还是那三步:
- 接口添加方法
- mapper配置映射(以下Mapper都是在UserMapper.xml 添加)
- 测试类:测试
/**
* 查询全部用户
* @return
*/
List<SysUser> selectAll();
<select id="selectAll" resultType="com.lyf.entity.SysUser">
select id,
user_name,
user_password,
user_email,
user_info,
head_img,
create_time
from sys_user
</select>
@Test
public void testSelectAll(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 调用selectALL 方法查询所有用户
List<SysUser> userList = mapper.selectAll();
// 结果不为空
Assert.assertNotNull(userList);
// 用户数量大于0个
Assert.assertTrue(userList.size() > 0);
// 打印输出
System.out.println(userList);
}finally {
sqlSession.close();
}
}
测试结果:总量:6条
业务需求:根据用户id 获取角色信息
业务需求:根据用户id 获取角色信息
/**
* 根据用户id 获取角色信息
* @param userId
* @return
*/
List<SysRole> selectRolesByUserId(Long userId);
<select id="selectRolesByUserId" resultType="com.lyf.entity.SysRole">
select
r.id,
r.role_name roleName,
r.enabled,
r.create_by createBy,
r.create_time createTime,
u.user_name as "user.userName",
u.user_email as "user.userEmail"
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{userId}
</select>
@Test
public void testSelectRolesByUserId(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<SysRole> roleList = mapper.selectRolesByUserId(1L);
Assert.assertNotNull(roleList);
Assert.assertTrue(roleList.size()>0);
System.out.println(roleList);
}finally {
sqlSession.close();
}
}
测试结果:
2. Insert
业务需求:新增用户
业务需求:新增用户
/**
* 新增用户
* @param sysUser
* @return
* @TODO: insert attempted to return null from a method with a primitive return type (int).
*/
int insert(SysUser sysUser);
<insert id="insert">
insert into sys_user(
id,user_name,user_password,
user_email,user_info,head_img,create_time
)
values(
#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg ,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP}
)
</insert>
@Test
public void testInsert(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 创建一个对象
SysUser user = new SysUser();
user.setUserName("test1");
user.setUserPassword("123456");
user.setUserEmail("test@mybatis");
user.setUserInfo("test info");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
// 将新建的对象插入数据库中, 特别注意这里的返回值result是执行的SQL影响的行数
int result = mapper.insert(user);
// 只插入一条数据
Assert.assertEquals(1,result);
//id 为null , 没有给id 赋值, 并且没有配置回写id的值
Assert.assertNull(user.getId());
System.out.println(result);
}finally {
sqlSession.commit();
sqlSession.close();
}
}
测试结果:添加成功
业务需求:新增用户-使用 useGenerateKeys 方式
业务需求:新增用户-使用 useGenerateKeys 方式
int insert2(SysUser sysUser);
<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
insert into sys_user(
user_name,user_password,
user_email,user_info,head_img,create_time
)
values(
#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg ,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP}
)
</insert>
@Test
public void testInsert2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 创建一个对象
SysUser user = new SysUser();
user.setUserName("test1");
user.setUserPassword("123456");
user.setUserEmail("test@mybatis");
user.setUserInfo("test info");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
// 将新建的对象插入数据库中, 特别注意这里的返回值result是执行的SQL影响的行数
int result = mapper.insert2(user);
// 只插入一条数据
Assert.assertEquals(1,result);
//因为id 会写, 所以id 不为null
Assert.assertNotNull(user.getId());
if (result>0){
System.out.println("添加成功:"+user);
}
}finally {
sqlSession.commit();
sqlSession.close();
}
}
测试结果:
业务需求:新增用户 -使用 selectKey 方式
业务需求:新增用户 -使用 selectKey 方式
/**
* 新增用户 -使用 selectKey 方式
*/
int insert3(SysUser sysUser);
<insert id="insert3" >
insert into sys_user(
user_name,user_password,
user_email,user_info,head_img,create_time
)
values(
#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg ,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP}
)
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
@Test
public void testInsert3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 创建一个对象
SysUser user = new SysUser();
user.setUserName("test1");
user.setUserPassword("123456");
user.setUserEmail("test@mybatis");
user.setUserInfo("test info");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
// 将新建的对象插入数据库中, 特别注意这里的返回值result是执行的SQL影响的行数
int result = mapper.insert3(user);
// 只插入一条数据
Assert.assertEquals(1,result);
//因为id 会写, 所以id 不为null
Assert.assertNotNull(user.getId());
if (result>0){
System.out.println("添加成功:"+user);
}
}finally {
sqlSession.commit();
sqlSession.close();
}
}
测试结果:三种方式都可以添加成功,根据业务不同选择不同方式。
3. Update
业务需求:根据主键更新
业务需求:根据主键更新
/**
* 根据主键更新
* @param sysUser
* @return
*/
int updateById(SysUser sysUser);
<update id="updateById">
update sys_user
set user_name = #{userName},
user_password = #{userPassword},
user_email = #{userEmail},
user_info = #{userInfo},
head_img = #{headImg,jdbcType=BLOB},
create_time = #{createTime,jdbcType=TIMESTAMP}
where id = #{id}
</update>
@Test
public void testUpdateById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
SysUser user = mapper.selectById(1L);
user.setUserName("admin_test2");
user.setUserEmail("admin@mybatis2");
int result = mapper.updateById(user);
if (result>0){
System.out.println("更新成功:"+user);
}
}finally {
// 提交,存储在数据库
sqlSession.commit();
// 回滚,存储在缓存
// sqlSession.rollback();
sqlSession.close();
}
}
测试:
4. Delete
业务需求:通过主键删除
业务需求:通过主键删除
/**
* 通过主键删除
* @param id
* @return
*/
int deleteById(Long id);
<delete id="deleteById">
delete from sys_user where id = #{id}
</delete>
@Test
public void testDeleteById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.deleteById(1019L);
if (result >0){
System.out.println("删除成功");
}
}finally {
// sqlSession.rollback();
// 造成数据变动,都要commit提交
sqlSession.commit();
sqlSession.close();
}
}
测试成功:
三、多个接口参数
1. 接口参数为基本类型
在实际应用中经常会遇到使用多个参数的情况,我们通常将多个参数合并到一个JavaBean中,例如:User类、Role类。这种方法用起来很方便,适合参数较多的情况,并不适合全部的情况。
对于参数比较少的情况,还有两种方式可以采用:
- 使用Map类型作为参数
- 使用
@Param
注解
Map类型不太简洁,所以主要使用@Param
。
步骤:
- 接口:@Param(“userId”) Long userId
- sql传入:#{userId}
先来看下不使用@Param 注解会发生什么错误:
业务需求:根据用户ID 和角色的enabled 状态来查询用户的所有角色。
/**
* 根据用户id 和角色的enabled状态获取用户的角色
* @param userId
* @param enabled
* @return
*/
List<SysRole> selectRolesByUserIdAndRoleEnabled( Long userId, Integer enabled);
<select id="selectRolesByUserIdAndRoleEnabled"
resultType="com.lyf.entity.SysRole">
select
r.id,
r.role_name roleName,
r.enabled,
r.create_by createBy,
r.create_time createTime
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{userId} and r.enabled = #{enabled}
</select>
@Test
public void testSelectRolesByUserIdAndRoleEnabled(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<SysRole> userList = mapper.selectRolesByUserIdAndRoleEnabled(1L, 1);
System.out.println(userList);
}finally {
// sqlSession.rollback();
// 造成数据变动,都要commit提交
sqlSession.close();
// Parameter 'userId' not found. Available parameters are [arg1, arg0, param1, param2]
}
}
测试结果:
报错:Parameter 'userId' not found. Available parameters are [arg1, arg0, param1, param2]
这个错误表示,XML可用的参数只有arg1, arg0, param1, param2, param1, param2
都是Mybatis根据参数位置自定义的名字,如果将XML中的#{userId} 改为#{arg0} 或 #{param1} ,将 # {enabled} 改为 #{1} 或 #{param2} ,这个方法便可使用,但实际上不建议这么做。
使用@param 目的:指明:SQL查询#{userId}的具体参数值在哪
添加@param注解
/**
* 根据用户id 和角色的enabled状态获取用户的角色
* @param userId
* @param enabled
* @return
*/
List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("userId") Long userId,@Param("enabled") Integer enabled);
测试:测试成功
这种情况一般出现在两个参数以上,因为一个参数的时候,Mybatis不关心这个参数叫什么名字,就会直接把这个唯一的参数值拿来使用。
2. 接口参数为对象类型
步骤:
- 接口:@Param(“user”) SysUser user,
- sql传入:#{user.id}
/**
* 根据用户id 和角色的enabled状态获取用户的角色
* 参数类型:实体类对象
* @param user
* @param role
* @return
*/
List<SysRole> selectRolesByUserIdAndRoleEnabled2(@Param("user") SysUser user,@Param("role") SysRole role );
<select id="selectRolesByUserIdAndRoleEnabled2"
resultType="com.lyf.entity.SysRole">
select
r.id,
r.role_name roleName,
r.enabled,
r.create_by createBy,
r.create_time createTime
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{user.id} and r.enabled = #{role.enabled}
</select>
@Test
public void testSelectRolesByUserIdAndRoleEnabled2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
SysUser user = new SysUser();
user.setId(1L);
SysRole sysRole = new SysRole();
sysRole.setEnabled(1L);
List<SysRole> userList = mapper.selectRolesByUserIdAndRoleEnabled2(user,sysRole );
System.out.println(userList);
}finally {
// sqlSession.rollback();
// 造成数据变动,都要commit提交
sqlSession.close();
// Parameter 'userId' not found. Available parameters are [arg1, arg0, param1, param2]
}
}
测试成功:
四、Mapper接口动态代理实现原理
待续