MyBatics总结介绍

Mybatis

1、Mybatis介绍

1.1背景

MyBatis 本是 Apache 的一个开源项目——iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis。

1.2 简介

  • MyBatis 是一个基于 Java 的持久层框架。MyBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO),它消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。

  • MyBatis 是一个基于 Java 的持久层框架。MyBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO),它消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。

  • MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

  • 目前,Java的持久型框架常用的是Hirbenate和Mybatis。

1.3 Hibernate 和 MyBatis 的区别

  • sql 优化方面:

    • Hibernate 不需要编写大量的 SQL,就可以完全映射,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)对 POJO 进行操作。但会多消耗性能。
    • MyBatis 手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对大些。\
  • 开发方面
    • MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO、SQL 和映射关系。

    • Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。

Hibernate 优势
  • Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
  • Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
  • Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
  • Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
Mybatis优势
  • MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。

  • MyBatis 容易掌握,而 Hibernate 门槛较高。

总结
  • 总的来说,MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。

  • 所以对于性能要求不太苛刻的系统,比如管理系统、ERP 等推荐使用 Hibernate,而对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。

2、Mybatis的安装

2.1 创建Maven项目

2.2 Mybatis的依赖导入

	<dependencies>
			<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>
   </dependencies>

3、MyBatis 的工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQ1sr3LM-1605844054612)(图片/MyBatis框架的执行流程图.png)])

  1. 创建MyBatis的配置文件并读取 : mybatis-config.xml ,此xml文件配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
  2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

  3. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

  4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

  5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

  6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

3.1 Mybatis的核心构成组件

  • SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

    public SqlSession getSession() throws IOException {
        return  new SqlSessionFactoryBuilder().
            build(Resources.getResourceAsStream("")).openSession();
  • SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。
  • SqlSession(会话):它既可以直接发送 SQL 语句执行并返回结果;也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,转而使用 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。
  • SQL Mapper(映射器,使用注解开发):这是MyBatis 新设计存在的组件,它由一个 Java 接口(DAO)和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7a4zJ6gx-1605844054616)(图片/MyBatis核心组件关系.png)]

3.2 MyBatis SqlSessionFactory及其常见创建方式

在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过Java代码的形式去生成 SqlSessionFactory。

​ 首先需要使用MyBatis 提供了构造器 SqlSessionFactoryBuilder, MyBatis 首先是使用配置或者代码去生产 SqlSessionFactory,推荐采用 XML 的形式,因为代码的方式在需要修改的时候会比较麻烦。当配置了 XML 或者提供代码后,MyBatis 会读取配置文件,通过 Configuration 类对象构建整个 MyBatis 的上下文。

​ 注意,SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory,一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory,它们之间的关系如图 1 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5mvVvsQ-1605844054618)(图片/SqlSessionFactory的生成.png)]

​ 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。我们往往会采用单例模式处理它,下面是使用配置文件和 Java 代码两种形式去生成 SqlSessionFactory 的方法。

3.2.1 使用 XML 构建 SqlSessionFactory

1.首先需要构建一个Mybatis-config.xml文件

下面是基本配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3309/mytest?characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone:GMT%2B8;"/>
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </properties>
    <settings>
        <setting name="logImpl" value="LOG4J" />
    </settings>
    <typeAliases>
        <!--<package name="com.mybatis.entity"/>-->
        <typeAlias alias="user" type="com.mybatis.entity.User"/>
        <typeAlias alias="village" type="com.mybatis.entity.Village"/>
    </typeAliases>
    <!-- 配置mybatis运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC的事务管理 -->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- MySQL数据库驱动 -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 将mapper文件加入到配置文件中 -->
    <mappers>
        <package name="com.mybatis.mapper"/>
    </mappers>
  • MyBatis 的基础配置文件:

    • 元素定义了一个别名 user,它代表着 com.mybatis.po.User 这个类。这样定义后,在 MyBatis 上下文中就可以使用别名去代替全限定名了。
    • 元素的定义,这里描述的是数据库。它里面的 元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。
    • 元素配置数据库,其中属性 type=“POOLED” 代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。
    • 元素代表引入的那些映射器,在谈到映射器时会详细讨论它。

    既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      <mapper url="file:///var/mappers/BlogMapper.xml"/>
      <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    
  1. 生成SqlSessionFactorySE
SqlSessionFactory factory=null;
        try {
            factory=new SqlSessionFactoryBuilder().
                build(Resources.getResourceAsStream("Mybatis-config.xml"));
        }catch(IOException e){
            e.printStackTrace();
        }
        return factory;

​ 首先读取 mybatis-config.xml,然后通过 SqlSessionFactoryBuilder 的 Builder 方法去创建 SqlSessionFactory,整个过程比较简单,而里面的步骤还是比较烦琐的,只是 MyBatis 采用了 Builder 模式为开发者隐藏了这些细节。这样一个 SqlSessionFactory 就被创建出来了。

	采用 XML 创建的形式,信息在配置文件中,有利于我们日后的维护和修改,避免了重新编译代码,推荐这种方式
3.2.2 使用代码生成创建 SqlSessionFactory

这种方式不推荐,配置文件和代码叠加在一起。仅需要了解

除非有特殊的需要,比如在配置文件中,需要配置加密过的数据库用户名和密码,需要我们在生成 SqlSessionFactory 前解密为明文的时候,才会考虑使用这样的方式。

		// 数据库连接池
        PooledDataSource dataSource=new PooledDataSource();
        dataSource.setDriver("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3309/parking?characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone:GMT%2B8;");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        //采用 MyBatis 的 JDBC 事务方式
        TransactionFactory transactionFactory=new JdbcTransactionFactory();
        Environment environment=new 					  								Environment("development",transactionFactory,dataSource);
        // 创建 Configuration 对象
        Configuration configuration=new Configuration(environment);
        // 注册一个 MyBatis 上下文别名
        configuration.getTypeAliasRegistry().registerAlias("user", User.class);
        // 加入一个映射器
        configuration.addMapper(UserMapper.class);
        //使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
        SqlSessionFactory factory =
                new SqlSessionFactoryBuilder().build(configuration);

        return factory;
3.2.3 生成SqlSession

​ 在 MyBatis 中,SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSessionSqlSessionManager

​ DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:

  • 获取Mapper接口
  • 发送SQL给数据库
  • 控制数据库的事务

SqlSessionFactory 创建的 SqlSession

SqlSession sqlSession=null;
        try{
            sqlSession=getSqlSessionFactory().openSession();
            sqlSession.commit();//事务的提交
        }catch(Exception e){
            sqlSession.rollback();//事务的回滚
        }finally {
            if(sqlSession!=null){
                sqlSession.close();//Session的关闭
            }
        }
        return sqlSession;

注:这里使用 commit 方法提交事务,或者使用 rollback 方法回滚事务。因为它代表着一个数据库的连接资源,使用后要及时关闭它,如果不关闭,那么数据库的连接资源就会很快被耗费光,整个系统就会陷入瘫痪状态,所以用 finally 语句保证其顺利关闭。

3.3 MyBatis实现映射器的2种方式

映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或注解)组成。它可以配置以下内容:

  • 映射规则。
  • SQL 语句,可以配置 SQL 参数类型、返回类型、缓存刷新等信息。
  • 配置缓存。

提供动态 SQL。映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据插入到数据库中,并定义一些关于缓存等的重要内容。

注意,开发只是一个接口,而不是一个实现类。初学者可能会产生一个很大的疑问,那就是接口不是不能运行吗?

是的,接口不能直接运行。MyBatis 运用了动态代理技术使得接口能运行起来,入门阶段只要懂得 MyBatis 会为这个接口生成一个代理对象,代理对象会去处理相关的逻辑即可。

3.3.1 用 XML 实现映射器
  • Mapper接口
package com.mybatis.mapper;

import com.mybatis.entity.User;

public interface UserMapper {
   User queryAll(Integer id);
}
  • 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.mybatis.mapper.UserMapper">
    <select id="queryAll" parameterType="integer" resultType="com.mybatis.entity.User">
        SELECT id,name,age,gender FROM person WHERE id =#{id}
    </select>
</mapper>
  • 元素中的属性 namespace 所对应的是一个接口的全限定名,于是 MyBatis 上下文就可以通过它找到对应的接口。

元素表明这是一条查询语句,而属性 id 标识了这条 SQL的名字,属性 parameterType=“integer” 说明传递给 SQL 的是一个 integer型的参数,而 resultType=“com.mybatis.entity.User” 表示返回的是一个 User 类型的返回值。而 user 是之前配置文件 mybatis-config.xml 配置的别名 com.mybatis.rntity.User

  • 这条 SQL 中的 #{id} 表示传递进去的参数

  • 注意,我们并没有配置 SQL 执行后和 role 的对应关系,它是如何映射的呢? 其实这里采用的是一种被称为自动映射的功能,MyBatis 在默认情况下提供自动映射,只要 SQL 返回的列名能和 POJO 对应起来即可。这里 SQL 返回的列名 id 和 name是可以和之前定义的 POJO 的属性对应起来,所以此时 MyBatis 就可以把 SQL 查询的结果通过自动映射的功能映射成为一个 POJO。

3.3.2 使用注解开发
 @Select("SELECT id,name,age,gender FROM person WHERE id =#{id}")
   User queryAllTwo(Integer id);

​ 这完全等同于 XML 方式创建映射器。也许觉得使用注解的方式比 XML 方式要简单。而且它和 XML 方式同时定义时,XML 方式将覆盖掉注解方式,。但是,一但SQL变得复杂,大量的 SQL 放入 Java 代码中,显然代码的可读性也会下降。所以 MyBatis 官方推荐使用的是 XML 方式。

​ 如果同时还要考虑使用动态 SQL,比如当参数 userName 为空,则不使用 u.user_name like concat(’%’, u s e r N a m e , ′ {userName},'%')作为查询条件;当 roleName 为空,则不使用 r.role_name like concat('%', userName,{roleName},’%’)作为查询条件,但是还需要加入其他的逻辑,这样就使得这个注解更加复杂了,不利于日后的维护和修改。

​ 此外,XML 可以相互引入,而注解是不可以的,所以在一些比较复杂的场景下,使用 XML 方式会更加灵活和方便。所以大部分的企业都是以 XML 为主,本教程也会保持一致,以 XML 方式来创建映射器。当然在一些简单的表和应用中使用注解方式也会比较简单。

3.4 MyBatis执行SQL的两种方式

  • SqlSession发送SQL语句
		SqlSession sqlSession=Session.getSession();
        User user=(User)sqlSession.selectOne("UserMapper.queryAll",1);
        System.out.println(user);

​ selectOne 方法表示使用查询并且只返回一个对象,而参数则是一个 String 对象和一个 Object 对象。这里的Object 对象是一个 integer参数,id参数是它的主键。

	String 对象是由一个命名空间加上 SQL id 组合而成的,它完全定位了一条 SQL,这样 MyBatis 就会找到对应的 SQL。如果在 MyBatis 中只有一个 id 为 queryAll的 SQL,那么也可以简写为:
User user=(User)sqlSession.selectOne("queryAll",1);
  • 用 Mapper 接口发送 SQL
		SqlSession sqlSession=Session.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        User user=mapper.queryAll(1);

通过 SqlSession 的 getMapper 方法来获取一个 Mapper 接口,就可以调用它的方法了。因为 XML 文件或者接口注解定义的 SQL 都可以通过“类的全限定名+方法名”查找,所以 MyBatis 会启用对应的 SQL 进行运行,并返回结果

  • 对比总结
  1. 上面分别展示了 MyBatis 存在的两种发送 SQL 的方式,一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。一般采用 SqlSession 获取 Mapper 的方式,理由如下:
  2. 使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。使用 Mapper 接口,类似 UserMapper.queryAll(1)则是完全面向对象的语言,更能体现业务的逻辑
  3. 使用 Mapper.queryAll(1)方式,IDE 会提示错误和校验,而使用 sqlSession.selectOne(“queryAll”,1)语法,只有在运行中才能知道是否会产生错误。
  4. 目前使用 Mapper 接口编程已成为主流,尤其在 Spring 中运用 MyBatis 时,Mapper 接口的使用就更为简单,所以推荐使用 Mapper 接口的方式讨论 MyBatis。

3.5 Mybatis核心组件的生命周期

​ 生命周期是组件的重要问题,尤其是在多线程的环境中,比如互联网应用、Socket 请求等,而 MyBatis 也常用于多线程的环境中,错误使用会造成严重的多线程并发问题,为了正确编写 MyBatis 的应用程序,我们需要掌握 MyBatis 组件的生命周期。

	所谓生命周期就是每一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被 [Java](http://c.biancheng.net/java/) 虚拟机(JVM)销毁,以避免继续占用资源,所以我们会根据每一个组件的作用去确定其生命周期。
3.5.1 SqlSessionFactoryBuilder

​ SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)

3.5.2 SqlSessionFactory

​ SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。

	由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个  SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。

​ 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是整个连接作用域

3.5.3 SqlSession

如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。

所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。

所以 SqlSession 的最佳的作用域是一次请求或一个方法作用域

3.5.4 Mapper

Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GAXaAmKZ-1605844054624)(图片/MyBatis组件的生命周期.png)]

4、Mybatis的第一个案例

4.1 创建数据库和实体类

#在mytest数据库中创建person数据表
use mytest;#切换数据库
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `id` tinyint(4) primary key auto_increment,
  `name` varchar(20),
  `age` tinyint(5),
    'gender' char(2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

#插入数据
INSERT INTO person (NAME,age,gender) VALUES ('张三','25','男');
INSERT INTO person (NAME,age,gender) VALUES ('麻六','26','男');
INSERT INTO person (NAME,age,gender) VALUES ('李四','24','男');
public class User {
    private int id;
    private String name;
    private int age;
    private String gender;
//  setter和getter方法
//  toString方法

4.2 创建Maven项目

按照上面第二节2、Mybatis的安装创建一个Maven项目后,导入相关依赖

4.3 创建Mybatis-config.xml

在项目的resources内创建一个Mybatis-config.xml,参考3.2.1 使用 XML 构建 SqlSessionFactory

4.4 创建映射文件

在 src 目录下创建一个名为 com.mybatis.mapper 的包,在该包中创建映射文件 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.mybatis.mapper.UserMapper">
    <select id="queryAll" parameterType="integer" resultType="com.mybatis.entity.User">
        SELECT id,name,age,gender FROM person WHERE id =#{id}
    </select>

    <update id="update" parameterType="com.mybatis.entity.User">
        update person
        set name = #{name},
        age = #{age},
        gender = #{gender}
        where id = #{id}
    </update>

    <delete id="delete" parameterType="integer">
        delete from person where id = #{id}
    </delete>

    <insert id="insert" parameterType="com.mybatis.entity.User">
        insert into person (name,age,gender)
        values
        (#{name},#{age},#{gender})
    </insert>
</mapper>

在上述映射文件中, 元素是配置文件的根元素,它包含了一个 namespace 属性,该属性值通常设置为“包名+SQL映射文件名”,指定了唯一的命名空间。

子元素 、、 以及 中的信息是用于执行查询、添加、修改以及删除操作的配置。在定义的 SQL 语句中,“#{}”表示一个占位符,相当于“?”,而“#{id}”表示该占位符待接收参数的名称为 id。

4.5 创建测试类

在 src 目录下创建一个名为Session 的测试类,首先使用输入流读取配置文件,然后根据配置信息构建 SqlSessionFactory 对象。

接下来通过 SqlSessionFactory 对象创建 SqlSession 对象,并使用 SqlSession 对象的方法执行数据库操作。 MyBatisTest 测试类的代码如下:

//获取SqlSessionFactory工厂
	public static SqlSessionFactory getSqlSessionFactory() {
        SqlSessionFactory factory=null;
        try {
            factory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("Mybatis-config.xml"));
        }catch(IOException e){
            e.printStackTrace();
        }
        return factory;
    }
 //获取SqlSession   
    public static SqlSession getSession(){
        SqlSession sqlSession=null;
        try{
            sqlSession=getSqlSessionFactory().openSession();

        }catch(Exception e){
            sqlSession.rollback();//事务的回滚
        }finally {
            
        }
        return sqlSession;
    }
//执行测试
    @Test
    public void test(){
        SqlSession sqlSession=Session.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        User user=mapper.queryAll(1);
        System.out.println(user);
        sqlSession.commit();//事务的提交
        sqlSession.close();
        
        //显示:User{id=1, name='张三', age=25, gender='男'}
    }

5、Mybatis的配置解析

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration><!-- 配置 -->
        <properties /><!-- 属性 -->
        <settings /><!-- 设置 -->
        <typeAliases /><!-- 类型命名 -->
        <typeHandlers /><!-- 类型处理器 -->
        <objectFactory /><!-- 对象工厂 -->
        <plugins /><!-- 插件 -->
        <environments><!-- 配置环境 -->
            <environment><!-- 环境变量 -->
                <transactionManager /><!-- 事务管理器 -->
                <dataSource /><!-- 数据源 -->
            </environment>
        </environments>
        <databaseIdProvider /><!-- 数据库厂商标识 -->
        <mappers /><!-- 映射器 -->
    </configuration>

但是需要注意的是,MyBatis 配置项的顺序不能颠倒。如果颠倒了它们的顺序,那么在 MyBatis 启动阶段就会发生异常,导致程序无法运行。

了解 MyBatis 配置项的作用,其中 properties、settings、typeAliases、typeHandler、plugin、environments、mappers 是常用的内容。

不讨论 plugin(插件)元素的使用,在进一步学习 MyBatis 的许多底层内容和设计后我们才会学习它。MyBatis 中 objectFactory 和 databaseIdProvider 不常用。

5.1 properties 元素

可以给系统配置一些运行参数,可以放在 XML 文件或者 properties 文件中,而不是放在 Java 编码中,这样的好处在于方便参数修改,而不会引起代码的重新编译。一般而言,MyBatis 提供了 3 种方式让我们使用 properties,它们是:

  • property 子元素。
  • properties 文件。
  • 程序代码传递。
5.1.1使用property子元素配置
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <properties>
            <property name="driver" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
            <property name="username" value="root" />
            <property name="password" value="password" />
        </properties>
            <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url"    value="${url}" />
                <property name="username" value="${root}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
        
    </configuration>

这里使用了元素 下的子元素 定义,用字符串 database.username 定义数据库用户名,然后就可以在数据库定义中引入这个已经定义好的属性参数,如 ${database.username},这样定义一次就可以到处引用了。但是如果属性参数有成百上千个,显然使用这样的方式不是一个很好的选择,这个时候可以使用 properties 文件。

5.1.2 使用properties文件

使用 properties 文件是比较普遍的方法,一方面这个文件十分简单,其逻辑就是键值对应,我们可以配置多个键值放在一个 properties 文件中,也可以把多个键值放到多个 properties 文件中,这些都是允许的,它方便日后维护和修改。

我们创建一个文件 jdbc.properties

database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mybatis
database.username=root
database.password=1128

在 MyBatis 中通过 的属性 resource 来引入 properties 文件。

<properties resource="jdbc.properties"/>

也可以按 ${database.username} 的方法引入 properties 文件的属性参数到 MyBatis 配置文件中。这个时候通过维护 properties 文件就可以维护我们的配置内容了。

5.1.3 使用程序传递方式传递参数

在真实的生产环境中,数据库的用户密码是对开发人员和其他人员保密的。运维人员为了保密,一般都需要把用户和密码经过加密成为密文后,配置到 properties 文件中。

对于开发人员及其他人员而言,就不知道其真实的用户密码了,数据库也不可能使用已经加密的字符串去连接,此时往往需要通过解密才能得到真实的用户和密码了。

现在假设系统已经为提供了这样的一个 CodeUtils.decode(str)进行解密,那么我们在创建 SqlSessionFactory 前,就需要把用户名和密码解密,然后把解密后的字符串重置到 properties 属性中,如下所示。

    String resource = "mybatis-config.xml";

    InputStream inputStream;
    Inputstream in = Resources.getResourceAsStream("jdbc.properties");

    Properties props = new Properties();
    props.load(in);
    String username = props.getProperty("database.username");
    String password = props.getProperty("database.password");

    //解密用户和密码,并在属性中重置
    props.put("database.username", CodeUtils.decode(username));
    props.put ("database.password", CodeUtils.decode(password)); 
    inputstream = Resources.getResourceAsStream(resource);

    //使用程序传递的方式覆盖原有的properties属性参数
    SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream, props);

首先使用 Resources 对象读取了一个 jdbc.properties 配置文件,然后获取了它原来配置的用户和密码,进行解密并重置,最后使用 SqlSessionFactoryBuilder 的 build 方法,传递多个 properties 参数来完成。

这将覆盖之前配置的密文,这样就能连接数据库了,同时也满足了运维人员对数据库用户和密码安全的要求。

5.2 Settings元素

在 MyBatis 中 settings 是最复杂的配置,它能深刻影响 MyBatis 底层的运行,但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。

配置项作用配置选项默认值
cacheEnabled该配置影响所有映射器中配置缓存的全局开关true|falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态true|falsefalse
aggressiveLazyLoading当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载true|felse版本3.4.1 (不包含) 之前 true,之后 false
multipleResultSetsEnabled是否允许单一语句返回多结果集(需要兼容驱动)true|falsetrue
useColumnLabel使用列标签代替列名。不同的驱动会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果true|falsetrue
useGeneratedKeys允许JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)true|falsefalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射。 PARTIAL 表示只会自动映射,没有定义嵌套结果集和映射结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)NONE、PARTIAL、FULLPARTIAL
autoMappingUnkno wnColumnBehavior指定自动映射当中未知列(或未知属性类型)时的行为。 默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常NONE、WARNING、FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 是普通的执行器;REUSE 会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新SIMPLE、REUSE、BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定驱动等待数据库响应的秒数任何正整数Not Set (null)
defaultFetchSize设置数据库驱动程序默认返回的条数限制,此参数可以重新设置任何正整数Not Set (null)
safeRowBoundsEnabled允许在嵌套语句中使用分页(RowBounds)。如果允许,设置 falsetrue|falsefalse
safeResultHandlerEnabled允许在嵌套语句中使用分页(ResultHandler)。如果允许,设置falsetrue|falsetrue
mapUnderscoreToCamelCase是否开启自动驼峰命名规则映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射true|falsefalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据SESSION|STATEMENTSESSION
jdbcTypeForNull当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHERNULL、VARCHAR、OTHEROTHER
lazyLoadTriggerMethods指定哪个对象的方法触发一次延迟加载equals、clone、hashCode、toString
defaultScriptingLanguage指定动态 SQL 生成的默认语言org.apache.ibatis .script.ing.xmltags .XMLDynamicLanguageDriver
callSettersOnNulls指定当结果集中值为 null 时,是否调用映射对象的 setter(map 对象时为 put)方法,这对于 Map.kcySet() 依赖或 null 值初始化时是有用的。注意,基本类型(int、boolean 等)不能设置成 nulltrue|falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀任何字符串Not set
loglmpl指定 MyBatis 所用日志的具体实现,未指定时将自动査找SLF4J|LOG4J|LOG4J2|JDK_LOGGING |COMMONS_LOGGING |ST DOUT_LOGGING|NO_LOGGINGNot set
proxyFactory指定 MyBatis 创建具有延迟加栽能力的对象所用到的代理工具CGLIB|JAVASSISTJAVASSIST (MyBatis 版本为 3.3 及以上的)
vfsImpl指定 VFS 的实现类提供 VFS 类的全限定名,如果存在多个,可以使用逗号分隔Not set
useActualParamName允许用方法参数中声明的实际名称引用参数。要使用此功能,项目必须被编译为 Java 8 参数的选择。(从版本 3.4.1 开始可以使用)true|falsetrue

settings 的配置项很多,但是真正用到的不会太多,我们把常用的配置项研究清楚就可以了,比如关于缓存的 cacheEnabled,关于级联的 lazyLoadingEnabled 和 aggressiveLazy Loading,关于自动映射的 autoMappingBehavior 和 mapUnderscoreToCamelCase,关于执行器类型的 defaultExecutorType 等。

这里给出一个全量的配置样例,如下所示。

    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="defaultFetchSize" value="100"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>

5.3 environments和子元素transactionManager、dataSource解析

在 MyBatis 中,运行环境主要的作用是配置数据库信息,它可以配置多个数据库,一般而言只需要配置其中的一个就可以了。

它下面又分为两个可配置的元素:事务管理器(transactionManager)、数据源(dataSource)

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${database.driver}" />
                <property name="url"
                    value="${database.url}" />
                <property name="username" value="${database.username}" />
                <property name="password" value="${database.password}" />
            </dataSource>
        </environment>
    </environments>
transactionManager事务管理器

可配置成以下两种方式

<transactionManager type="JDBC"/>
<transactionManager type="MANAGED"/>

JDBC 使用 JdbcTransactionFactory 生成的 JdbcTransaction 对象实现。它是以 JDBC 的方式对数据库的提交和回滚进行操作。

MANAGED 使用 ManagedTransactionFactory 生成的 ManagedTransaction 对象实现。它的提交和回滚方法不用任何操作,而是把事务交给容器处理。在默认情况下,它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

源码:

在 MyBatis 中,transactionManager 提供了两个实现类,它需要实现接口 Transaction(org.apache.ibatis.transaction.Transaction),它的定义代码如下所示。

public interface Transaction {
    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;

    Integer getTimeout() throws SQLException;
}

从方法可知,它主要的工作就是提交(commit)、回滚(rollback)和关闭(close)数据库的事务。MyBatis 为 Transaction 提供了两个实现类:JdbcTransaction 和 ManagedTransaction

Transaction的实现类

如果不想使用mybatis采用的规则时,可以自己定义。

实现一个自定义事务工厂:创建类,实现TransactionFactory接口

public class MyTransactionFactory implements TransactionFactory {
    @Override
    public void setProperties(Properties props) {
    }
    @Override
    public Transaction newTransaction(Connection conn) {
        return new MyTransaction(conn);
    }
    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionlsolationLevel level, boolean autoCommit) {
        return new MyTransaction(dataSource, level, autoCommit);
    }
}

这里就实现了 TransactionFactory 所定义的工厂方法,这个时候还需要事务实现类 MyTransaction,它用于实现 Transaction 接口

public class MyTransaction extends JdbcTransaction implements Transaction {

    public MyTransaction(DataSource ds, TransactionIsolationLevel desiredLevel,
            boolean desiredAutoCommit) {
        super(ds, desiredLevel, desiredAutoCommit);
    }

    public MyTransaction(Connection connection) {
        super(connection);
    }

    public Connection getConnection() throws SQLException {
        return super.getConnection();
    }

    public void commit() throws SQLException {
        super.commit();
    }

    public void rollback() throws SQLException {
        super.rollback();
    }

    public void close() throws SQLException {
        super.close();
    }

    public Integer getTimeout() throws SQLException {
        return super.getTimeout();
    }
}
dataSource 数据源

数据源配置有三种

<dataSource type="UNPOOLED">
<dataSource type="POOLED">
<dataSource type="JNDI">

在 MyBatis 中,数据库通过 PooledDataSource Factory、UnpooledDataSourceFactory 和 JndiDataSourceFactory 三个工厂类来提供,前两者对应产生 PooledDataSource、UnpooledDataSource 类对象,而 JndiDataSourceFactory 则会根据 JNDI 的信息拿到外部容器实现的数据库连接对象。

  1. UNPOOLED 采用非数据库池的管理方式,每次请求都会打开一个新的数据库连接,所以创建会比较慢。在一些对性能没有很高要求的场合可以使用它。

    对有些数据库而言,使用连接池并不重要,那么它也是一个比较理想的选择。UNPOOLED 类型的数据源可以配置以下几种属性:

    • driver 数据库驱动名,比如 MySQL 的 com.mysql.jdbc.Driver。
    • url 连接数据库的 URL。
    • username 用户名。
    • password 密码。
    • defaultTransactionIsolationLevel 默认的连接事务隔离级别

    传递属性给数据库驱动也是一个可选项,注意属性的前缀为“driver.”,例如 driver.encoding=UTF8。它会通过 DriverManager.getConnection(url,driverProperties)方法传递值为 UTF8 的 encoding 属性给数据库驱动。

  2. 数据源 POOLED 利用“池”的概念将 JDBC 的 Connection 对象组织起来,它开始会有一些空置,并且已经连接好的数据库连接,所以请求时,无须再建立和验证,省去了创建新的连接实例时所必需的初始化和认证时间。它还控制最大连接数,避免过多的连接导致系统瓶颈。

    除了 UNPOOLED 下的属性外,会有更多属性用来配置 POOLED 的数据源

名称说明
poolMaximumActiveConnections是在任意时间都存在的活动(也就是正在使用)连接数量,默认值为 10
poolMaximumIdleConnections是任意时间可能存在的空闲连接数
poolMaximumCheckoutTime在被强制返回之前,池中连接被检出(checked out)的时间,默认值为 20 000 毫秒(即 20 秒)
poolTimeToWait是一个底层设置,如果获取连接花费相当长的时间,它会给连接池打印状态日志,并重新尝试获取一个连接(避免在误配置的情况下一直失败),默认值为 20 000 毫秒(即 20 秒)。
poolPingQuery为发送到数据库的侦测查询,用来检验连接是否处在正常工作秩序中,并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。
poolPingEnabled为是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL),默认值为 false。
poolPingConnectionsNotUsedFor为配置 poolPingQuery 的使用频度。这可以被设置成匹配具体的数据库连接超时时间,来避免不必要的侦测,默认值为 0(即所有连接每一时刻都被侦测——仅当 poolPingEnabled 为 true 时适用)。
  1. 数据源 JNDI 的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性:

    1)initial_context

    用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。initial_context 是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。

    2)data_source

    是引用数据源实例位置上下文的路径。当提供 initial_context 配置时,data_source 会在其返回的上下文中进行查找;当没有提供 initial_context 时,data_source 直接在 InitialContext 中查找。

    与其他数据源配置类似,它可以通过添加前缀“env.”直接把属性传递给初始上下文(InitialContext)。比如 env.encoding=UTF8,就会在初始上下文实例化时往它的构造方法传递值为 UTF8 的 encoding 属性。

5.4 mappers 映射器

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括

file:/// 形式的 URL),或类名和包名等。例如:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

6、Mybatis的标签

6.1select标签

在 SQL 映射文件中 元素用于映射 SQL 的 select 语句,其示例代码如下:

<!--根据id查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer" resultType="com.mybatis.po.User">
    select * from user where id = #{id}
</select>

在上述示例代码中,id 的值是唯一标识符,它接收一个 Integer 类型的参数(parameterType),返回一个 MyUser 类型的对象(resultType),结果集自动映射到 MyUser 属性。

除了有上述示例代码中的几个属性以外,还有一些常用的属性

属性名称描 述
id它和 Mapper 的命名空间组合起来使用,是唯一标识符,供 MyBatis 调用
parameterType表示传入 SQL 语句的参数类型的全限定名或别名。它是一个可选属性,MyBatis 能推断出具体传入语句的参数
resultTypeSQL 语句执行后返回的类型(全限定名或者别名)。如果是集合类型,返回的是集合元素的类型,返回时可以使用 resultType 或 resultMap 之一
resultMap它是映射集的引用,与 元素一起使用,返回时可以使用 resultType 或 resultMap 之一
flushCache用于设置在调用 SQL 语句后是否要求 MyBatis 清空之前查询的本地缓存和二级缓存,默认值为 false,如果设置为 true,则任何时候只要 SQL 语句被调用都将清空本地缓存和二级缓存
useCache启动二级缓存的开关,默认值为 true,表示将査询结果存入二级缓存中
timeout用于设置超时参数,单位是秒(s),超时将抛出异常
fetchSize获取记录的总条数设定
statementType告诉 MyBatis 使用哪个 JDBC 的 Statement 工作,取值为 STATEMENT(Statement)、 PREPARED(PreparedStatement)、CALLABLE(CallableStatement)
resultSetType这是针对 JDBC 的 ResultSet 接口而言,其值可设置为 FORWARD_ONLY(只允许向前访问)、SCROLL_SENSITIVE(双向滚动,但不及时更新)、SCROLLJNSENSITIVE(双向滚动,及时更新)
6.1.1 使用 Map 接口传递多个参数

在实际开发中,查询 SQL 语句经常需要多个参数,例如多条件查询。当传递多个参数时, 元素的 parameterType 属性值的类型是什么呢?在 MyBatis 中允许 Map 接口通过键值对传递多个参数。

public List<User> selectAllUser(Map<String,Object> param);

设置查询的方法

<select id="selectAllUser" resultType="com.mybatis.entityUser">   
    select * from user where name like concat(#{name},'%')<!--查询姓名和性别的数据-->
    and gender = #{gender}
</select>

下面是测试数据

		SqlSession sqlSession=Session.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
    
        HashMap<String, Object> map=new HashMap<String, Object>();
        map.put("name","李");//name是Map集合的key,"李"是Map的value
        map.put("gender","女");
        List<User> user=mapper.selectAllUser(map);
        System.out.println(user);
		//[User{id=8, name='李晓', age=23, gender='女', village=4}, 
		//User{id=9, name='李梅梅', age=25, gender='女', village=2}]

        sqlSession.commit();//事务的提交
        sqlSession.close();

Map 是一个键值对应的集合,使用者要通过阅读它的键才能了解其作用。另外,使用 Map 不能限定其传递的数据类型,所以业务性不强,可读性较差。如果 SQL 语句很复杂,参数很多,使用 Map 将很不方便。幸运的是,MyBatis 还提供了使用 JavaBean 传递多个参数的形式

6.1.2 使用Java Bean来传递多个参数

在entity包中创建一个参数类- SeletUserParam

    package com.mybatis.entity;
    public class SeletUserParam {
        private String name;
        private String sex;
        // 此处省略setter和getter方法
    }

接着将 Mapper映射包中的UserMapper的 selectAllUser 方法修改为如下:

List<User> selectAllUser(SelectUserParam selectUserParam);

测试数据

SqlSession sqlSession=Session.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);

        SelectUserParam s=new SelectUserParam();
        s.setName("李");
        s.setGender("男");
        List<User> user=mapper.selectAllUser(s);
        System.out.println(user);
		//[User{id=6, name='李四', age=24, gender='男', village=2}]

        sqlSession.commit();//事务的提交
        sqlSession.close();

在实际应用中是选择 Map 还是选择 Java Bean 传递多个参数应根据实际情况而定,如果参数较少,建议选择 Map;如果参数较多,建议选择 Java Bean

6.2 insert标签

元素用于映射插入语句,MyBatis 执行完一条插入语句后将返回一个整数表示其影响的行数。它的属性与 元素的属性大部分相同(都包含id、paragramType),在本节讲解它的几个特有属性。

  • useGeneratedKeys:该属性将使 MyBatis 使用 JDBC 的 getGeneratedKeys()方法获取由数据库内部产生的主键,例如 MySQL、SQL Server 等自动递增的字段,其默认值为 false,如果为true,则要设置keyProperty
  • keyProperty:该属性的作用是将插入或更新操作时的返回值赋给 POJO 类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以将多个值用逗号隔开。
  • keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第 1 列时需要设置。如果是联合主键,可以将多个值用逗号隔开。

7、级联查询

级联关系是一个数据库实体的概念,有 3 种级联关系,分别是一对一级联、一对多级联以及多对多级联

级联的优点是获取关联数据十分方便,但是级联过多会增加数据库系统的复杂度,同时降低系统的性能。

在实际开发中要根据实际情况判断是否需要使用级联。更新和删除的级联关系很简单,由数据库内在机制即可完成。本节只讲述级联查询的相关实现。

7.1 一对一级联

MyBatis 如何处理一对一级联查询呢?在 MyBatis 中,通过 元素的子元素 处理这种一对一级联关系。

在 元素中通常使用以下属性。

  • property:指定映射到实体类的对象属性。
  • column:指定表中对应的字段(即查询返回的列名)。
  • javaType:指定映射到实体对象属性的类型。
  • select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
  • 注意:association 和 collection的property是本mapper对应实体类的中的一个属性;
public class User {
    private Integer id;
    private String name;
    private int age;
    private String gender;
    private int village;

    private Countryside v;
    //association 和 collection的property是UserMapper对应实体类的中的一个属性;
    //association的 javaType 和collection的ofType都是指对应属性所关联的实体类

    
}
<resultMap id="UserMap" type="com.mybatis.entity.User">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="gender" property="gender"/>
        <result column="village" property="village"/>
        <association property="v" javaType="village" resulrMap="">
            <id property="id" column="id"/>
            <result property="villageName" column="villageName"/>
            <!--多表之间,column不能重复,否则查询出错-->
        </association>
     </resultMap>
    
     <select id="queryAll2" parameterType="integer" resultMap="UserMap">
        SELECT p.*,c.villageName
        FROM person p,countryside c
        WHERE p.village=c.id and p.id =#{id};
    </select>

7.2 一对多级联

 <resultMap id="VillageMap" type="com.mybatis.entity.Village">
        <id column="id" property="id"></id>
        <result property="villageName" column="villageName"/>
        <collection property="user" javaType="user" resulrMap="">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="age" property="age"/>
            <result column="gender" property="gender"/>
            <result column="village" property="village"/>
        </collection>
    </resultMap>

<select id="queryAll2" parameterType="integer" resultMap="VillageMap">
        SELECT p.*,c.villageName
        FROM person p,countryside c
        WHERE p.village=c.id and c.id =#{id};
    </select>

7.3 多对多级联

暂未写,没用到

8、MyBatis缓存介绍

正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持

  1. 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
  3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
一级缓存
  • MyBatis的一级查询缓存(也叫作本地缓存)是基于org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存,其作用域是SqlSession
  • 在同一个SqlSession中两次执行相同的 sql 查询语句,第一次执行完毕后,会将查询结果写入到缓存中,第二次会从缓存中直接获取数据,而不再到数据库中进行查询,这样就减少了数据库的访问,从而提高查询效率。
  • 当一个 SqlSession 结束后,该 SqlSession 中的一级查询缓存也就不存在了。
  • myBatis 默认一级查询缓存是开启状态,且不能关闭。
  • 增删改会清空缓存,无论是否commit
    当SqlSession关闭和提交时,会清空一级缓存

可能你会有疑惑,我的mybatis bean是由spring 来管理的,已经屏蔽了sqlSession这个东西了?那怎么的一次操作才算是一次sqlSession呢?

  • spring整合mybatis后,非事务环境下,每次操作数据库都使用新的sqlSession对象。因此mybatis的一级缓存无法使用(一级缓存针对同一个sqlsession有效)
  • 在开启事物的情况之下,spring使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的
    在开启以及缓存的时候查询得到的对象是同一个对象。
    这种情况下会出现一个问题。我们先看一下代码。
public void listMybatisModel() {
        List<MybatisModel> mybatisModels = mapper.listMybatisModel();
        List<MybatisModel> mybatisModelsOther = mapper.listMybatisModel();
        System.out.println(mybatisModels == mybatisModelsOther);     
    }
System.out.println(mybatisModels == mybatisModelsOther);

输出结果竟然是true,这样说来是同一个对象。 会出现这种场景,第一次查出来的对象然后修改了,第二次查出来的就是修改后的对象。

一级缓存实现

对SqlSession的操作mybatis内部都是通过Executor来执行的。Executor的生命周期和SqlSession是一致的。Mybatis在Executor中创建了一级缓存,基于PerpetualCache 类的 HashMap

二级缓存
  • MyBatis的二级缓存是mapper范围级别的
  • SqlSession关闭后才会将数据写到二级缓存区域
  • 增删改操作,无论是否进行提交commit(),均会清空一级、二级缓存
  • 二级缓存是默认开启的。(想开启就不必做任何配置)
  • 二级缓存会使用 Least Recently Used (LRU,最近最少使用的)算法来收回。
  • 缓存不会以任何时间顺序来刷新 。
  • 缓存会存储集合或对象(无论查询方法返回什么类型的值)的 1024 个引用。
  • 缓存会被视为 read/write (可读/可写)的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改 。

用下面这张图描述一级缓存和二级缓存的关系。

img

image

配置二级缓存
  1. 在保证二级缓存的全局配置开启的情况下,给某个xml开启二级缓存只需要在xml中添加即可
// mybatis-config.xml 中配置
<settings>
    默认值为 true。即二级缓存默认是开启的
    <setting name="cacheEnabled" value="true"/>
</settings>

// 具体mapper.xml 中配置
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
    <!-- 开启本mapper的namespace下的二级缓存
    type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
    要和ehcache整合,需要配置type为ehcache实现cache接口的类型-->

    <cache />

</mapper>

如果想要设置增删改操作的时候不清空二级缓存的话,可以在其insert或delete或update中添加属性flushCache=”false”,默认为 true。

<delete id="deleteStudent" flushCache="false">
    DELETE FROM user where id=#{id}
</delete>

通过以下一些配置可以修改一些缓存参数。

<cache
    eviction= "FIFO"
    flushlnterval="600000"
    size="512"
    readOnly="true" />

配置创建了一个FIFO缓存,并每隔 60 秒刷新一次,存储集合或对象的512个引用, 而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。cache可以配置的属性如下。
eviction (收回策略)

LRU (最近最少使用的: 移除最长时间不被使用的对象,这是默认值 。
FIFO (先进先出〉 : 按对象进入缓存的顺序来移除它们 。
SOFT (软引用) : 移除基于垃圾回收器状态和软引用规则的对象 。
WEAK (弱引用) : 更积极地移除基于垃圾收集器状态和弱引用规则的对象

  • flushinterval(刷新间隔)。
    可以被设置为任意的正整数,而且它们代表一个合理
    的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。
  • size (引用数目)。 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是 1024 。
  • readOnly (只读)。属性可以被设置为 true 或 false 。只读的缓存会给所有调用者
    返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是false。
  1. 当只使用注解方式配置二级缓存时,如果在Mapper接口中,则需要增加如下配置 。
@CacheNamespace (
eviction = FifoCache.class ,
flushinterval = 60000 ,
size = 512 ,
readWrite = true)
public interface Mapper {
    
}

括号内的内容是配置缓存属性。

  1. Mapper 接口和对应的 XML 文件是相同的命名空间,想使用二级缓存,两者必须同时配置(如果接口不存在使用注解方式的方法,可以只在 XML 中配置〉,因此按照上面的方
    式进行配置就会出错 , 这个时候应该使用参照缓存。在 Mapper 接口中,参照缓存配置如下 。
@CacheNarnespaceRef(RoleMapper.class)
public interface RoleMapper {

因为想让 RoleMapper 接口中的注解方法和 XML中的方法使用相同的缓存,因此使用参照缓存配置RoleMapper.class,这样就会使用命名空间为xx.xxx.xxx.xxx.RoleMapper的缓存配置,即RoleMapper.xml 中配置的缓存 。
Mapper 接口可以通过注解引用XML 映射文件或者其他接口的缓存,在 XML 中也可以配置参照缓存,如可以在 RoleMapper.xml 中进行如下修改 。

<cache-ref narnespace="xxx.xxx.xxx.xxx.RoleMapper"/>

这样配置后XML 就会引用 Mapper 接口中配置的二级缓存,同样可以避免同时配置二级缓存导致的冲突。MyBatis 中很少会同时使用 Mapper 接口注解方式和XML映射文件,所以参照缓存并不是为了解决这个问题而设计的。参照缓存除了能够通过引用其他缓存减少配置外,主要的作用是解决脏读。

MyBatis使用SerializedCache(org.apache.ibaits.cache.decorators.SerializedCache)序列化缓存来实现可读写缓存类,井通过序列化和反序列化来保证通过缓存获取数据时,得到的是一个新的实例。因此,如果配置为只读缓存,MyBatis就会使用Map来存储缓存值,这种情况下,从缓存中获取的对象就是同一个实例。因为使用可读写缓存,可以使用SerializedCache序列化缓存。这个缓存类要求所有被序列化的对象必须实现 Serializable (java.io.Serializable)接口
虽然使用序列化得到的对象都是不一样的对象修改时都是互不影响,但是还是不安全的。

脏读的产生

Mybatis的二级缓存是和命名空间绑定的,所以通常情况下每一个Mapper映射文件都有自己的二级缓存,不同的mapper的二级缓存互不影响。

  • 引起脏读的操作通常发生在多表关联操作中,比如在两个不同的mapper中都涉及到同一个表的增删改查操作,当其中一个mapper对这张表进行查询操作,此时另一个mapper进行了更新操作刷新缓存,然后第一个mapper又查询了一次,那么这次查询出的数据是脏数据。出现脏读的原因是他们的操作的缓存并不是同一个。
脏读的避免
  • mapper中的操作以单表操作为主,避免在关联操作中使用mapper
  • 使用参照缓存
集成EhCache缓存

缓存数据有内存和磁盘两级,无须担心容量问题。

  • 缓存数据会在虚拟机重启的过程中写入磁盘。可以通过RMI、可插入API等方式进行分布式缓存。
  • 具有缓存和缓存管理器的侦昕接口。
  • 支持多缓存管理器实例以及一个实例的多个缓存区域。
1. 添加项目依赖
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.0.3</version>
    </dependency>
2. 配置 EhCache

在 src/main/resources 目录下新增 ehcache.xml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd"
    updateCheck="false" monitoring="autodetect"
    dynamicConfig="true">
    
    <diskStore path="D:/cache" />
            
    <defaultCache      
        maxElementsInMemory="3000"      
        eternal="false"      
        copyOnRead="true"
        copyOnWrite="true"
        timeToIdleSeconds="3600"      
        timeToLiveSeconds="3600"      
        overflowToDisk="true"      
        diskPersistent="true"/> 
</ehcache>

有关EhCache的详细配置可以参考地址 http://www.ehcache.org/ehcache.xml 中的内容。

  • copyOnRead 的含义是,判断从缓存中读取数据时是返回对象的引用还是复制一个对象返回。默认情况下是false,即返回数据的引用,这种情况下返回的都是相同的对象,和MyBatis默认缓存中的只读对象是相同的。如果设置为 true ,那就是可读写缓存,每次读取缓存时都会复制一个新的实例 。
  • copyOnWrite 的含义是 ,判断写入缓存时是直接缓存对象的引用还是复制一个对象然后缓存,默认也是false。如果想使用可读写缓存,就需要将这两个属性配置为true,如果使用只读缓存,可以不配置这两个属性,使用默认值 false 即可 。
  1. 修改Mapper.xml中的缓存配置
    ehcache-cache 提供了如下 2 个可选的缓存实现。
  • org.mybatis.caches.ehcache.EhcacheCache
  • org.mybatis.caches.ehcache.LoggingEhcache 这个是带日志的缓存。

在xml中添加

 <cache type ="org.mybatis.caches.ehcache.EhcacheCache" />

只通过设置 type 属性就可 以使用 EhCache 缓存了,这时cache的其他属性都不会起到任何作用,针对缓存的配置都在ehcache.xml中进行。在ehcache.xml配置文件中,只有一个默认的缓存配置,所以配置使用EhCache缓存的Mapper映射文件都会有一个以映射文件命名空间命名的缓存。如果想针对某一个命名空间进行配置,需要在 ehcache.xml 中添加一个和映射文件命名空间一致的缓存配置,例如针对RoleMapper可以进行如下配置。

    <cache      
        name="tk.mybatis.simple.mapper.RoleMapper"
        maxElementsInMemory="3000"      
        eternal="false"      
        copyOnRead="true"
        copyOnWrite="true"
        timeToIdleSeconds="3600"      
        timeToLiveSeconds="3600"      
        overflowToDisk="true"      
        diskPersistent="true"/>
集成Redis缓存
  1. 添加依赖,目前只有bata版本。
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-redis</artifactId>
            <version>1.0.0-beta2</version>
        </dependency>
  1. 配置Redis
    使用 Redis 前,必须有一个 Redis 服务,有关Redis安装启动的相关内容,可参考如下地址中的官方文档:https://redis.io/topics/quickstart。Redis服务启动后,在src/main/resources 目录下新增 redis.properties 文件 。
host=localhost
port=6379
connectionTimeout=SOOO
soTimeout=SOOO
password=
database=O
clientName=
  1. 修改mapper.xml中的配置。
<mapper namespace = ” tk.mybat 工 s.s 工 mple.mapper.RoleMapper ” 〉
<cache type= "org.mybatis.caches.redis.RedisCache" />
〈 !一其他自己直一 〉
</mapper>

配置依然很简单, RedisCache 在保存缓存数据和获取缓存数据时,使用了Java的序列化和反序列化,因此还需要保证被缓存的对象必须实现Serializable接口。改为RedisCache缓存配置后, testL2Cache 测试第一次执行时会全部成功,但是如果再次执行,就会出错。这是因为Redis作为缓存服务器,它缓存的数据和程序(或测试)的启动无关,Redis 的缓存并不会因为应用的关闭而失效。所以再次执行时没有进行一次数据库查询,所有查询都使用缓存,测试的第一部分代码中的rolel和role2都是直接从二级缓存中获取数据,因为是可读写缓存,所以不是相同的对象。当需要分布式部署应用时,如果使用MyBatis自带缓存或基础的EhCahca缓存,分布式应用会各自拥有自己的缓存,它们之间不会共享缓存 ,这种方式会消耗更多的服务器资源。如果使用类似 Redis 的缓存服务,就可以将分布式应用连接到同一个缓存服务器,实现分布式应用间的缓存共享 。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值