文章目录

    • 一、初始MyBatis
      • 1.1 ORM
      • 1.2 MyBatis是什么
        • 1.2.1 MyBatis特点
        • 1.2.2 Mybatis的适用场景
        • 1.2.3 为什么说Mybatis是半自动ORM映射工具
      • 1.3 Mybatis的优缺点
        • 1.3.1 优点
        • 1.3.2 缺点
      • 1.4 Mybatis和JDBC、Hibernate的比较
        • 1.4.1 传统JDBC开发存在的问题
        • 1.4.2 JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的
        • 1.4.3 Hibernate和MyBatis的比较
      • 1.5 在进行数据库编程时,连接池有什么作用?
    • 二、Mybatis的基础使用
      • 2.1 MyBatis编程步骤【了解即可】
      • 2.1 Mybatis的使用示例
        • 2.1.1 实体类
        • 2.1.2 Mapper.xml
        • 2.1.3 数据库连接配置文件
        • 2.1.4 mybatis-config.xml
        • 2.1.4 DAO类
        • 2.1.5 业务类
    • 三、Mybatis原理
      • 3.1 MyBatis的工作原理
      • 3.2 MyBatis架构
        • 3.2.1 API接口层
        • 3.2.2 数据处理层
        • 3.2.3 基础支撑层
        • 3.2.4 Mybatis层次结构
      • 3.3 Executor执行器
        • 3.3.1 Executor的类别
        • 3.3.2 Executor的配置
      • 3.4 mybatis-config.xml中的标签顺序
      • 3.5 Mybatis是否支持延迟加载
    • 四、Mybatis缓存
      • 4.1 一级缓存
        • 4.1.1 一级缓存原理
        • 4.1.2 生命周期
        • 4.1.3 工作流程
      • 4.2 二级缓存
        • 4.2.1 二级缓存原理
        • 4.2.2 二级缓存分类
        • 4.2.3 配置二级缓存
        • 4.2.4 二级缓存的属性
      • 4.3 缓存的相关问题
        • 4.3.1 一级缓存什么时候会失效
        • 4.3.2 二级缓存怎么开启
        • 4.3.3 一集缓存和二级缓存的查询顺序
        • 4.3.4 Mybatis默认使用的二级缓存框架

本系列文章:
  Mybatis(一)Mybatis的基本使用
  Mybatis(二)Mybatis的高级使用
  Mybatis(三)配置文件解析流程
  Mybatis(四)映射文件解析流程
  Mybatis(五)SQL执行流程
  Mybatis(六)数据源、缓存机制、插件机制

一、初始MyBatis

1.1 ORM

  了解ORM,先了解下面两个概念:

  • 持久化
      即把数据(如内存中的对象)保存到可永久保存的存储设备中。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
  • 持久层
      即专注于实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。

  ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
  ORM的理解:

  1. 它是一种将内存中的对象保存到关系型数据库中的技术;
  2. 主要负责实体对象的持久化,封装数据库访问细节;
  3. 提供了实现持久化层的另一种模式,采用映射元数据(XML)来描述对象-关系的映射细节,使得ORM中间件能在任何一个Java应用的业务逻辑层和数据库之间充当桥梁。

  Java典型的ORM框架:
   1)hibernate:全自动的框架,强大、复杂、笨重、学习成本较高
   2)Mybatis:半自动的框架, 必须要自己写sql
   3)JPA:JPA全称Java Persistence API、JPA通过JDK 5.0注解或XML描述对象-系表的映射关系,是Java自带的框架。

1.2 MyBatis是什么

  MyBatis是一款持久层框架,一个半ORM框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
  MyBatis的核心配置文件是mybatis-config.xml

1.2.1 MyBatis特点

  • 1、定制化SQL
      同为持久层框架的Hibernate,对操作数据库的支持方式较多,完全面向对象的、原生SQL的和HQL的方式。MyBatis只支持原生的SQL语句,这个“定制化”是相对Hibernate完全面向对象的操作方式的。
  • 2、存储过程
      储存过程是实现某个特定功能的一组sql语句集,是经过编译后存储在数据库中。当出现大量的事务回滚或经常出现某条语句时,使用存储过程的效率往往比批量操作要高得多。
      MyBatis是支持存储过程的,可以看个例子。假设有一张表student:
	create table student
	(
	  id bigint not null,
	  name varchar(30),
	  sex char(1),
	  primary key (id)
	);

  有一个添加记录的存储过程:

	create procedure pro_addStudent (IN id bigint, IN name varchar(30), IN sex char(1))
	begin
	   insert into student values (id, name, sex);
	end

  此时就可以在mapper.xml文件中调用存储过程:

<!-- 调用存储过程 -->
<!-- 第一种方式,参数使用parameterType -->
<select id="findStudentById" parameterType="java.lang.Long" statementType="CALLABLE" 
    resultType="com.mybatis.entity.Student">
    {call pro_getStudent(#{id,jdbcType=BIGINT,mode=IN})}
</select>

 <parameterMap type="java.util.Map" id="studentMap">
     <parameter property="id" mode="IN" jdbcType="BIGINT"/>
</parameterMap>

<!-- 调用存储过程 -->
<!-- 第二种方式,参数使用parameterMap -->
<select id="findStudentById" parameterMap="studentMap" statementType="CALLABLE" 
    resultType="com.mybatis.entity.Student">
    {call pro_getStudent(?)}
</select>
  • 3、高级映射
      可以简单理解为支持关联查询。
  • 4、避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。使用Mybatis时,数据库的连接配置信息,是在mybatis-config.xml文件中配置的,至于获取查询结果的代码,也是尽量做到了代码的简洁,以模糊查询为例,需要做两步工作:
      1)首先在配置文件中写上SQL语句,示例:
    <mapper namespace="com.test.pojo">
        <select id="listCategoryByName"  parameterType="string" resultType="Category">
            select * from   category_  where name like concat('%',#{0},'%')
        </select>    
    </mapper>

    2)在Java代码中调用此语句,示例:

        List<Category> cs = session.selectList("listCategoryByName","cat");
        for (Category c : cs) {
            System.out.println(c.getName());
        }
  • 5、Mybatis中ORM的映射方式也是比较简单的
      先看段示例代码:
    <mapper namespace="com.test.pojo">
        <select id="listCategory" resultType="Category">
            select * from   category_     
        </select>
    </mapper>

  "resultType"参数的值指定了SQL语句返回对象的类型。

1.2.2 Mybatis的适用场景

  MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
  MyBatis框架的适用场景:对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

1.2.3 为什么说Mybatis是半自动ORM映射工具

  Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
  而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

1.3 Mybatis的优缺点

1.3.1 优点

  • 1、基于SQL语句编程,相当灵活
      SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
  • 2、代码量少
      与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接。
  • 3、很好的与各种数据库兼容
  • 4、数据库字段和对象之间可以有映射关系
      提供映射标签,支持对象与数据库的ORM字段关系映射。
  • 5、能够与Spring很好的集成

1.3.2 缺点

  • 1、SQL语句的编写工作量较大
      尤其当字段多、关联表多时,SQL语句较复杂。
  • 2、数据库移植性差
      SQL语句依赖于数据库,不能随意更换数据库(可以通过在mybatis-config.xml配置databaseIdProvider来弥补),示例:
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="SQL Server" value="sqlserver"/>
        <property name="Oracle" value="oracle"/>
    </databaseIdProvider>

  然后在xml文件中,就可以针对不同的数据库,写不同的sql语句。

1.4 Mybatis和JDBC、Hibernate的比较

1.4.1 传统JDBC开发存在的问题

  JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
  传统的JDBC开发指的通常是如下的流程:

	加载驱动;
	建立连接;
	定义sql语句;
	准备静态处理块对象;
	执行sql语句;
	处理结果集;
	关闭连接.

  传统的JDBC开发的最原始的开发方式,有以下问题:

  • 1、频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题,但是使用JDBC需要自己实现连接池。
  • 2、sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布,不好维护。
  • 3、使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  • 4、结果集处理存在重复代码,处理麻烦。 这点也容易理解,在使用JDBC时,需要用 ResultSet之类的方式来遍历数据库中查询出来的一条条字段,这是不方便的,示例:
            /*ResultSet:查询结果集*/
            ResultSet rs = s.executeQuery(sql);
            while (rs.next()) {
            	/*可以使用字段名获取该列内容*/
                int id = rs.getInt("id");
                /*也可以使用字段的顺序,需要注意的是:顺序是从1开始的*/
                String name = rs.getString(2);
                float hp = rs.getFloat("hp");
                int damage = rs.getInt(4);
                System.out.printf("%d\t%s\t%f\t%d%n", id, name, hp, damage);
            }

1.4.2 JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的

  • 问题1
      数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能。
      MyBatis解决的解决方法:在mybatis-config.xml中配置数据连接池,使用连接池管理数据库连接
  • 问题2
      Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变Java代码。
      MyBatis解决的解决方法:将Sql语句配置在XXXXmapper.xml文件中,与Java代码分离
  • 问题3
      向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
      MyBatis解决的解决方法: Mybatis自动将Java对象映射至sql语句。
  • 问题4
      对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
      MyBatis解决的解决方法:Mybatis可以自动将sql执行结果映射至Java对象(一般是DTO对象)。

1.4.3 Hibernate和MyBatis的比较

  Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,建立对象与数据库表的映射。Hibernate是一个全自动的、完全面向对象的持久层框架。
  Mybatis是一个半自动化的持久层框架。
  Hibernate和MyBatis的区别:

  • 1、开发速度
      hibernate,sql语句已经被封装(可以采用完全面向对象的方式开发),直接可以使用,加快系统开发;
      Mybatis属于半自动化,sql需要手工完成,稍微繁琐;
  • 2、sql优化方面
      Hibernate自动生成sql,有些语句较为繁琐,会多消耗一些性能;
      Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;
  • 3、对象管理比对
      Hibernate是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可。示例:假设已经有个Product类,接下来就可以创建一个Product.hbm.xml,用来表明该类的一个实例就和数据库中的一条数据像对应
<hibernate-mapping package="com.test.pojo">
    <class name="Product" table="product_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" />
        <property name="price" />
    </class>
</hibernate-mapping>

  而Mybatis中没有类似的文件来管理类的实例和数据库的数据映射关系。
  Mybatis需要自行管理映射关系

  • 4、缓存方面
      相同点:Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以使用其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
      不同点:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是哪种缓存。
      MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

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

  • Mybatis优势
      1)MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
      2)MyBatis容易掌握,而Hibernate门槛较高。
      3)sql语句和Java代码耦合性低。

1.5 在进行数据库编程时,连接池有什么作用?

  由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。
  池化技术在Java开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于Java的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。

二、Mybatis的基础使用

2.1 MyBatis编程步骤【了解即可】

  • 第一种步骤
  1. 创建SqlSessionFactory;
  2. 通过SqlSessionFactory创建SqlSession;
  3. 通过sqlsession执行数据库操作;
  4. 调用sessionmit()提交事务;
  5. 调用session.close()关闭会话。

  示例:

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
 
        Category c = new Category();
        c.setName("新增加的Category");
        session.insert("addCategory",c);
         
        session.commit();
        session.close();
  • 第二种步骤
  1. 创建SqlSessionFactory对象。
  2. 通过SqlSessionFactory获取SqlSession对象。
  3. 通过SqlSession获得Mapper代理对象。
  4. 通过Mapper代理对象,执行数据库操作。
  5. 执行成功,则使用SqlSession提交事务。
  6. 执行失败,则使用SqlSession回滚事务。
  7. 关闭session会话。

  示例:

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        CategoryMapper mapper = session.getMapper(CategoryMapper.class);
  
        List<Category> cs = mapper.list();
        for (Category c : cs) {
            System.out.println(c.getName());
        }
        session.commit();
        session.close();

2.1 Mybatis的使用示例

  先在数据库建一张表:表名为student,字段有id、name、score、age、gender。

2.1.1 实体类

package com.test.po;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
	private Integer id;
	private String name;
	private Integer score;
	private Integer age;
	private Integer gender;
}

2.1.2 Mapper.xml

<!-- StudentMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis//DTD Mapper 3.0//EN"
        "http://mybatis/dtd/mybatis-3-mapper.dtd">

<mapper namespace="test">
    <select id="findAll" resultType="com.test.po.Student">
        SELECT * FROM student;
    </select>

    <insert id="insert" parameterType="com.test.po.Student">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
    </insert>
    
    <delete id="delete" parameterType="int">
        DELETE FROM student WHERE id = #{id};
    </delete>
</mapper>

2.1.3 数据库连接配置文件

  配置数据库连接信息:

	db.url=jdbc:mysql://localhost:3306/yogurt?characterEncoding=utf8
	db.user=root
	db.password=root
	db.driver=com.mysql.jdbc.Driver

2.1.4 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis//DTD Config 3.0//EN"
        "http://mybatis/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置文件信息 -->
    <properties resource="properties/db.properties"></properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 从配置文件中加载属性 -->
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.user}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 加载前面编写的SQL语句的文件 -->
        <mapper resource="StudentMapper.xml"/>
    </mappers>

</configuration>

2.1.4 DAO类

  此处调用XXXMapper.xml文件中的SQL语句。

package com.test.dao;

import com.test.po.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class StudentDao {

	private SqlSessionFactory sqlSessionFactory;

	public StudentDao(String configPath) throws IOException {
		InputStream inputStream = Resources.getResourceAsStream(configPath);
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	public List<Student> findAll() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		List<Student> studentList = sqlSession.selectList("findAll");
		sqlSession.close();
		return studentList;
	}

	public int addStudent(Student student) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		int rowsAffected = sqlSession.insert("insert", student);
		sqlSession.commit();
		sqlSession.close();
		return rowsAffected;
	}

	public int deleteStudent(int id) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		int rowsAffected = sqlSession.delete("delete",id);
		sqlSession.commit();
		sqlSession.close();
		return rowsAffected;
	}
}

2.1.5 业务类

  此处用测试类代替:

public class SimpleTest {
	private StudentDao studentDao;

	@Before
	public void init() throws IOException {
		studentDao = new StudentDao("mybatis-config.xml");
	}

	@Test
	public void insertTest() {
		Student student = new Student();
		student.setName("yogurt");
		student.setAge(24);
		student.setGender(1);
		student.setScore(100);
		studentDao.addStudent(student);
	}

	@Test
	public void findAllTest() {
		List<Student> all = studentDao.findAll();
		all.forEach(System.out::println);
	}
}

  结果示例:

三、Mybatis原理

3.1 MyBatis的工作原理

  工作原理图示:

  • 1、读取MyBatis配置文件
      mybatis-config.xml为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,例如数据库连接信息。
  • 2、加载映射文件(SQL映射文件,一般是XXXMapper.xml)
      该文件中配置了操作数据库的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.2 MyBatis架构

3.2.1 API接口层

  提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  MyBatis和数据库的交互有两种方式:使用传统的MyBatis提供的API、使用Mapper接口。

  • 1、使用传统的MyBatis提供的API
      这是传统的传递Statement Id和查询参数给SqlSession对象,使用SqlSession对象完成和数据库的交互;MyBatis提供了非常方便和简单的API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和MyBatis自身配置信息的维护操作。

      示例:
    SqlSession session = sqlSessionFactory.openSession();
    Category c = new Category();
    c.setName("新增加的Category");
    session.insert("addCategory",c);

  上述使用MyBatis的方法,是创建一个和数据库打交道的SqlSession对象,然后根据Statement Id和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。

  • 2、使用Mapper接口
      MyBatis将配置文件中的每一个<mapper>节点抽象为一个Mapper接口,而这个接口中声明的方法和跟<mapper>节点中的<select|update|delete|insert>节点项对应,即<select|update|delete|insert>节点的id值为Mapper接口中的方法名称,parameterType值表示Mapper对应方法的入参类型,而resultMap值则对应了Mapper接口表示的返回值类型或者返回结果集的元素类型
      示例:
    SqlSession session = sqlSessionFactory.openSession();
    CategoryMapper mapper = session.getMapper(CategoryMapper.class);
    List<Category> cs = mapper.list();
    for (Category c : cs) {
        System.out.println(c.getName());
    }


   根据MyBatis的配置规范配置后,通过SqlSession.getMapper(XXXMapper.class)方法,MyBatis会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mapper实例。使用Mapper接口的某一个方法时,MyBatis会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select("statementId",parameterObject)或者SqlSession.update("statementId",parameterObject)等等来实现对数据库的操作。
   MyBatis引用Mapper接口这种调用方式,纯粹是为了满足面向接口编程的需要。

3.2.2 数据处理层

  负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

  • 1、参数映射和动态SQL语句生成
      动态语句生成可以说是MyBatis框架非常优雅的一个设计,MyBatis通过传入的参数值,使用OGNL表达式来动态地构造SQL语句,使得MyBatis有很强的灵活性和扩展性。

      参数映射指的是对于Java数据类型和JDBC数据类型之间的转换:这里有包括两个过程:查询阶段,我们要将java类型的数据,转换成JDBC类型的数据,通过preparedStatement.setXXX()来设值;另一个就是对ResultSet查询结果集的JdbcType 数据转换成Java数据类型。
  • 2、SQL语句的执行以及封装查询结果集成List< E>
      动态SQL语句生成之后,MyBatis将执行SQL语句,并将可能返回的结果集转换成List<E> 。MyBatis 在对结果集的处理中,支持结果集关系一对多和多对一的转换,并且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。

3.2.3 基础支撑层

  负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

3.2.4 Mybatis层次结构

3.3 Executor执行器

3.3.1 Executor的类别

  Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  • 1、SimpleExecutor
      每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象
  • 2、ReuseExecutor
      执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象
  • 3、BatchExecutor
      执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

  作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

3.3.2 Executor的配置

  指定Executor方式有两种:

  • 1、在配置文件中指定
<settings>
    <setting name="defaultExecutorType" value="BATCH" />
</settings>
  • 2、在代码中指定
      在获取SqlSession时设置,需要注意的时是,如果选择的是批量执行器时,需要手工提交事务(默认不传参就是SimpleExecutor)。示例:
	// 获取指定执行器的sqlSession
	SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)
	// 获取批量执行器时, 需要手动提交事务
	sqlSession.commit();

3.4 mybatis-config.xml中的标签顺序

  mybatis-config.xml中,各个标签要按照如下顺序进行配置,因为mybatis加载配置文件的源码中是按照这个顺序进行解析的:

<configuration>
	<!-- 配置顺序
     properties  
     settings
     typeAliases
     typeHandlers
     objectFactory
     plugins
     environments
        environment
            transactionManager
            dataSource
     mappers
     -->
</configuration>
  • 1、<properties>
      一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息。
  • 2、<settings>
      用来开启或关闭mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>来开启延迟加载,可以用<settings name="cacheEnabled" value="true"/>来开启二级缓存。
  • 3、<typeAliases>
      在XXXMapper.xml中需要使用parameterType和resultType属性来配置SQL语句的输入参数类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为Student类,则resultType属性要写com.test.po.Student,可以用别名来简化书写,示例:
<typeAliases>
    <typeAlias type="com.test.po.Student" alias="student"/>
</typeAliases>

  之后就可以在resultType上直接写student,mybatis会根据别名配置自动找到对应的类。当然,如果想要一次性给某个包下的所有类设置别名,可以用如下的方式:

<typeAliases>
   <package name="com.test.po"/>
</typeAliases>

  如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名。
  别名使用时是不区分大小写的
  另外,对于基本的Java类型 -> 8大基本类型以及包装类,以及String类型,mybatis提供了默认的别名,别名为其简单类名的小写,比如原本需要写java.lang.String,其实可以简写为string。
  Mybatis默认的别名在TypeAliasRegistry中进行注册的,这个类就是Mybatis注册别名使用的,别名和具体的类型关联是放在这个类的一个map属性(typeAliases)中。
  Mybatis默认为很多类型提供的别名:

别名对应的实际类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

  别名的原理:Mybatis允许我们给某种类型注册一个别名,别名和类型之间会建立映射关系,这个映射关系存储在一个map对象中,key为别名的名称,value为具体的类型。当我们通过一个名称访问某种类型的时候,Mybatis根据类型的名称,先在别名和类型映射的map中按照key进行查找,如果找到了直接返回对应的类型。如果没找到,会将这个名称当做完整的类名去解析成Class对象,如果这2步解析都无法识别这种类型,就会报错。

  • 4、<typeHandlers>
      用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。
  • 5、<objectFactory>
      mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例。
  • 6、<plugins>
      可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。示例:
<!-- PageHelper 分页插件 -->
<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor">
     <property name="helperDialect" value="mysql"/>
  </plugin>
</plugins>
  • 7、<environments>
      配置数据源。
  • 8、<mappers>
      用来配置XXXMapper.xml映射文件。

3.5 Mybatis是否支持延迟加载

  Mybatis仅支持association关联对象collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false
  延迟加载默认是打开的,如果需要关闭,需要在mybatis-config.xml中修改:

    <settings> 
         <!-- 打开延迟加载的开关 --> 
         <setting name="lazyLoadingEnabled" value="true" /> 
         <!-- 将积极加载改为消息加载即按需加载 --> 
         <setting name="aggressiveLazyLoading" value="false"/> 
    </settings> 

  如果设置了全局加载,但是希望在某一个sql语句查询的时候不适用延时策略,可以添加如下属性:

    <association property="dept" select="com.test.dao.DeptDao.getDeptAndEmpsBySimple" c
        olumn="deptno" fetchType="eager"/>

fetchType,有效值为lazy和eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

  它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

四、Mybatis缓存

  MyBatis中,默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存(即一级缓存)。
  缓存分为一级缓存和二级缓存:一级缓存:线程级别的缓存,是本地缓存,sqlSession级别的缓存;二级缓存:全局范围的缓存,不止局限于当前会话。

4.1 一级缓存

  每当使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。
  在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
  为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。这就是一级缓存。

  一级缓存默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库
  当然,若第一次和第二次相同的SQL查询之间,执行DML(增删改),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库。
  一级缓存在下面情况会被清除:

  • 1、在同一个SqlSession下执行增删改操作时(即在发送过程中发生了数据的修改),会清除一级缓存;
  • 2、SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存;
  • 3、对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在mybatis中会被封装成一个MappedStatement);
  • 4、在全局配置文件中如下设置:
	<setting name="localCacheScope" value="STATEMENT"/>`

   这样会使一级缓存失效,二级缓存不受影响。

  • 5、传递的参数发生了变化。
  • 6、在两次查询期间,手动去清空缓存(sqlSession.clearCache()),也会让缓存失效。

4.1.1 一级缓存原理

  当创建了一个SqlSession对象时,MyBatis会为这个SqlSession对象创建一个新的Executor执行器,而缓存信息就被维护在这个Executor执行器中,MyBatis将缓存和对缓存相关的操作封装成了 Cache 接口中。SqlSession、Executor、Cache之间的关系图示:

  Executor接口的实现类BaseExecutor中,拥有一个Cache接口的实现类 PerpetualCache,对于BaseExecutor对象而言,它将使用PerpetualCache对象维护缓存。

  PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v>来实现的,没有其他的任何限制。

4.1.2 生命周期

  1. MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
  2. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
  3. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
  4. SqlSession中执行了任何一个update 操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

4.1.3 工作流程

  1)对于某个查询,根据statementId、params、rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
  2)判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
  3)如果命中,则直接将缓存结果返回;
  4)如果没命中:

  1. 去数据库中查询数据,得到查询结果;
  2. 将key和查询到的结果分别作为key,value对存储到Cache中;
  3. 将查询结果返回。

  图示:

  怎样判断某两次查询是完全相同的查询?也就是说:如何确定Cache中的key值?MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询:

  1. 传入的 statementId;
  2. 查询时要求的结果集中的结果范围(结果的范围通过rowBounds.offset和rowBounds.limit表示);
  3. 这次查询所产生的最终要传递给JDBC的Sql语句字符串(boundSql.getSql() );
  4. 传递给java.sql.Statement要设置的参数值。

  也就是说:Cache Key由以下条件决定: statementId、rowBounds、传递给JDBC的SQL、传递给JDBC的参数值。

4.2 二级缓存

  二级缓存是全局作用域缓存(Mapper级别的缓存),默认是不开启的,需要手动进行配置。二级缓存实现的时候要求实体类实现Serializable接口,二级缓存在sqlSession关闭或提交之后才会生效。

4.2.1 二级缓存原理

  Mybatis缓存机制示意图:

  当开一个会话时,一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章 。如果用户配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。
  CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后再返回给用户。

  CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式。

4.2.2 二级缓存分类

  MyBatis并不是简单地对整个Application,只有一个Cache缓存对象。它将缓存划分的更细,是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象,具体:

a.为每一个Mapper分配一个Cache缓存对象(使用<cache>节点配置);
b.多个Mapper共用一个Cache缓存对象(使用<cache-ref>节点配置);

  对于每一个Mapper.xml,如果在其中使用了<cache>节点,则MyBatis会为这个Mapper创建一个Cache缓存对象,图示:

  上述的每一个Cache对象,都会有一个自己所属的namespace命名空间,并且会将Mapper的namespace作为它们的ID。
  如果想让多个Mapper公用一个Cache的话,你可以使用<cache-refnamespace=“”>节点,来指定你的这个Mapper使用到了哪一个Mapper的Cache缓存。

  

4.2.3 配置二级缓存

  要开启二级缓存总开关,需要进行3个步骤的配置:

  • 1、需要在全局配置文件中添加配置
	<settings name="cacheEnabled" value="true"/>
  • 2、在某个具体的mapper.xml中增加配置
	<cache />

  <cache/>标签的效果:

  1. 映射语句文件中的所有 select 语句的结果将会被缓存。
  2. 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  3. 缓存会使用最近最少使用算法(LRU)算法来清除不需要的缓存。
  4. 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  5. 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  6. 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
  • 3、实体类必须要实现Serializable接口

  经过以上三步配置,就开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中。
  MyBatis对二级缓存的支持粒度很细,它会指定某一条查询语句是否使用二级缓存。虽然在Mapper中配置了<cache>,并且为此Mapper分配了Cache对象,这并不表示使用Mapper中定义的查询语句查到的结果都会放置到Cache对象之中,我们必须指定Mapper中的某条选择语句是否支持缓存,即如下所示,在<select>节点中配置useCache="true",Mapper才会对此Select的查询支持缓存特性,否则,不会对此Select查询,不会经过Cache缓存。

4.2.4 二级缓存的属性

  在二级缓存使用时,有一些属性:

  • eviction
      表示缓存回收策略,默认是LRU。
      LRU:最近最少使用的,移除最长时间不被使用的对象
      FIFO:先进先出,按照对象进入缓存的顺序来移除
      SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
      WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
  • flushInternal
      刷新间隔,单位毫秒,默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
  • size
      引用数目,正整数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出。
  • readonly
      只读。
      设置为true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
      设置为false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值。

  二级缓存跟一级缓存中不会同时存在数据,因为二级缓存中的数据是在sqlsession关闭之后才生效的。缓存查询的顺序是先查询二级缓存再查询一级缓存
  在开启了完全局的二级缓存后,还可以在每一个单独的select语句进行特殊的缓存设置:

  1. 在setting中设置,配置二级缓存开启,一级缓存默认一直开启
 <setting name="cacheEnabled" value="true"/>
  1. select标签的useCache属性:在每一个select的查询中可以设置当前查询是否要使用二级缓存,只对二级缓存有效。
  2. sql标签的flushCache属性:增删改操作默认值为true,sql执行之后会清空一级缓存和二级缓存,而查询操作默认是false。
  3. sqlSession.clearCache():用来清除一级缓存。

4.3 缓存的相关问题

4.3.1 一级缓存什么时候会失效

  一级缓存指的是sqlsession(回话)级别的缓存,关闭回话之后自动失效,默认情况下是开启的。会失效的情况:

  • 1、不在同一个session中执行相同的sql语句;
  • 2、当传递对象的时候,如果对象中的属性值不同,也不会走缓存;
  • 3、在同一次查询过程中,如果数据库中数据发生了修改,那么缓存会失效。不同回话之间是不受影响的;
  • 4、如果在一个会话过程中,手动清空了缓存(sqlSession.clearCache()),那么缓存也会失效。

4.3.2 二级缓存怎么开启

  二级缓存:表示的是全局缓存,必须要等到sqlsession关闭之后才会生效(这意味着二级缓存跟一级缓存中不会同时存在数据,因为二级缓存中的数据是在sqlsession关闭之后才生效的)。
  要想使某条Select查询支持二级缓存,需要进行如下设置:

  1. 修改全局配置文件,在settings中添加配置。
   <setting name="cacheEnabled" value="true"/>
  1. 指定在哪个映射文件中使用缓存的配置。
   <cache></cache>
  1. 对应的java实体类必须要实现序列化的接口。
  2. 该select语句的参数useCache=true

4.3.3 一集缓存和二级缓存的查询顺序

  如果MyBatis使用了二级缓存,并且Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:先查二级缓存,再查一级缓存,再查数据库
  即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此。

  一集缓存和二级缓存的具体查询顺序:

  • 1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库。
  • 2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取。
  • 3、一般不会关闭一级缓存。
  • 4、二级缓存默认不开启。
  • 5、如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库。
  • 6、如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库。

4.3.4 Mybatis默认使用的二级缓存框架

  Ehcache,这是一个Java进程内的缓存框架。
  Ehcache的主要特性:

快速;
简单;
多种缓存策略;
缓存数据有内存和磁盘两级,无须担心容量问题;缓存数据会在虚拟机重启的过程中写入磁盘;
可以通过RMI、可插入API等方式进行分布式缓存;
具有缓存和缓存管理器的监听接口;
支持多级缓存管理器实例以及一个实例的多个缓存区域。

更多推荐

Mybatis(一)Mybatis的基本使用