MyBatis从入门到精通笔记(2)XML方式的基本用法

现在大多数时候都是用SpringBoot来整合Mybatis了,很多细节也都被隐藏掉了。而XML是Mybatis最原始的用法,学习这些用法。有助于我们理解Mybatis的底层运行机制

一 权限控制需求

权限管理的需求:
一个用户拥有若干角色,一个角色拥有若干权限,权限就是对某个资源(模块〉的某种操作(增、删、改、查),这样就构成了“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间、角色与权限之间,一般是多对多的关系

表关系如下:
在这里插入图片描述

1.1 环境搭建

在数据库中建立这5张表,并在模块中创建5个对应的实体类
这部分代码省略,可以去参考资料中获取资源

注意点

  1. 一般我们都是表中用下划线命名法,实体用驼峰命名法
  2. 再数据库字段和Java类型的对应关系中,需要注意特殊的类型byte[].这个类型一般对应数据库中的BLOB\LONGVARBINARY以及一些和二级制有关的字段类型。
  3. 由于Java中的基本类型有默认值,无法表示null(字段不存在)。所以实体类字段需要使用引用类型
  4. 创建代码的过程,在学习Mybatis Generator之后可以用代码代替

在这里,我新建了一个simple_all模块,可以将之前的配置文件直接复制到新项目

在这里插入图片描述

二 使用XML 方式

2.1 Mybatis3.0的变化

MyBatis 3.0 相比2.0 版本的一个最大变化,就是支持使用接口来调用方法。

以前使用SqlSession 通过命名空间调用MyBatis 方法时,首先需要用到命名空间和方法id 组成的字符串来调用相应的方法。当参数多于1 个的时候,需要将所有参数放到一个Map对象中。通过Map 传递多个参数,使用起来很不方便,而且还无法避免很多重复的代码。

使用接口调用方式就会方便很多,MyBatis 使用Java 的动态代理可以直接通过接口来调用相应的方法,不需要提供接口的实现类,更不需要在实现类中使用SqlSession以通过命名空间间接调用。另外,当有多个参数的时候,通过参数注解@Param设置参数的名字省去了手动构造Map 参数的过程

注意点
接口可以配合XML 使用,也可以配合注解来使用。XML 可以单独使用,但是注解必须在接口中使用。(我的理解就是xml可以用2.0的用法,直接配合SqlSession使用)

2.2 创建映射文件

先在src/main/resource 下面创建目录(这个目录不是乱写的,后面会说到,主要就是要和包名类似),然后建立5个空的xml映射文件

结构如下
在这里插入图片描述

然后在src/main/resource建立5个接口,注意这个5接口的包名和映射文件的目录结构一致
在这里插入图片描述

打开UserMapper.xnl 文件,在文件中输入以下内容。

<?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="zyc.mybatis.simple.mapper.UserMapper">


</mapper>

注意其中namespace属性。
当Mapper 接口和XML 文件关联的时候,命名空间namespace 的值就需要配置成接口的全限定名称, MyBatis 内部就是通过这个值将接口和XML 关联起来的(重要点!) 甚至在idea中编程,都会有代码提示
在这里插入图片描述

继续创建其他4个mapper映射文件的内容

mybati s -config.xml 配置文件中的mappers 元素中配置所有的mapper ,原来我们需要像下面这么配
在这里插入图片描述
但是这样太麻烦了,我们改成

    <mappers>
        <package name="zyc.mybatis.simple.mapper"/>
    </mappers>

配置包名匹配原理

这种配置方式会先查找zyc.mybatis.simple.mapper 包下所有的接口,循环对接口进行如下操作。

  1. 判断接口对应的命名空间是否己经存在,如果存在就抛出异常,不存在就继续进行接下来的操作。
  2. 加载接口对应的xml映射文件,将接口全限定名转换为路径,例如,将接口
    zyc.mybatis.simple.mapper.UserMapper 转换为zyc/mybatis/simple/mapper/UserMapper. xml,
    以.xml 为后缀搜索XML资源,如果找到就解析XML
  3. 处理接口中的注解方法。
    因为这里的接口和XML 映射文件完全符合上面操作的第2 点,因此直接配置包名就能自动扫描包下的接口和XML 映射文件,省去了很多麻烦。准备好这一切后就可以开始学习具体的用法了

三 select用法

3.1 简单查询

在UserMapper接口中添加方法

    /**
     * 通过 id 查询用户
     *
     * @param id
     * @return
     */
    SysUser selectById(Long id);

在UserMapper.xml中添加查询语句

        <resultMap id="userMap" type="zyc.mybatis.simple.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">
                select * from sys_user where id = #{id}
        </select>

3.2 方法映射规则

接口和XML 是通过将namespace 的值设置为接口的全限定名称来进行关联的,那么接口中方法和XML 又是怎么关联的呢?

可以发现,XML 中的select 标签的id 属性值和定义的接口方法名是一样的。MyBatis就是通过这种方式将接口方法和XML 中定义的SQL 语句关联到一起的,如果接口方法没有和XML 中的id 属性值相对应,启动程序便会报错

规则如下

  • 只使用XML而不使用接口的时候,namespace 的值可以设置为任意不重复的名称
  • 标签的id 属性值在任何时候都不能出现英文句号“.”,并且同一个命名空间下不能出现重复的id
  • 一般情况下我们不使用重载。测试可以两个方法对应一个标签(关键这样写没意义)

3.3 标签和属性

< select >
映射查询语句使用的标签。

  • id :命名空间中的唯一标识符,可用来代表这条语句。
  • resultMap :用于设置返回值的类型和映射关系

<resultMap>

  • id :必填,并且唯一。在select 标签中,resultMap 指定的值即为此处id 所设置的值。
  • type :必填,用于配置查询列所映射到的Java 对象类型。
  • extends :选填,可以配置当前的resultMap 继承自其他的re sultMap ,属性值为继承resultMap 的id
  • autoMapping :选填,可选值为true 或false ,用于配置是否启用非映射字段(没有在resultMap 中配置的字段〉的自动映射功能,该配置可以覆盖全局的autoMappingBehavior 配置。

<resultMap>包含的标签

  • constructor :配置使用构造方法注入结果,包含以下两个子标签
    • idArg: id 参数,标记结果作为id (唯一值),可以帮助提高整体性能。
    • arg :注入到构造方法的一个普通结果。
  • id:一个id 结果,标记结果作为id (唯一值),可以帮助提高整体性能。
  • result :注入到Java 对象属性的普通结果
  • association :一个复杂的类型关联,许多结果将包成这种类型(后面会提
  • collection :复杂类型的集合。(后面会提
  • discriminator :根据结果值来决定使用哪个结果映射。(后面会提
  • case :基于某些值的结果映射。

id 和result 标签包含的属性

  • column :从数据库中得到的列名,或者是列的别名
  • property :映射到列结果的属性。可以映射简单的如“username ”这样的属性,也可以映射一些复杂对象中的属性,例如“address.street.number ”,这会通过“.”方式的属性嵌套赋值。
  • javaType :一个Java类的完全限定名,或一个类型别名(通过typeAlias 配置或者默认的类型)。如果映射到一个JavaBean, M yBatis 通常可以自动判断属性的类型。如果映射到HashMap ,则需要明确地指定javaType 属性。
  • jdbcType :列对应的数据库类型。JDB C 类型仅仅需要对插入、更新、删除操作可能为空的列进行处理。这是JDBC jdbcType 的需要,而不是MyBatis 的需要
  • typeHandler :使用这个属性可以覆盖默认的类型处理器。这个属性值是类的完全限定名或类型别名。

3.4 查询多条记录

接口增加方法

    /**
     * 查询全部用户
     *
     * @return
     */
    List<SysUser> selectAll();

映射文件增加标签

	<select id="selectAll" resultType="zyc.mybatis.simple.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>

selectByid 和selectAll 的区别
selectByid 中使用了resultMap来设置结果映射,而selectAll 中则通过resultType 直接指定了返回结果的类型。可以发现,如果使用resultType 来设置返回结果的类型,需要在SQL 中为所有列名和属性名不一致的列设置别名,通过设置别名使最终的查询结果列和resultType 指定对象的属性名保持一致,进而实现自动映射。

名称映射规则
在数据库中,由于大多数数据库设置不区分大小写,因此下画线方式的命名很常见,如user name 、user email 。在Java 中,一般都使用驼峰式命名,如userName 、userEmail 。因为数据库和Java 中的这两种命名方式很常见,因此MyBatis 还提供了一个 全局属性mapUnderscoreToCamelCase ,通过配置这个属性为true 可以自动将以下画线方式命名的数据库列映射到Java 对象的驼峰式命名属性中。这个属性默认为false

3.5 测试之前写的方法

测试基本类,父类。给其他类继承

/**
 * 基础测试类
 */
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();
	}
	
}

测试类

public class UserMapperTest extends BaseMapperTest {
	
	@Test
	public void testSelectById(){
		//获取 sqlSession
		SqlSession sqlSession = getSqlSession();
		try {
			//获取 UserMapper 接口
			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			//调用 selectById 方法,查询 id = 1 的用户
			SysUser user = userMapper.selectById(1l);
			//user 不为空
			Assert.assertNotNull(user);
			//userName = admin
			Assert.assertEquals("admin", user.getUserName());
		} finally {
			//不要忘记关闭 sqlSession
			sqlSession.close();
		}
	}
	
	@Test
	public void testSelectAll(){
		SqlSession sqlSession = getSqlSession();
		try {
			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			//调用 selectAll 方法查询所有用户
			List<SysUser> userList = userMapper.selectAll();
			//结果不为空
			Assert.assertNotNull(userList);
			//用户数量大于 0 个
			Assert.assertTrue(userList.size() > 0);
		} finally {
			//不要忘记关闭 sqlSession
			sqlSession.close();
		}
	}
	


}

这两个方法在正常情况下都能测试通过

问题验证

修改映射文件所在的目录,验证接口自动寻找规则
在这里插入图片描述
报错了,符合预期

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): zyc.mybatis.simple.mapper.UserMapper.selectAll

3.6 多表关联查询(复杂的用法)

查询一个类的数据

UserMapper接口增加

    /**
     * 根据用户 id 获取角色信息
     *
     * @param userId
     * @return
     */
    List<SysRole> selectRolesByUserId(Long userId);

对应映射

    <select id="selectRolesByUserId" resultType="zyc.mybatis.simple.model.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}
    </select>

查询多个类

假设查询的结果不仅要包含sysrole 中的信息,还要包含当前用户的部分信息(不考虑嵌套的情况),例如增加查询列u .u sername as userName 。这时resultType 该如何设置呢?

方法一
在SysRole 对象中直接添加userName 属性,这样仍然使用SysRole 作为返回值.

增加一个类,继承SysRole ,我的理解就是一个便于关系映射的类


```public class SysRoleExtend extends SysRole {
	private String userName;

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

其他旧省略了

方法二
在SysRole类中,增加一个SysUser的字段(这种应该比较我们正常的开发方式

    /**
     * 用户信息
     */
    private SysUser user;

调整之前的映射配置,增加两列

    <select id="selectRolesByUserId" resultType="zyc.mybatis.simple.model.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>

在这里插入图片描述

四 insert用法

4.1 标签包含的属性

  • id :命名空间中的唯一标识符,可用来代表这条语句。
  • parameterType :即将传入的语句参数的完全限定类名或别名。这个属性是可选的,因为MyBatis 可以推断出传入语句的具体参数,因此不建议配置该属性
  • flushCache :默认值为true ,任何时候只要语句被调用,都会清空一级缓存和二级缓存。
  • timeout :设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
  • statementType :对于STATEMENT 、PREPARED 、C ALLABLE, MyBatis 会分别使用对应的Statement 、PreparedStatement 、CallableStatement ,默认值为PREPARED
  • useGeneratedKeys :默认值为false 。如果设置为true, MyBatis 会使用JDBC的
    getGeneratedKeys 方法来取出由数据库内部生成的主键。
  • keyProperty : MyBatis 通过getGeneratedKeys 获取主键值后将要赋值的属性名。如果希望得到多个数据库自动生成的列,属性值也可以是以逗号分隔的属性名称列表。
  • keyColumn :仅对INSERT 和UPDATE 有用。通过生成的键值设置表中的列名,这个设置仅在某些数据库(如PostgreSQL )中是必须的,当主键列不是表中的第一列时需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。(多个时,和keyProperty一一对应)
  • databaseId :如果配置了databaseidProvider ,MyBatis会加载所有的不带databaseid 的或匹配当前databaseid 的语句。如果同时存在带databaseid 和不带databaseid 的语句,后者会被忽略(主要语句在不同的数据库执行时会用到)

为了防止类型错误,对于一些特殊的数据类型,建议指定具体的jdbcType 值。例如headimg 指定BLOB 类型,createTime指定TIM STAMP 类型。

Mapper接口增加方法

    /**
     * 插入一个用户
     * @param sysUser
     * @return  int表示的是影响的行数
     */
    int insert(SysUser sysUser);

映射文件增加

    <!--  *注意这里可以指定jdbc的类型  -->
    <insert id="insert">
        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>

4.2 使用JDBC 方式返回主键自增的值

只需要修改两个属性即可

接口增加方法

    /**
     * 插入一个用户,带有id。
     * 使用 useGeneratedKeys 方式
     * @param sysUser
     * @return  int表示的是影响的行数
     */
    int insert2(SysUser sysUser);

映射文件

    <insert id="insert2" useGeneratedKeys="true" keyProperty="id">
        //不变...
    </insert>

debug可以发现id有值了
在这里插入图片描述

4.3 使用selectKey 返回主键的值

上面的方式适用于支持主键自增的数据库,但Oracle一般id都是取之序列。所以不适用。
selectKey 标签两种类型都支持。

    /**
     * selectkey方式
     * @param sysUser
     * @return
     */
    int insert3(SysUser sysUser);

xml

    <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>

    <!-- Oracle 的例子,查询多个列的时候需要 keyColumn -->
    <insert id="insertOracle">
        <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
            SELECT SEQ_USER.nextval from dual
        </selectKey>
        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>

也成功了
在这里插入图片描述
属性说明

  • resultType 用于设置返回值类型
  • order 属性的设置和使用的数据库有关。
    在MySQL 数据库中,order 属性设置的值是AFTER,因为当前记录的主键值在insert 语句
    执行成功后才能获取到。而在Oracle 数据库中,order 的值要设置为BEFORE ,这是因为Oracl e
    中需要先从序列获取值,然后将值作为主键插入到数据库中

五 update用法

接口

    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>

六 delete用法

下面两种方式都行

    /**
     * 通过主键删除
     *
     * @param id
     * @return
     */
    int deleteById(Long id);

    /**
     * 通过主键删除
     *
     * @param sysUser
     * @return
     */
    int deleteById(SysUser sysUser);

xml(两个接口对应一个标签)


    <delete id="deleteById">
        delete from sys_user where id = #{id}
    </delete>

七 多个接口参数的用法

不涉及集合和数组(之后会讲)

如果我们的需求是多个参数的话,我们可以使用Map或@Param.map就是以key来对应xml中sql使用的参数值名字,value来存放参数值(不建议,太麻烦,还要自己拼一个Map),@param是比较推荐的方式。

    /**
     * 根据用户 id 和 角色的 enabled 状态获取用户的角色
     *
     * @param userId
     * @param enabled
     * @return
     */
    List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("userId")Long userId, @Param("enabled")Integer enabled);
    <select id="selectRolesByUserIdAndRoleEnabled" resultType="zyc.mybatis.simple.model.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>

当参数类型是一些JavaBean 的时候,用法略有不同 .我们不能直接使用属性名,而是要用..如
#{user.name}

八 动态代理实现原理

前面我们使用MyBatis时,只需要声明接口即可,原理是生成了动态代理类。但这种动态代理方式不想我们在spring中经常碰到的jdk动态代理或者cglib动态代理方式(技术还是jdk动态代理

大体逻辑是通过接口名和方法名,确认方法在映射文件中的id。然后使用最原始的SqlSession来查询。
下面是一个简单的实现(实际肯定更加复杂
InvocationHandler实现

public class MyMapperProxy<T> implements InvocationHandler {
    private Class<T> mapperInterface;
    private SqlSession sqlSession;

    public MyMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
        this.mapperInterface = mapperInterface;
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//针对不同的 sql 类型,需要调用 sqlSession 不同的方法
    	//参数也有很多情况,这里只考虑一个参数的情况
        List<?> list = sqlSession.selectList(mapperInterface.getCanonicalName() + "." + method.getName());
        //返回值也有很多情况
        return list;
    }
}

测试方法

	@Test
	public void testMyMapperProxy(){
		SqlSession sqlSession = getSqlSession();
		MyMapperProxy<UserMapper> userMapperMyMapperProxy = new MyMapperProxy<>(UserMapper.class,sqlSession);
		UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
				new Class[]{UserMapper.class},userMapperMyMapperProxy);

		List<SysUser> list = userMapper.selectAll();
		System.out.println(list.size());

	}

参考

  1. 《MyBatis 从入门到精通》
  2. 源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值