Mybatis

The woodcutter’s axe begged for its handle from the tree.
The tree gave it.

樵夫的斧头,问树要斧柄。                  
树便给了他。

Mybatis

点击跳转Mybatis-Github官方

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,是一个基于Java的持久层框架。

  • 持久层: 可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏,在断电或者其他情况下,重新开启系统仍然可以读取到这些数据。
  • 优点: 可以使用巨大的磁盘空间存储相当量的数据,并且很廉价
  • 缺点:慢(相对于内存而言)

为什么使用 MyBatis

在我们传统的 JDBC 中,我们除了需要自己提供 SQL 外,还必须操作 Connection、Statment、ResultSet,不仅如此,为了访问不同的表,不同字段的数据,我们需要些很多雷同模板化的代码,闲的繁琐又枯燥

而我们在使用了 MyBatis 之后,只需要提供 SQL 语句就好了,其余的诸如:建立连接、操作 Statment、ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点于是可以就此集中在 SQL 语句上,关注在增删改查这些操作层面上。

并且 MyBatis 支持使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

搭建环境

创建maven项目,配置pom.xml依赖包

点击跳转Mybatis_Maven环境配置

Mybaitis官方中文配置文件

从 XML 中构建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

rescouces文件下创建mybatis-config.xml配置文件

点击跳转Mybatis_XML环境配置

点击跳转Mybatis环境配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/数据库名?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>
</configuration>

封装工具类

获取sqlSessionFactory对象,从sqlSessionFactory中获取SqlSession

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.bobo.utils;

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;

//sqlSessionFaction --> sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用Mybatis的第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}

//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}

优化配置

引入配置文件

通过propertise配置文件实现引用

db.properties

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
username=用户名
password=密码

mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--导入配置-->
<properties resource="db.properties">
<!--当在这里配置属性时,优先使用外部配置文件-->
</properties>

<environments default="mysql">
<environment id="mysql">
<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>

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

配置别名

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--别名
用来减少完全限定名的冗余
-->
<typeAliases>
<!--方法一:可以给实体类其别名-->
<typeAlias type="com.bobo.pojo.User" alias="User"></typeAlias>
<!--
方法二:
指定一个包名,Mybatis会在包名下搜索所需要的JavaBean
扫描实体类的包,它的默认别名就是这个类的类名
在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名
也可以在实体类上增加注解
-->
<package name="com.bobo.pojo"/>
</typeAliases>
1
2
3
4
5
6
7
8
<!--方法一-->
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
<!--方法二-->
<select id="getUserList" resultType="user">
select * from mybatis.user
</select>

设置(settings)

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true \ false true
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true \ false False
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J \ LOG4J(deprecated since 3.5.9) \ LOG4J2 \ JDK_LOGGING \ COMMONS_LOGGING \ STDOUT_LOGGING \ NO_LOGGING 未设置
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true \ false false

映射器(mappers)

可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等告诉 MyBatis 到哪里去找映射文件.

方式一:resource

1
2
3
4
><!-- 使用相对于类路径的资源引用 -->
><mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
></mappers>

方式二:class
1
2
3
4
><!-- 使用映射器接口实现类的完全限定类名 -->
><mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
></mappers>

注意:

  1. 接口和Mapper配置文件必须同名
  2. 接口和Mapper配置文件必须在同一个包下

方式三:package

1
2
3
4
><!-- 将包内的映射器接口实现全部注册为映射器 -->
><mappers>
<package name="org.mybatis.builder"/>
></mappers>

编写程序

MyBatis 提供的所有特性都可以利用基于 XML 的映射语言来实现,这使得 MyBatis 在过去的数年间得以流行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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接口-->
<mapper namespace="com.bobo.Mapper.UserMapper">
<!--
id:对应的namespace中的方法名
resultType:SQL语句执行的返回值
parameterType:参数类型
-->
<select id="getUserList" resultType="com.bobo.pojo.User">
select * from mybatis.user
</select>
</mapper>

与之对应的是

1
2
3
4
5
6
7
8
9
10
11
package com.bobo.Mapper;
import com.bobo.pojo.User;
import java.util.List;
import java.util.Map;
public interface UserMapper{
/**
* @return 查询全部用户
* 得到用户列表
*/
List<User> getUserList();
}

绑定

每一个Mapper.XML都需要在Mybatis核心配置文件中注册

1
2
3
4
<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/bobo/dao/UserMapper.xml"/>
</mappers>

如果绑定失败,可能是资源目录没有导出成功,点击跳转Maven资源导出配置

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.bobo.Mapper;
import com.bobo.pojo.User;
import com.bobo.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest{
@Test
public void test(){
//1.获得SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:getMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

//方式二:
// List<User> userList = sqlSession.selectList("com.bobo.dao.UserMapper.getUserList");
for (User user : userList) {
System.out.println(user);
}

//关闭SqlSession
sqlSession.close();
}
}

注意:增删改需要提交事务

1
2
3
//提交事务
sqlSession.commit();
sqlSession.close();

对命名空间的一点补充

在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

命名解析:为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

  1. 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。

  2. 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

作用域(Scope)和生命周期

对象生命周期和依赖注入框架

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。

  1. SqlSessionFactoryBuilder

    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

  2. SqlSessionFactory

    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

  3. SqlSession

    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中。

XML 映射文件

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • resultType - 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。
  • parameterType - 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句

结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

举例

1
2
3
4
5
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>

1
2
3
4
5
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>

复杂查询

  1. 多对一查询(多个同学对应一个老师)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.bobo.pojo;
    import lombok.Data;
    @Data
    public class Student {
    private int id;
    private String name;
    //学生要关联一个老师
    private Teacher teacher;
    }
    1
    2
    3
    4
    5
    6
    7
    package com.bobo.Mapper;
    import com.bobo.pojo.Student;
    import java.util.List;
    public interface StudentMapper {
    //查询所有的学生信息,以及对应的老师的信息
    public List<Student> getStudent();
    }
  1. 方法一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!--思路:
    1。查询所有学生信息
    2。根据查询出来的学生的tid 寻找对应的老师
    -->
    <select id="getStudent" resultMap="StudentTeacher">
    select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂的属性需要单独处理
    对象:association
    集合:collection
    javaType 指定的属性类型
    -->

    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>
    <select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{tid}
    </select>
  2. 方法二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--按照结果集嵌套处理-->
    <select id="getStudent" resultMap="StudentTeacher">
    select s.id sid,s.name sname,t.name tname,t.id tid
    from student s,teacher t where s.tid = t.id;
    </select>
    <resultMap id="StudentTeacher" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    </association>
    </resultMap>
  1. 一对多(一个老师对应多个同学)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.bobo.pojo;
    import com.bobo.pojo.Student;
    import lombok.Data;
    import java.util.List;
    @Data
    public class Teacher {
    private int id;
    private String name;

    //一个老师拥有多个学生
    private List<Student> students;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.bobo.Mapper;
    import com.bobo.pojo.Teacher;
    import org.apache.ibatis.annotations.Param;
    public interface TeacherMapper {
    //获取所有老师
    //List<Teacher> getTeacher();
    //获取指定老师下的所有学生以及老师的信息
    Teacher getTeacher(@Param("tid") int id);
    }
  1. 方法一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!--按照结果嵌套查询-->
    <select id="getTeacher" resultMap="TeacherStudent">
    select s.id sid, s.name sname, t.name tname, t.id tid
    from student s,teacher t
    where s.tid = t.id and t.id = #{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--复杂的属性,我们需要单独处理
    javaType 指定的属性类型
    集合中的泛型信息,我们使用ofType获取
    -->
    <collection property="students" ofType="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <result property="tid" column="tid"/>
    </collection>
    </resultMap>
  2. 方法二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <select id="getTeacher" resultMap="TeacherStudent">
    select * from mybatis.teacher where id = #{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="id"/>
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"
    column="id"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
    select *
    from mybatis.student where tid = #{tid}
    </select>

日志

SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

STDOUT_LOGGING 标准日志输出

mybatis-config.xml

1
2
3
4
<settings>
<!--标准日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

LOG4J(deprecated since 3.5.9)

1
2
3
4
<settings>
<!--LOG4J日志-->
<setting name="logImpl" value="LOG4J"/>
</settings>

使用LOG4J需要添加propertise配置文件log4j.properties(直接百度)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
log4j.rootLogger=DEBUG,console,file

log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/bobo.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

log4j使用

  1. 在要使用Log4j的类中,导入包org.apache.log4j.Logger;

  2. 日志对象,参数为当前类的class

    1
    static Logger logger = Logger.getLogger(UserDaoTest.class);
  3. 日志级别

    1
    2
    3
    logger.info("info:进入了testLog4j");
    logger.debug("debug:进入了testLog4j");
    logger.error("error:进入了testLog4j");

#{} 和 ${} 的区别

#{}和${}这两个语法是为了动态传递参数而存在的,是Mybatis实现动态SQL的基础,总体上他们的作用是一致的(为了动态传参),但是在编译过程、是否自动加单引号、安全性、使用场景等方面有很多不同,下面详细比较两者间的区别:

  1. 编译过程

    1. #{} 占位符 :动态解析 -> 预编译 -> 执行
    2. ${} 拼接符 :动态解析 -> 编译 -> 执行

    预编译可以类比java类的编译,java类被编译成class文件,载入虚拟机,载入虚拟机的字节码文件可以先被编译成机器吗,那么在执行某行代码的时候就可以直接执行编译后的机器码,而不用从字节码开始编译再执行,那么执行效率就高了。这也是为啥热机状态比冷机状态可以抗更多负载的原因。

    sql的预编译也是一样的道理,在执行执行前就编译好,等执行时直接取编译结果去执行。省去编译时间。sql预编译后会在参数位置用占位符表示。

    预编译:数据库驱动在发送sql和参数到DBMS之前,先对sql语句进行编译处理,之后DBMS则可以直接对sql进行处理,不需要再次编译,提高了性能。这一点mybatis 默认情况下,将对所有的 sql 进行预编译处理。

    • 预编译可以将多个操作步骤合并成一个步骤,一般而言,越复杂的sql,编译程度也会复杂,难度大,耗时,费性能,而预编译可以合并这些操作,预编译之后DBMS可以省去编译直接运行sql。
    • 预编译语句可以重复利用。
      把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的 PreparedState 对象。
  2. 是否自动加单引号

    1. #{} 对应的变量会自动加上单引号
    2. ${} 对应的变量不会加上单引号
  3. 安全性

    1. #{} 能防止sql注入
    2. ${} 不能防止sql注入
  4. Mybatis默认值不同

    1. #{} 默认值 arg0、arg1、arg2 或 0、 1
    2. ${} 默认值param1、param2、param3
  5. 如何选择 #{} 和 ${}

    1. 能用 #{} 的地方就用 #{},尽量少用 ${}
    2. 表名作参数,或者order by 排序时用 ${}
    3. 传参时参数使用@Param(“”)注解,@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值(相当于又加了一层密),正确的将参数传入sql语句中(一般通过#{}的方式,${}会有sql注入的问题)。

缓存

使用缓存, 我们可以避免频繁的与数据库进行交互, 尤其是在查询越多、缓存命中率越高的情况下, 使用缓存对性能的提高更明显。

mybatis 也提供了对缓存的支持, 分为一级缓存和二级缓存。 但是在默认的情况下, 只开启一级缓存(一级缓存是对同一个 SqlSession 而言的)。

缓存就是数据交换的缓冲区(称作Cache),是存贮数据(使用频繁的数据)的临时地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行。如果找不到,则去数据库中查找。

MyBatis 提供了一级缓存二级缓存的支持,默认情况下只有一级缓存

  1. 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush close 之后,该Session中的所有 Cache 就将清空

    增删改也会刷新缓存

    手动清理缓存:sqlSession.clearCache();

  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。 要启用全局的二级缓存,首先开启全局缓存:

    1
    2
    3
    4
    <settings>
    <!--显示的开启全剧缓存-->
    <setting name="cacheEnabled" value="true"/>
    </settings>

    然后只需要在你的 SQL 映射文件中添加一行:

    1
    <cache/>

    工作机制:

    1. 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    2. 如果当前会话关闭了,这个会话对应的一级缓存就没了;
    3. 会话关闭之后,一级缓存中的数据会被保存在二级缓存中;
    4. 新的会话查询消息,就会从二级缓存中获取内容;
    5. 不同的mapper查出 的数据会放在自己对应的缓存(map)中
  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

    提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

查看评论