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

本章将经过完成权限管理的常见业务来学习 MyBatis XML方式的基本用法html

2.1 一个简单的权限控制需求

权限管理的需求: 一个用户拥有若干角色,一个角色拥有若干权限,权限就是对某个模块资源的某种操做(增、删、改、查),这即是“用户-角色-权限”的受权模型。java

采用RBAC(Role-Based Access Control,基于角色的访问控制)方式。sql

 

2.1.1 建立数据库表

 在已经建立好的 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

 

2.1.2  建立实体类

在包(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 }

MyBatis从入门到精通:各个实体类建立的源码

能够参考上面建立Java实体类[JavaBean]的方式依次完成   SysUserRole 、SysRole 、SysPrivilege   、SysRolePrivilege 四个类的代码。工具

另外还能够根据本书在第5章介绍使用MyBatis官方提供的工具 MyBatis Generator: mybatis-generator-core-1.3.7  ,根据数据库表的信息自动生成这些实体类。post

注:

  •    MyBatis默认遵循(从SQL到JAVA)“下划线转驼峰的命名方式。如sys_user表对应的实体类名是SysUser,数据库字段user_name对应的实体类的变量名是userName。
  •    在实体类中不要使用Java的基本数据类型,基本类型包括 byte、int、short、long、float、doubule、char、boolean。由于Java基本类型会有默认值,例如当某个实体类(对应着一个数据库表)中存在private int age;若是使用age != null进行判断,结果总会为true     会致使不少隐藏的问题。 一个特殊的类型“byte[]”不是Java基本数据类型。

2.2  使用接口+XML方式

注:   接口能够配合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();
}

 

 2.3  SELECT用法

查询单条数据

先写一个根据用户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和接口的命名须要符合以下规则:

  • 标签的id属性值在任什么时候候都不能出现英文句号“.”,而且同一个命名空间下不能出现重复的id。
  • 接口的方法是能够重载的,因此接口中能够出现多个同名但参数不一样的方法,可是XML中id的值不能重复,于是接口中的全部同名方法只会对应着XML中的同一个id的select方法。最多见的用法就是,同名方法中其中一个方法增长一个 RowBound 类型的参数用于实现分页查询

XML 标签和属性讲解:

  • <select>:映射查询语句使用的标签。
  • id:命名空间中的惟一标识符,可用来表明这条语句。
  • resultMap:用于设置数据库返回值(列)的类型 和Java对象属性的映射关系。
  • SELECT * FROM sys_user WHERE id = #{id}是查询语句。
  • #{id}:MyBatis SQL中使用预编译参数的一种方式,大括号中的id是传入的参数名。

resultMap标签用于配置Java对象的属性和查询结果列的对应关系,经过resultMap中配置的columnproperty能够将查询列的值映射到type对象的属性上。

上面查询语句用到的resultMap包含的属性和标签讲解:

  • id:必填,而且惟一。在select标签中,resultMap属性的值为此处id所设置的值。
  • type:必填,用于配置查询列所映射到的Java实体对象类型。
  • extends: 选填,
  • autoMapping: 选填(true/false),该配置能够覆盖全局的 autoMappingBehavior配置。 

接着看一下id和result标签包含的意义:

id: 一个id结果,标记结果做为id(惟一值),能够帮助提升总体性能。id表明主键的字段(能够有多个),它们的属性值是经过setter方法注入的。

result :  注入到JAVA对象属性的普通结果。

  • column:  从数据库中获得的列名,或者列的别名。
  • property:映射到列结果的属性。支持经过 . 方式的属性嵌套赋值。
  • javaType:  一个Java类的彻底限定名,或经过typeAlias配置的类型别名。 

接口中定义的返回值类型必须和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字段中的属性。

 

相关文章
相关标签/搜索