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

一、初始MyBatis

1.1 ORM

  要了解ORM,先了解下面概念:

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

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

  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>

  statementType="CALLABLE"表示调用存储过程。

  • 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的映射方式也是比较简单的
      "resultType"参数的值指定了SQL语句返回对象的类型。示例代码:
    <mapper namespace="com.test.pojo">
        <select id="listCategory" resultType="Category">
            select * from   category_     
        </select>
    </mapper>
1.2.2 Mybatis的适用场景

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

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

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

  MyBatis作为半自动ORM映射工具与全自动ORM工具相比,有几个主要的区别点:

  • 1、SQL的灵活性
      MyBatis作为半自动ORM映射工具,开发人员可以灵活地编写SQL语句,充分发挥数据库的特性和优势。而全自动ORM工具通常会在一定程度上限制开发人员对SQL的灵活控制。
  • 2、映射关系的可定制性
      MyBatis允许开发人员通过配置文件(或注解)自定义对象和数据库表之间的映射关系,可以满足各种复杂的映射需求。而全自动ORM工具通常根据约定和规则自动生成映射关系,对于某些特殊需求无法满足。
  • 3、SQL的可复用性
      MyBatis支持SQL的可复用性,可以将常用的SQL语句定义为独立的SQL片段,并在需要的地方进行引用。而全自动ORM工具通常将SQL语句直接与对象的属性绑定在一起,缺乏可复用性。
  • 4、性能调优的灵活性
      MyBatis作为半自动ORM映射工具,允许开发人员对SQL语句进行灵活的调优,通过手动编写SQL语句和使用高级特性进行性能优化。而全自动ORM工具通常将性能优化的控制权交给框架,开发人员无法灵活地对SQL进行调优。

  MyBatis作为一种半自动ORM映射工具,相对于全自动ORM工具具有更高的灵活性和可定制性。通过灵活的SQL控制、自定义的映射关系、可复用的SQL以及灵活的性能调优,MyBatis可以满足各种复杂的映射需求和性能优化需求。虽然MyBatis相对于全自动ORM工具需要开发人员编写更多的SQL语句,但正是由于这种半自动的特性,使得MyBatis在某些复杂场景下更加灵活和可控。
  因此,我们可以说MyBatis是一种半自动ORM映射工具,与全自动的ORM工具相比,它更适用于那些对SQL灵活性和性能调优需求较高的场景。

1.2.4 Mybatis的优缺点

  Mybatis有以下优点:

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

  Mybatis有以下缺点:

  • 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语句。示例:

  <select id="selectById" resultType="user"  databaseId="mysql">
	  select * from user where id = #{id}
  </select>

  关于数据源,可以在配置文件中配置一个默认数据源,示例:

    <environments default="development">
        <environment id="development">
        ...
        </environment>
    </environments>
  • 3、字段映射标签和对象关系映射标签仅仅是对映射关系的描述,具体实现仍然依赖于sql
      示例:
public class Student{
	String name;	
	List<Interest>  interests;
}

public class Interest{
	String studentId;
	String name;
	String direction;
}
	<resultMap id="ResultMap" type="com.test.Student">
        <result column="name" property="name" />
        <collection property="interests" ofType="com.test.Interest">
            <result column="name" property="name" />
            <result column="direction" property="direction" />
        </collection>
    </resultMap>

  在该例子中,如果查询sql中,没有关联Interest对应的表,则查询出数据映出的Student对象中,interests属性值就会为空。

  • 4、DAO层过于简单,对象组装的工作量较大
      即Mapper层Java代码过少,XxxMapper.xml文件中维护数据库字段和实体类字段的工作量较大。
  • 5、不支持级联更新、级联删除*
      仍以上面的Student和Interest为例,当要更新/删除某个Student的信息时,需要在两个表进行手动更新/删除。
1.2.5 Mybatis使用时可能会产生什么问题
  • 1、使用不当,会导致N+1的sql性能问题*
      “N+1”问题具体指的是:
  1. 执行了一个单独的SQL语句来获取结果的一个列表(就是“1”)。
  2. 对列表返回的每条记录,执行一个select查询语句来为每条记录加载详细信息(就是“N”)。

  其实就是相关联的两步查询,可能会出现"N+1"的情况。修改方法如下:

1、在sql语句中使用join语句连接多张表并进行查询(常用)。
2、使用懒加载技术,延迟“N查询”部分中各操作被执行的时间节点;
3、合并N查询为一个查询,通过使用Sql的关键字in。

  • 2、使用不当,会分页BUG*
      写法1:
	//错误写法
	startPage();
	List<User> list;
	if(user != null){
	    list = userService.selectUserList(user);
	} else {
	    list = new ArrayList<User>();
	}
	Post post = postService.selectPostById(1L);
	return getDataTable(list);
	
	//正确写法
	List<User> list;
	if(user != null){
		startPage();
		list = userService.selectUserList(user);
	} else {
		list = new ArrayList<User>();
	}
	Post post = postService.selectPostById(1L);
	return getDataTable(list);

  以上代码错误的原因是:由于user存在null的情况,就会导致pageHelper生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。 当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
  写法2:

	//错误写法
	startPage();
	Post post = postService.selectPostById(1L);
	List<User> list = userService.selectUserList(user);
	return getDataTable(list);

	//正确写法
	Post post = postService.selectPostById(1L);
	startPage();
	List<User> list = userService.selectUserList(user);
	return getDataTable(list);

  以上代码错误的原因是:只对开启分页后的第一个查询sql语句查到的数据进行分页

  • 3、使用不当,会产生脏数据*
      产生脏数据的原因是Mybatis的二级缓存机制。
      MyBatis二级缓存是和命名空间绑定的 ,所以通常情况下不同的Mapper映射文件都拥有自己的二级缓存,不同Mapper之间的二级缓存互不影响 。在关联多表查询时肯定会将该查询放到某个命名空间下的映射文件中,这样一个多表的查询就会缓存在该命名空间的二级缓存中。
      因此,对于同一个表的数据,在一个Mapper文件了进行了增删改查,该Mapper文件对应的二级缓存发生了变化。该表所在的别的Mapper文件的二级缓存可能没发生变化,这种情况下就会产生脏数据。
      脏数据解决方法:不使用Mybatis的二级缓存,使用Redis的缓存

1.3 Mybatis和JDBC、Hibernate的区别

1.3.1 JDBC和Mybatis的比较

  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、结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便。 这点也容易理解,在使用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);
    }
  • 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.3.2 Hibernate和MyBatis的比较

  相同点:都是对JDBC的封装,都是持久层的框架,都用于dao层的开发。

  • 不同点1:映射关系
      MyBatis是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单。
      Hibernate是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂。

  • 不同点2:SQL优化和移植性
      Hibernate对SQL语句封装,提供了日志、缓存、级联(级联比MyBatis强大)等特性, 此外还提供HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
      MyBatis需要手动编写SQL,支持动态SQL、处理列表、动态生成表名、支持存储过程。 开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优 化容易。

  • 不同点3:开发难易程度和学习成本
      Hibernate是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如: 办公自动化系统。
      MyBatis是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互 联网电子商务系统。

  • 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代码耦合性低。

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

二、Mybatis的基础使用

  以下两种方式了解即可,实际开发中不会这样使用Mybatis。

2.1 MyBatis编程步骤

  • 第一种步骤
  1. 创建SqlSessionFactory;
  2. 通过SqlSessionFactory创建SqlSession;
  3. 通过sqlsession执行数据库操作;
  4. 调用session.commit()提交事务;
  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.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/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.org//DTD Config 3.0//EN"
        "http://mybatis.org/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);
	}
}

  结果示例:

2.3 mybatis-config.xml*

  mybatis-config.xml是Mybatis使用的核心配置文件。在该文件中,各个标签是有顺序的,因为mybatis加载配置文件的源码中是按照这个顺序进行解析的:

<configuration>
	<!-- 配置顺序
     properties  
     settings
     typeAliases
     typeHandlers
     objectFactory
     plugins
     environments
        environment
            transactionManager
            dataSource
     mappers
     -->
</configuration>
  • 1、<properties>
      一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息。示例:
<properties>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://mysql://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="learn"/>
</properties>

  置完这些属性信息之后,就可以在XML文件的上下文中使用:

<dataSource type="POLLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>
  • 2、<settings>
      用来开启或关闭mybatis的一些特性。示例:
	<!-- 配置全局属性 -->
	<settings>
		<!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
		<setting name="useGeneratedKeys" value="true" />

		<!-- 使用列别名替换列名 默认:true -->
		<setting name="useColumnLabel" value="true" />

		<!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
		<setting name="mapUnderscoreToCamelCase" value="true" />
		
		<!-- 开启延迟加载 -->
		<setting name="lazyLoadingEnabled" value="true"/>
		
		<!-- 开启二级缓存 -->
        <settings name="cacheEnabled" value="true"/>
	</settings>
  • 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。
      如果要对特殊的字段进行解析,可以自定义类型转换器。简单的使用步骤:
      1)在Java代码中继承BaseTypeHandler,实现自定义转换器逻辑。
      2)在mybatis-config.xml文件中,注册自定义转化器:
    <typeHandlers>
        <typeHandler handler="com.zdp.type.CommentTypeHandler"/>
    </typeHandlers>

  3) 在需要使用的字段上指定自定义转化器:

    <!-- 插入时使用 -->
    <insert id="insertBlog" parameterType="com.zdp.entity.BlogComment">
        insert into blog (bid, name, author_id, comment)
        values (
            #{bid,jdbcType=INTEGER},
            #{name,jdbcType=VARCHAR},
            #{authorId,jdbcType=INTEGER},
            #{comment,jdbcType=VARCHAR, typeHandler=com.zdp.type.CommentTypeHandler}
        )
    </insert>

    <!-- 查询时使用 -->
    <resultMap id="BaseResultMap" type="com.zdp.entity.BlogComment">
        <id column="bid" property="bid" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="author_id" property="authorId" jdbcType="INTEGER"/>
        <result column="comment" property="comment" jdbcType="VARCHAR" typeHandler="com.zdp.type.CommentTypeHandler"/>
    </resultMap>
    <select id="selectBlogById" resultMap="BaseResultMap" statementType="PREPARED" useCache="false">
        select * from blog where bid = #{bid}
    </select>
  • 5、<objectFactory>
      mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例。
      该标签使用较少,如果通过继承DefaultObjectFactory类的方法来实现了自定义的ObjectFactory,那么就得使用Mybatis提供的标签来注册自定义的ObjectFactory。示例:
    <objectFactory type="">
        <property name="" value=""/>
    </objectFactory>
  • 6、<plugins>
      可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。示例:
<!-- PageHelper 分页插件 -->
<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor">
     <property name="helperDialect" value="mysql"/>
  </plugin>
</plugins>
  • 7、<environments>
      配置数据源。示例:
<environments default="development">
		<environment id="development">
			<!-- 使用jdbc事务管理 -->
			<transactionManager type="JDBC" />
			<!-- 数据库连接池 -->
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
</environments>
  • 8、<mappers>
      用来配置XxxMapper.xml映射文件。示例:
<!-- Using classpath relative resources -->
<mappers>
    <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
    <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

  在实际开发中,上面的配置XxxMapper文件的方式不太方便,一般使用@Mapper的方式进行配置。

三、Mybatis原理

3.1 MyBatis的工作原理*

  工作原理图示:

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

3.4.1 延迟加载是什么

  MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。
  假如Clazz 类中有子对象HeadTeacher。两者的关系:

public class Clazz {
	private Set<HeadTeacher> headTeacher;
	//...
}

  是否查出关联对象的示例:

    @Test
        public void testClazz() {
        ClazzDao clazzDao = sqlSession.getMapper(ClazzDao.class);
        Clazz clazz = clazzDao.queryClazzById(1);
        //只查出主对象
        System.out.println(clazz.getClassName());            
        //需要查出关联对象 
        System.out.println(clazz.getHeadTeacher().size());   
    }
3.4.2 延迟加载的设置*

  在Mybatis中,延迟加载可以分为两种:延迟加载属性和延迟加载集合,association关联对象collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false

  • 延迟加载的全局设置
      延迟加载默认是关闭的。如果需要打开,需要在mybatis-config.xml中修改:
    <settings> 
         <!-- 延迟加载的开关 --> 
         <setting name="lazyLoadingEnabled" value="true" /> 
         <!--不是必要的标签   false 深入式延迟加载   true 侵入式延迟加载 -->
	 	<setting name="aggressiveLazyLoading" value="false"/>
    </settings> 

  比如class班级与student学生之间是一对多关系。在加载时,可以先加载class数据,当需要使用到student数据时,我们再加载 student 的相关数据。

  侵入式延迟加载,指的是只要主表的任一属性加载,就会触发延迟加载,比如:class的name被加载,student信息就会被触发加载。
  深度延迟加载指的是只有关联的从表信息被加载,延迟加载才会被触发。通常,更倾向使用深度延迟加载。

  • 延迟加载的局部设置
      如果设置了全局加载,但是希望在某一个sql语句查询的时候不适用延时策略,可以配置局部的加载策略。示例:
    <association 
    	property="dept" select="com.test.dao.DeptDao.getDeptAndEmpsBySimple" 
    	column="deptno" fetchType="eager"/>

  fetchType值有2种,eager:立即加载;lazy:延迟加载。 由于局部的加载策略的优先级高于全局的加载策略。指定属性后,将在映射中忽略全局配置参数lazyLoadingEnabled,使用属性的值。

3.4.3 延迟加载的原理

  MyBatis使用Java动态代理来为查询对象生成一个代理对象。当访问代理对象的属性时,MyBatis会检查该属性是否需要进行延迟加载。如果需要延迟加载,则MyBatis将再次执行SQL查询,并将查询结果填充到代理对象中。

四、Mybatis缓存*

  缓存分为一级缓存和二级缓存。一级缓存:线程级别的缓存,sqlSession级别的缓存;二级缓存:全局范围的缓存。默认情况下一级缓存是开启的,而且是不能关闭的。
  Mybatis缓存机制示意图:

4.1 一级缓存

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

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

  • 1、在同一个SqlSession下执行增删改操作时,会清除一级缓存;
  • 2、SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存;
  • 3、对mapper.xml中的某个CRUD标签,设置属性flushCache=true,即强制刷新缓存。默认情况下,增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
  • 4、在全局配置文件中如下设置:
	<setting name="localCacheScope" value="STATEMENT"/>`

   localCacheScope,用于控制一级缓存的级别,该参数的取值为SESSION、STATEMENT。当指定localCacheScope参数值为SESSION时,缓存对整个SqlSession有效,只有执行DML语句(更新语句)时,缓存才会被清除。当localCacheScope值为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对象的数据,但是该对象可以继续使用。

  • 一级缓存原理(sqlsession级别)
      第一次发出一个查询sql,sql查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个map。

  key:MapperID+offset+limit+Sql+所有的入参
  value:查到的数据。

  同一个sqlsession再次发出相同的sql,就从缓存中取出数据。如果两次中间出现commit操作(修改、添加、删除),本sqlsession中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据库查询,从数据库查询到再写入缓存。

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 二级缓存*

  一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章。如果用户配了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。
  CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后在返回给用户。
  二级缓存是全局作用域缓存(Mapper级别的缓存),默认是不开启的,需要手动进行配置。二级缓存实现的时候要求实体类实现Serializable接口,二级缓存在sqlSession关闭或提交之后才会生效。
  二级缓存是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的 sqlsession 是可以共享的。
  MyBatis的二级缓存设计得比较灵活,你可以使用MyBatis自己定义的二级缓存实现;你也可以通过实现org.apache.ibatis.cache.Cache接口自定义缓存;也可以使用第三方内存缓存库。

4.2.1 二级缓存原理

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

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

  • 二级缓存原理
      二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为key创建缓存数据结构,结构是map。mybatis的二级缓存是通过CacheExecutor实现的。CacheExecutor其实是Executor的代理对象。所有的查询操作,在CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库。
      key:MapperID+offset+limit+Sql+所有的入参。
      具体使用需要配置:
  1. Mybatis 全局配置中启用二级缓存配置
  2. 在对应的 Mapper.xml 中配置 cache 节点
  3. 在对应的 select 查询节点中添加 useCache=true
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的查询中可以设置当前查询是否要使用二级缓存,只对二级缓存有效。useCache默认为true,表示会将本条语句的结果进行二级缓存。设置为false时不使用二级缓存。
  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等方式进行分布式缓存;
具有缓存和缓存管理器的监听接口;
支持多级缓存管理器实例以及一个实例的多个缓存区域。

4.3.5 清空或者跳过二级缓存的3种方式*
  1. 对应的mapper中执行增删改查会清空二级缓存中数据。
  2. select元素的flushCache属性置为true,会先清空二级缓存。存中的数据,然后再去db中查询数据,然后将数据再放到二级缓存中
  3. select元素的useCache属性置为true,可以使这个查询跳过二级缓存,然后去查询数据。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐