您现在的位置是:首页 > 后台技术 > MyBatisMyBatis

MyBatisXML基本用法(第二章)(图文)

第十三双眼睛2020-04-13【MyBatis】人已围观

简介本章,我们设计了一个简单的权限控制需求,采用RBAC(基于角色的访问控制方式),这个简单的权限管理系统会贯穿本书的所有示例。本章通过完成权限管理的常见业务来学习MyBatisXML的基本用法。

一个简单的权限控制需求
在这里简单描述一下权限管理的需求:一个用户拥有若干角色,一个角色拥有若干权限,权限就是对某个资源的某种操作,这样就构成了用户-角色-权限的授权模型,在这种模型种,用户与角色之间,角色与权限之间,一般是多对多的关系。

创建数据库表
首先要创建5张表:用户表,角色表,权限表,用户角色关系表和角色权限关系表,在已经创建好的数据库种执行如下脚本:
create table sys_user(
    id INT not null auto_increment COMMENT '用户Id',
    user_name varchar(20) COMMENT '用户名',
    user_password varchar(20) COMMENT '密码',
    user_mail varchar(20) COMMENT '邮箱',
    user_info varchar(20) COMMENT '简介',
    head_img varchar(20) COMMENT '头像',
    create_time datetime COMMENT '创建时间',
    PRIMARY key(id)
);

alter table SYS_USER COMMENT '用户表';
create table sys_role(
    id int not null auto_increment COMMENT '角色id',
    role_name varchar(20) COMMENT '角色名',
    enabled int COMMENT '有效标志',
    create_by int comment '创建人',
    create_time datetime COMMENT '创建时间',
    PRIMARY key (id)
);

alter table sys_role COMMENT '角色表';
 
create table sys_privilege(
    id int not null auto_increment COMMENT '权限id',
    privilege_name varchar(20) COMMENT '权限名称',
    privilege_url varchar(20) COMMENT '权限url',
    PRIMARY key(id)
);

alter table sys_privilege COMMENT '权限表';
create table sys_user_role(
    user_id int COMMENT '用户id',
    role_id int COMMENT '角色id'
);

alter table sys_user_role COMMENT '用户角色关联表';
create table sys_role_privilege(
    role_id int COMMENT '角色id',
    privilege_id int COMMENT '权限id'
)

alter table sys_role_privilege COMMENT '角色权限关联表';

为了方便对表进行操作,没有创建表之间的外键关系,对于表之间的关系,会通过业务逻辑来控制,为了方便测试,在表中插入一些数据:语句如下:
添加用户
insert into sys_user values (1,'admin','123456','admin@mybatis.tk','管理员',null,'2020-03-03 00:00:00');
insert into sys_user values (1001,'test','123456','test@mybatis.tk','测试用户',null,'2020-03-04 00:00:00');
添加角色
insert into sys_role values(1,'管理员',1,1,'2020-03-04 15:15:25');
insert into sys_role values(2,'普通用户',1,1,'2020-03-04 16:15:25');
添加用户角色关系
insert into syso_user_role values (1,1);
insert into syso_user_role values (1,2);
insert into syso_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,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);

创建实体类
MyBatis默认是遵循下划线转驼峰命名方式的,所以在创建实体类时一般都遵循这种方式进行创建。如果采用了驼峰转下划线命名方式,我们即不需要配置resultMap进行数据库字段和实体类属性的映射。在MyBatis中,关于数据库字段和java类型的对应关系,不需要可以牢记,但是要注意一个特殊的类型,byte[],这个类型一般和数据库中的blob,longvarbinary以及一些和二进制流相对应。
注意:因为java中的基本类型会有默认值,例如当某个类中存在private int age,创建这个类时,age会有默认值,当使用age属性时,它总会有值,在某些情况下,会无法实现age为null,并且在动态sql部分,无法使用if age==null,因此实体类中尽量不要用基本类型。而要用他们的包装类。
创建实体类的过程比较枯燥,后面可以通过MyBatis官方提供的工具MyBatis Generator,根据数据库表的信息自动生成这些类。以减少工作量,有个这个工具的使用方法会在第五章介绍。

使用XML方式
MyBatis的强大之处在于它的映射语句,这也是它的魔力所在。由于它的映射语句异常强大,映射器的XML文件就显得简单,MyBatis3.0相比于2.0,一个最大的变化,就是支持使用接口来调用方法。以前使用SqlSession通过命名空间调用MyBatis方法时,首先需要通过命名空间和方法id组成的字符串来调用相应的方法,当参数多于1个的时候,需要将参数放到一个map中,通过map传递多个参数,使用起来很不方便,而且还无法避免很多重复的代码
使用接口调用方式就会方便很多,MyBatis使用java动态代理的方式可以通过接口来调用相应的方法,不需要提供接口的实现类,更不需要再接口中使用SqlSession以通过命名空间简介调用,另外当有多个参数的时候,通过参数注解@Param设置的参数名字省去了构造map参数的过程,尤其再使用spring的时候,可以配置为自动扫描所有的接口类,直接将接口注入到需要的地方,因为接口使用更方便,而且它已被广泛使用,因此本书中会主要使用接口调用的形式同时也会提供一些SqlSession方式调用的例子帮助大家理解MyBatis
接口可以配合注解来使用,也可以配合XML来使用。XML可以单独使用,但是注解必须再接口中使用。
首先在resources目录下,创建5章表对应的mapper.xml文件,并在src目录下创建对应的接口类。
到目前为止,Mapper对应的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="com.xjava.mybatis.dao.StudentDao">
   
</mapper>

需要注意的是,<mapper>根标签的namespace属性,当mapper文件和xml文件进行关联的时候命名空间namespace的值就是接口的全限定名。准备好这几个mapper文件后,还需要再mybatis-config.xml文件中mapper元素中配置所有的mapper,例如:
<mappers>
    <mapper resource="com/xjava/mybatis/mapper/StudentMapper.xml"/>
</mappers>

这种配置方式需要将Mapper文件一一列举出来,只要新增加了一个配置文件,都需要再此处添加,比较麻烦,还有一种简单的配置方式,如下:
<mappers>
    <package name="com.xjava.mybatis.mapper"/>
</mappers>

这种配置方式会首先查找com.xjava.mybatis.mapper报下所有的接口,循环对接口进行如下操作:
1判断接口对应的命名空间是否已经配置过,如果配置过就抛出异常,如果没有配置过就进行接下来的操作
2加载接口对应的xml映射文件,将接口全限定名转为路径
3处理接口中的注解方法

select用法
再权限系统中有几个常见的业务,我们需要查询出系统对应的用户,角色,权限等数据,在使用纯粹的jdbc时,需要写查询语句,并对结果集进行手动处理,将结果集映射到对象的属性中,在使用MyBatis时,只需要在xml中添加一个select元素,写一个sql,再做一些简单的配置,就可以将查询结果映射到对象中。先写一个根据用户id查询用户信息的方法。代码如下:
<select id="getUser" resultType="User">
    select * from sys_user    
</select> 

public interface UserMapper {
    
    User getUser(Integer id);
}
可以发现,XML中的select标签id属性值和定义的接口方法名是一样的,Mybatis就是通过这种方式将接口方法和XML中定义的sql语句关联再一起的,如果接口方法中没有和xml中的id属性值相对应,启动程序便会报错,映射xml和接口需要符合如下规则:
1当只使用xml而不使用接口的时候,namespace的值可以设置为任意不重复的名称
2标签的id属性值再任何时候都不能出现英文句号.,并且同一个命名空间下不能出现重复的id.
3因为接口方法是可以重载的,所以接口中可以出现多个同名但是参数不同的方法,但是xml中的id值不能重复,因而接口中所有的同名方法会对应xml中同一个id的方法,最常见的用法就是,同名方法中的其中一个方法增加一个rowbound类型的参数,用于实现分页查询。
明白上述两者之间的关系后,通过userMapper.xml先来了解以下XML中一些标签和属性的作用。
<select>:映射查询语句使用的标签
id:命名空间中唯一的标识符,可用来代表这条语句
resultMap:用于设置返回值的类型和映射关系
select * from sys_user where id=#{id}是查询语句
#{id}MyBatis中一种预编译参数的方式,大括号中的是传入的参数名
resultMap用于设置java对象属性和查询结果列的映射关系,通过resultMap中的property和column可以将查询列的属性映射到type对应的对象上。
resultMap是一种很重要的映射方法,我们需要熟练掌握resultMap的配置方法。resultMap中包含的所有属性如下:
id:必填,并且唯一,在select标签中指定的即为此处的id
type:必填,用于配置查询列所映射到的java对象类型
extends:选填,可以配置当前的resultMap继承自其他的resultMap,属性值为继承的resultMap的id
atuoMapptin:选填,可选值为true和false,用于配置是否启用非映射字段(没有配置在resultMap中的字段)的自动映射功能,该配置可以覆盖全局的autoMappingBehavior配置
以上是resultMap的属性,resultMap包含的标签如下:
construsctor:配置使用构造方法注入结果,包含以下两个子标签
1idArg:id参数,标记结果作为id,可以帮助提高整体性能
2arg:注入到构造方法中的一个普通结果
id:一个id结果,标记结果作为id,可以帮助提高整体性能
result:注入到java对象属性的普通结果
associaton:一个复杂的类型关联,许多结果将包装称为这种类型
collection:复杂类型的集合
discriminator:根据结果值来确定使用哪个结果映射。
case:基于某些值得结果映射。

首先来了解一下这些标签间的关系
constructor:通过构造方法注入属性的结果值,构造方法中的idArg,arg参数分别对应着resultMap中的id,result标签,他们的含义相同,只是注入方式不同。
resultMap中的id和result标签包含的属性相同,不同地方在于id表示的是主键的字段,他们的属性是通过setter方法注入的。
column:从数据库得到的列名,或者是列的别名
property:映射到列结果的属性,可以映射简单的属性如username,也可以映射一些复杂对象中的属性,如address.street.number,这会通过.的方式嵌套赋值。
javaType:一个java类的全限定名,或者一个类型别名,如果映射到一个javabean,通常可以自动判断属性的类型,如果映射到HashMap,则需要明确指定javaType.
jdbcType:列对应的数据库类型。
typeHandler:使用这个属性可以覆盖默认的类型处理器,这个属性值是类的全限定名或者别名。
下面看以下接口的方法返回值如何定义
接口中定义的返回值类型必须和xml中配置的resultType类型一致,否则会应为类型不一致而抛出异常。
在定义接口中对应方法的返回值类型时,必须注意查询sql可能返回的结果数量
当返回结果的数量最多只有一个时,可以将返回值定义为单一对象。当返回值可能有多个时,必须定义为List,如果不定义为List,就会抛出TooManyResultException异常。
在数据库中,因为大多数数据库设置不区分大小写,因此下划线方式命名很常见,而java中,一般使用驼峰命名,因此MyBatis提供了一个全局属性来自动将数据库的字段映射到java对象。

insert用法
和上一节得select相比,insert要简单得多,只有让它返回主键时,由于不同的数据库主键的生成方式不同,这种情况可能会复杂一些。
首先再UserDao中添加如下方法:
public interface UserMapper {
    
    User getUser(Integer id);
    
    int insertUser(User user);
}

在userMapper.xml中添加如下代码:
<insert id="insertUser">
    insert into sys_user(id,user_name,user_password,user_email,user_info,head_img,create_time)
    values (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg},#{createTime})
</insert>

先看insert元素,这个标签包含以下属性:
id:命名空间中的唯一标识符,可以用来代表这条语句
parameterType:即将传入的语句参数的全限定名或者别名,这个属性是可选的。因为MyBatis可以推断出传入语句的参数,因此不建议配置改属性。
flushcache:任何时候,只要被调用,都会清空一级二级缓存。
timeout:设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
statementType:对于statement,prepared,callable,Mybatis会分别使用对应的statement,preparedstatement,callablestatement,默认值是prepared.
useGeneratedKeys:默认值为false,如果设置为true,Mybatis会使用jdbc的generateKeys方法来取出由数据库内部生成的主键。
KeyProperty:Mybatis通过generateKeys获取主键后需要赋值的属性,如果希望得到多个数据库生成的列,属性值也可以是以逗号分隔的属性名称列表。
keyCloumn:仅仅对insert和update有用,通过生成的键值设置表中的列名,这个设置仅在某些数据库中是必须的,当主键列不是表中的第一列时需要设置,如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
databaseId:如果配置了databaseProvider,Mybatis会加载所有不带databaseid的或者匹配当前database语句,如果同时存在带databaseId和不带databaseId的语句,后者会被忽略。
此处<insert>中的就是一个简单的insert语句,将所有的列都列举出来,在values中通过#{property}方式将参数中获取属性的值。为了防止类型错误,对于一些特殊的数据类型,建议指定具体的jdbcType类型,例如userImg指定为blob型,createTime指定为timestamp类型。
特别说明!
blob对应的类型是byteArrayInputStream,就是二进制数据流。
由于数据库区分date,time,datetime.但是java中一般都使用java.util.date。因此为了保证数据类型的正确性,需要手动指定日期的类型,date,time,datetime对应的jdbc类型分别是date,time,timestamp。
接下来看一下对应的方法int insertUser(User user);,很多人会把这个int类型的返回值当作数据库返回的主键值,它其实是执行的sql影响的行数,这个和日志中的update:1是一致的。既然这个返回值不是主键的值,那么如何获得该主键的值呢,下面提供两种方法,可以涵盖不同的数据库。
使用jdbc返回主键自增的值
在使用主键自增时,插入数据后可能需要得到自增的主键值,然后用这个值进行一些别的操作,现在增加一个insert2方法
int insertUser2(User user);
 
<insert id="insertUser2" 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},#{createTime})
</insert> 
主要的变化是在insert标签上加了两个属性,useGeneratedKeys="true"和 keyProperty="id",useGeneratedKeys设置为true后,Mybatis会使用jdbc的 useGeneratedKeys方法获取数据库内部生成的主键。然后赋值给指定的id,当需要设置多个属性时,使用逗号隔开,这种情况下通常还需要设置keyColumn属性,按顺序指定数据库的列,这里的列值会和keyProperty配置的属性一一对应。,由于要使用数据库返回的主键,sql中去掉id列和#{id}.

使用selectKey返回主键的值,上面这种写法只可用于主键自增的数据库,比如mysql,但是有些数据库不支持主键自增长,比如oracle是通过一个序列获得一个主键,然后将这个值赋值给id,然后插入数据库,对于这种情况,可以采用另外一种方式,使用<selectKey>标签来获取主键的值,这种方式不仅适用于主键不自增的数据库,也适用于主键自增的数据库。
在接口中新增一个insert3方法int insertUser3(User user);UserMapper.xml文件中的内容如下:
<insert id="insertUser3" 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},#{createTime})
<selectKey keyColumn="id" resultType="int" keyProperty="id" order="after">
select last_insert_id()
</selectKey>
</insert> 

selectKey标签的keyColumn,keyProperty和上面useGeneratedKeys的用法含义相同,这里的resultType用于设置返回值类型,order属性的设置和数据库类型有关,再mysql中,order设置的值是after,因为当前记录的值在insert语句执行成功后才能获取到,而在oracle数据库中,oracle的值要设置为before,这是因为oracle中需要先从序列中获取值,将值作为主键插入到数据库中。
以上是对selectKey标签中属性的介绍,接着看一下selectKey元素的内容,它的内容就是一个独立的sql语句,在oracle中,用来获取一个这个语句对于不同的数据库来说不一样。
update用法
接下来看一个简单的通过主键更新的update方法的例子。在接口中添加一个方法。int update(User user);
<update id="update">
    update sys_user set user_name = #{userName}
</update> 

同样,int类型的返回值也表示执行sql后影响的数据行数

delete用法
delete与update相似,简单说明一下,delete如何使用,在UserMapper.xml中添加一个简单的例子
int deleteById(Integer id);
根据主键删除数据的时候,如果主键只有这一个字段,那么就可以像这个方法一样,使用一个参数id,这个方法对应UserMapper.xml文件中的写法如下:
<delete id="deleteById">
    delete from sys_user where id=#{id}
</delete>

接口中的参数为Integer id,如果将参数类型修改如下,也是正确的,int delteById(User user);

多个接口参数的用法
通过观察,不难发现目前所列举的接口方法中参数只有一个,参数的类型分为两种,一种是基本类型,一种是javabean
当参数是基本类型的时候,它在xml中对应的sql语句中只会使用一个参数,例如delete方法,当参数是一个javabean的时候,它在xml文件中对应的sql语句会有多个参数,例如insert,update方法。
在实际应用中,会经常遇到多个参数的情况,对于这种情况,我们可以使用Map类型或者使用@Param注解。
使用Map类型作为参数的方法,就是在Map中通过key来映射xml中sql使用的参数值名称,value存放值,需要多个参数时,通过map的key-value方式传递参数值,由于这种方式需要手动创建Map,并进行赋值,并不简洁,主要介绍一下使用@Param注解的使用。
例如:User getUser2(String userName,String password);
这个接口方法对应的xml文件如下:
<select id="getUser2">
    select * from sys_user where user_name = #{userName} and password= #{password}
</select>
测试的时候会报如下错误:parameter userName not found,Available parameters are [0,1,param1,param2],
这个错误表示,xml可用的参数只有0,1,param1,param2,没有userId,0和1,param1,param2都是mybatis根据参数位置自己定义的名字,如果将userName改为#{0}或者#{param1},这个方法就会被正常调用了,但是这样不好 。现在用注解的方式来实现,代码如下:
User getUser2(@Param("userName")String userName,@Param("password")String password);
给参数配置@Param注解后,mybatis就会自动将参数封装称为map类型。@Param注解值会作为map中的key,因此在sql部分就可以通过配置的注解来使用这个值。而只有一个值的时候,mybatis不关心这个参数的名字叫什么,直接拿来用。

mapper接口动态代理实现原理
通过上面的学习,有人会有疑问,为什么mapper接口没有实现类,却能正常调用呢。这是因为mybatis在mapper接口上使用了动态代理的一种非常规的用法,熟悉这种动态代理的用法不仅有利于理解MyBatis接口和xml的关系,还能开阔思路,接下来说明这种动态代理的思路。
假设有一个mapper接口如下:
public interface UserMapper{
          List<User> selectAllUser();
}
这里使用java动态代理方式创建一个类,代码如下:
package com.xjava.mybatis.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.xjava.mybatis.dao.UserMapper;
import com.xjava.mybatis.entity.User;

public class MyMapper<T> implements InvocationHandler{

    private Class<T> mapperInterface;
    
    private SqlSession sqlSession;
    
    public MyMapper(Class<T> mapperInterface,SqlSession sqlSession){
        this.mapperInterface=mapperInterface;
        this.sqlSession = sqlSession;
    }
    
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        List<Object> list = sqlSession.selectList(mapperInterface.getCanonicalName()+"."+method.getName());
        return list;
    }
    
    public static void main(String[] args) {
        // 获取sqlSession
        SqlSession sqlSession = StudentTest.getSqlSession();
        
        // 获取userMapper接口
        MyMapper userMapperProxy = new MyMapper(UserMapper.class,sqlSession);
        
        UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                new Class[]{UserMapper.class}, userMapperProxy);
        
        List<User> selectAll = userMapper.selectAll();
    }

    

}

 
从代理类中可以看到,当调用一个接口的方法时,会先通过接口的全限定名和当前调用的方法名组合称为一个方法id,这个id的值就是映射xml中的namespace和具体方法id的组合,所以可以在代理方法中使用sqlSession以命名空间的方式去调用方法,通过这种方式可以将接口和xml文件中的方法关联起来,这种代理方式和常规代理的不同方式在于这里没有对某个类进行代理,而是通过代理转化成了对其代码的调通。
由于方法参数和返回值存在很多种情况,因此mybatis的内部实现会比上面的逻辑更加复杂,正是因为mybatis对接口动态代理的实现,我们在使用接口的时候才会如此简单。

 

Tags:MyBatis   持久层

很赞哦! ()

文章评论

    共有条评论来说两句吧...

    用户名:

    验证码:

本站推荐

站点信息

  • 网站名称:JavaStudy
  • 建站时间:2019-1-14
  • 网站程序:帝国CMS7.5
  • 文章统计242篇文章
  • 标签管理标签云
  • 统计数据百度统计
  • 微信公众号:扫描二维码,关注我们