不定期补充、修正、更新;欢迎大家讨论和指正
目录
- 传统的JDBC
- MyBatis
-
- 基本使用
-
- 传参问题
- 全局配置文件
- 结果映射
-
- 多表查询
- 动态SQL
-
- if标签
- choose标签
- foreach标签
- 缓存
-
- 一级缓存
- 二级缓存
- 自定义缓存
- 源码分析
- 整合Spring
- MyBatisPlus
传统的JDBC
回顾以前数据库的连接和操作,我们使用的是原生的Java AP来实现,大致的步骤为
- 配置账号、密码、Driver、url成功连接数据库
- 通过链接Connection获取PreparedStatement类
- 编写SQL语句,将SQL语句放入PreparedStatement中预编译
- 填充占位符
- 执行SQL语句,如果是查询SQL,还需要用ResultSet类来接收结果集
- 关闭资源等收尾工作
以下表为例

插入
@Override
public void insert(Jobs jobs) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JdbcUtils.getConnection();//JdbcUtils是自己封装的工具类,里面包含获取连接,关闭连接的功能
//就是上一篇博客中CURD->insert->封装 的getconnection()和closeResource()方法
String sql = "INSERT INTO `jobs`(`job_id`,`job_title`,`min_salary`,`max_salary`)VALUE(?,?,?,?);";
ps = conn.prepareStatement(sql);//预编译
ps.setString(1,jobs.getJob_id());//填充占位符
ps.setString(2,jobs.getJob_title());
ps.setInt(3,jobs.getMin_salary());
ps.setInt(4,jobs.getMax_salary());
ps.execute();//执行
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.closeResource(conn,ps);//关闭连接
}
}
删除
@Override
public void deleteByID(String ID) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JdbcUtils.getConnection();
String sql = "DELETE FROM `jobs` WHERE `job_id`=?;";
ps = conn.prepareStatement(sql);
ps.setString(1,ID);
ps.execute();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.closeResource(conn,ps);
}
}
查询
@Override
public Jobs getByID(String ID) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "SELECT * FROM `jobs` WHERE `job_id` = ?;";
ps = conn.prepareStatement(sql);
ps.setString(1,ID);
rs = ps.executeQuery();
if (rs.next()) {
Jobs jobs = new Jobs();
jobs.setJob_id(rs.getString(1));
jobs.setJob_title(rs.getString(2));
jobs.setMin_salary(rs.getInt(3));
jobs.setMax_salary(rs.getInt(4));
return jobs;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.closeResource(conn,ps,rs);
}
return null;
在使用传统的JDBC中,我们可以发现诸多不便
-
CURD很多步骤相同,连接数据库,获取PreparedStatement等。
解决方法:提出公共部分作于模板
-
将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。
解决方法:利用反射从外部文件获取SQL语句。
- 对于查询语句,接收结果集的过程十分繁琐
-
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能
解决方法:利用数据库连接池
尽管有一些简化的工具比如Dbutils和JDBCTemplate等,但能做的也很有限。
随后市面上就出现了许多持久层框架来简化开发和操作,如MyBatis、Hibernate、TopLink、Guzz、jOOQ、Spring Data。如今MyBatis占主流,同时也是主流MVC框架——SSM(Spring + SpringMVC + MyBatis)重要的一环。
MyBatis
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
–摘自官网
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
–摘自百度百科
MyBatis官方帮助文档
基本使用
导入Mybatis
jar包:mybatis
Maven依赖
<!-- Maven -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
<!-- 之前用的3.4.6执行后会抛出一堆Warning,查了之后说要降级jdk或者升级mybatis的版本 3.5.5亲测没有这些警告-->
</dependency>
mysql驱动
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
根据官方所说,SqlSessionFactory是整个MyBatis的核心,SqlSessionFactory通过SqlSessionFactoryBuilder获取,而SqlSessionFactoryBuilder可以通过XML配置文件获取,以下为官方给出的XML配置文件模板(配置文件名根据规范一般为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>
<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="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--将mapper注册到mybatis中,后面会讲,很重要的一步,先注释掉-->
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
接下来就可以获取SqlSessionFactory实例了,通过SqlSessionFactory可以获取SqlSession实例,而SqlSession才是真正与数据库交互的东西。
为了后续方便使用,可以写成工具类
package com.jojo.mybatis;
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;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
在测试前先把主配置文件中的mapper映射注释掉,因为这是官方给的例子,我们并没有该mapper需要映射,否则会报错
成功获取SqlSession,但不代表能成功连接到数据库,就算连接数据库的账号密码是错误的,在这时也不会提示
利用SqlSession向数据库做CURD操作,仍以下表为例

ORM(对象关系映射),创建实体类,用于后面接收结果集
按照原生JDBC,接下来就是写SQL语句了,MyBatis提供在XML中编写SQL语句的形式
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
- namespece:命名空间。在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。命名空间在之前版本的MyBatis中是可选的,这样容易引起混淆因此毫无益处。现在命名空间则是必须的,且易于简单地用更长的完完全限定名来隔离语句。
- id:全局唯一不解释
- resultType:接收结果集的类
- #{id}:占位符以该形式编写
命名空间是为了防止id冲突,这里简单使用完全不怕,但既然是必须的,就先创建一个接口
接下来很重要的一步,将做CURD的mapper在主配置文件中注册
在这里很容易遇到找不到该mapper路径的异常,因为Maven一般只读取resource下的xml文件,而mapper一般是放在src目录下
因此需要在pom.xml添加以下配置
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
接下来就可以通过sqlSession向数据库进行CURD操作了
因为getById标签目前全局唯一,直接获取即可
为了避免标签冲突,安全的做法是先获取命名空间所绑定的接口
这样不仅更安全,可读性好、传参也方便
经过简单的使用,可以发现MyBatis很好地解决了原生JDBC的弊端:SQL语句在外部文件中编写,降低耦合性、结果集的接收MyBatis自动完成,只需要指定接收结果集的类即可,使得我们可以专注于SQL语句的构造。
接下来把剩下的CURD写完,不过在实现之前导入log4j日志功能,我们只能通过SqlSession是否操作数据库成功,但是如果出错,仅仅通过SqlSession很难进行排错。
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
导入依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
创建log4j的配置文件(log4j.properties,最好放在resource下)
这里是从Log4j的配置与使用详解直接扒下来的
# Global logging configuration
# 设置日志输出级别以及输出目的地,可以设置多个输出目的地,开发环境下,日志级别要设置成DEBUG或者ERROR
# 前面写日志级别,逗号后面写输出目的地:我自己下面设置的目的地相对应,以逗号分开
# log4j.rootLogger = [level],appenderName1,appenderName2,…
log4j.rootLogger=DEBUG,CONSOLE,LOGFILE
#### 控制台输出 ####
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 输出到控制台
log4j.appender.CONSOLE.Target = System.out
# 指定控制台输出日志级别
log4j.appender.CONSOLE.Threshold = DEBUG
# 默认值是 true, 表示是否立即输出
log4j.appender.CONSOLE.ImmediateFlush = true
# 设置编码方式
log4j.appender.CONSOLE.Encoding = UTF-8
# 日志输出布局
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p (%c:%L) - %m%n
#### 输出错误信息到文件 ####
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
# 指定输出文件路径
#log4j.appender.LOGFILE.File =F://Intellij idea/logs/error.log
log4j.appender.LOGFILE.File =./logs/error.log
#日志输出到文件,默认为true
log4j.appender.LOGFILE.Append = true
# 指定输出日志级别
log4j.appender.LOGFILE.Threshold = ERROR
# 是否立即输出,默认值是 true,
log4j.appender.LOGFILE.ImmediateFlush = true
# 设置编码方式
log4j.appender.LOGFILE.Encoding = UTF-8
# 日志输出布局
log4j.appender.LOGFILE.layout = org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.LOGFILE.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
有了日志功能,可以了解到SQL完整的执行过程,方便调试
在接口制定CURD方法
插入
对于增删改操作,SqlSession默认是不会自动提交事务的,需要手动提交
更改
删除
查询全部
查询多个结果,虽然返回值是集合,但在SQL语句返回值设置为集合中元素的类型就行
传参问题
细心的朋友可以发现在以上CURD传参的形式都有所不同
在MyBatis中,当传参只有一个时,在SQL语句写的参数名可以随意
比如删除操作,因为不会产生歧义,名字怎么取都没关系
当传的参数有多个时,可以传map,SQL语句的#{}中的变量名需要和map中的key相同
也可以通过Java Bean传递多个参数,SQL语句的#{}中的变量名和实体类的属性相同
还有是使用@param注解的形式,这里不进行演示
MyBatis(四)SQL语句中参数传递的五种方法
还有需要注意的是 ${},#{}的区别
在很多地方,${} 的作用是直接取值,这种方式容易产生SQL注入的危险(以预编译的方式将参数设置到sql中)
而#{} 运行结果会是一个?占位符 ,和使用PreparedStatement目的一致
当然${} 并不是一无是处,可以用于分库分表排序等原生jdbc不支持占位符的地方,例如
select * from ${month}_salary order by ${name}
全局配置文件
全局配置文件的属性设置,完全可以看官方文档
mybatis-配置
这里使用几个常用的
属性(properties)
主要用来获取外部文件的内容,比如连接数据库的信息可以单独放到另一个文件,然后通过properties引入
也可以直接在标签内设置,但是优先级还是文件中的高
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
比如之前mapper用到类时写的是全路径比较长
取别名
当需要某个包下有很多类需要取别名时,可以以包为单位为其下的所有类取别名
类的别名为类名(无视大小写)
环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境,所以要在< environments default=“development” >标签内选择具体的环境
每套环境内都要设置transactionManager和dataSource,具体内容自己看
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。自行了解,后续使用时会提到。
结果映射
在前面select语句中,对于复杂的结果集我们常用JavaBean 或 POJO来接收,也就是resultType=‘JavaBean’,虽然看似是ResultMap完成了结果集的映射,但实际上是ResultMap完成了这些事。MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
什么时候用ResultMap呢,当数据库的字段和javaBean属性字段不一样时就可以使用(数据库字段命名一般为job_id,而Java是驼峰式命名)
之前属性名和数据库字段是一一对应的,所以直接可以用resultType接收
假如改成驼峰式命名,我们来看看还能接收到结果集吗
可以看到能查询到结果,但是没接收到结果集
这种情况下容易解决,我们可以在全局配置文件中的setting标签内开启驼峰命名自动映射
也可以利用SQL语法中的as取别名(别忘了关掉驼峰命名自动映射,不然不知道有没有成功)
还有一种方法就是用resultMap作映射了
resultMap标签有三个属性
这里使用两个子标签就足够了(id和result的作用一样)
在数据库字段和属性名一一对应或者驼峰转换后对应,直接用resultType就行,用resultMap就多次一举了
但是对于多表查询就不得不用resultMap了,在此之前先了解resultMap标签内所有的属性
多表查询
在先前我们已经有了一张job表
现在根据job_id与另一张employee表进行多表查询(为了方便点,这里只考虑以下四个属性,以及job表的job_title属性)
而这两张表结合呈现一对多的关系(以job表为主表,一个job可以有多个employee),而反过来就是多对一的关系(以employee为主表),因此使用resultMap时要对两种关系分别进行实现。
构造实体类
因为job表可以查出多个employees,所以要为其添加一个列表类型的属性
重写下Employees的toString方法,方便后面查看
多对一
接下来先以简单的多对一关系进行实现,以下面查询结果为例
映射文件,在主配置文件注册这些常规流程就不说了
先构造select语句
再构造resultMap,因为查询的主表是Employees,所以type为Employees
这段没什么好说的,都是Employees的属性,按照普通的列名-属性名进行映射就行
重点是association标签,关联(association)元素处理“有一个”类型的关系
association标签下又包含四个属性,不过只用关心property和javaType
简单来说
property:你想将另一张表查询的结果集放到哪个属性下,还记得在Employees表下的Jobs jobs属性吗,所以这里property=’jobs’
javaType:接收另一张表结果集的JavaBean
接下来只要完成另一张表自己的列名-属性名的映射就行了
test
查询成功
在该例中,我们完全可以将多表查询等价为嵌套子查询,即先查询jobs表得到结果,在此结果上再查询employees表
因此我们可以修改成另一种形式
实现查询jobs表和employees的SQL语句,jobs直接返回结果,employees表还需要进行映射处理
同样重点在于association标签
property和javaType和上面一样
column和select的功能如下
column:连接键的列名,这里column是必选项,不像前面可以略去,因为要知道通过哪个共同的键进行查询
select:也是必选,不然怎么获得嵌套查询的结果
同样成功查询
一对多
当jobs作为主表时,通过job_id查询employees,显然构成了一对多的关系,但是大部分操作都相似,连SQL语句都是一样的
接下来是关键点resultMap,association标签用于处理“有一个”类型的关联,而处理“有很多个”类型的关联就需要用到collection集合标签了,collection标签的属性和association标签大部分相同,需要注意的是collection返回的类型,因为返回的是List,所以javaType得是ArrayList类型(可忽略),而ofType填才是List中元素的类型
在jobs表中实现打印方法(本来不用这么麻烦,但是只取了几个字段,其他字段就不打印了)
成功获取
关于多表查询的结果映射还是比较令人头大,可以参考以下视频,以及强烈建议参考官方文档
【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂
动态SQL
动态sql是指在进行sql操作的时候,传入的参数对象或者参数值,根据匹配的条件,有可能需要动态的去判断是否为空,循环,拼接等情况,是比较常用的功能。
比如在一个页面中的表单有几个传输参数(学到MyBatis应该都学过JaveWeb了)
根据传输的参数动态地构造SQL语句
执行SQL语句
原文:JDBC实现动态查询
可以发现使用原生的JDBC十分的繁琐,而且还容易漏掉空格逗号造成错误,因此MyBatis提供了强大的动态SQL,让我们摆脱这种麻烦。
if标签
if标签是比较常用的功能,最常见情景是根据条件包含 where 子句的一部分
比如想查询工资大于minSalary,小于maxSalary范围的字段
仅传入minSalary
根据日志信息可以看到成功拼接了SQL语句(maxSalary为空)
在此基础上查询工资低于20000的
其实这里写的拼接语句有很大的漏洞,假如minSalary为空但maxSalary不为空
就会拼接成错误的SQL语句导致报错
一种解决方法是在主SQL中添加where 1=1(保证后续的语句肯定执行),拼接SQL统一用逻辑连接符开头
这样SQL语句就不会出错了
另一种方法是使用trim标签,这个就自己去官网看。
choose标签
choose标签和swith-case语句类似,只会进入一个分支
比如我们可以根据job_id或job_title查询字段,如果一个属性存在,另一个就属性就不使用
为了规范点,以后查询条件都放在where标签内
只用job_title查询(when标签)
同时用两个属性查询,可以看到当排在前面的分支符合后,就算后面有其他分支符合条件也不会进入
默认情况(otherwise标签)
foreach标签
foreach常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
foreach使用难点比if和choose复杂些,主要在于参数的选择,foreach标签内有以下几个参数
- item:表示集合中每一个元素进行迭代时的别名。(必选)
- index:指定一个名字,表示在迭代过程中每次迭代到的位置。(可选)
- open:表示该语句以什么开始(如果是 in 条件语句,以’('开始 )(可选)
- separator:表示在每次进行迭代之间以什么符号作为分隔符(如果是 in 条件语句,以’,'作为分隔符)(可选)
- close:表示该语句以什么结束(如果 in 条件语句,以’)'开始 )(可选)
使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
- 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
- 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
- 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
- 摘自MyBatis foreach标签
比如传入List,通过多个job_id查询多个字段
另外提一下,我们知道形参不同就可以函数重载,但在mybatis这是不允许的,因为标签id必须得唯一
Mybatis的XxxMapper.xml中能否配置重载方法
Mybatis的mapper接口函数重载问题
缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率
MyBatis默认定义了两级缓存:一级缓存和二级缓存
一级缓存
一级缓存是默认开启的(SqlSession级别的缓存,也称为本地缓存)
如下,当我们先后分别查询同一字段,因为第一次查询的结构仍在缓存中,所以并不需要再次从数据库查询,大大增加了高并发的效率,因为我们知道数据从内存中读取远比从磁盘IO读取来的快
演示需要用到日志功能,这里不使用log4j,使用MyBatis自带的日志功能换换口味,在主配置文件的setting标签开启就行
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
一级缓存在几种情况下会失效,官方的给的文档说到select语句的结果会被缓存,但是insert、update、delete语句会刷新缓存
如下,在两次查询同一字段中,我们还更新了字段,尽管更新的字段和要查询的字段无关,MyBatis为了保证ACID原则也会再次从数据库查询
对此我们可以看各个标签内的属性,对于select语句,默认使用缓存
对于增删改操作,没有useCache属性,并且flushCache默认为true,所以一执行就会刷新缓存
二级缓存
二级缓存也叫全局缓存,是基于namespace级别的缓存,一个命名空间对应一个缓存(二级缓存并不是来解决一级缓存失效的问题的)
一个会话查询一个缓存,查询的数据会存放在当前会话的一级缓存中,如果该会话关闭,其缓存也会消除。
但我们可以使其保存在二级缓存中,新的会话查询就可以从二级缓存读取,不同的mapper查出的数据会放在自己对应的缓存中
要启用全局的二级缓存,需要现在主配置文件开启。
因为二级缓存的作用域是namespace,也就是mapper映射文件,所以哪个映射文件需要二级缓存,在该mapper映射文件中添加< cache >即可
该标签下有五个属性
- eviction(清除策略):缓存清空策略,默认为LRU
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:即队列,按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
- flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
- size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
- readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
- type(自定义缓存)除了上述自定义缓存的方式,也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
创建两个会话,在第一次会话关闭后,仍然查询相同的字段,很明显可以看到两次查询的对象是相同的。
关闭二级缓存后
自定义缓存
MyBatis毕竟不是专门做缓存的,所以在一些复杂的情况下就需要用其他更强大的缓存,比如EhCache、Redis。自行了解
MyBatis自定义缓存——Redis实现
MyBatis自定义缓存——EhCache实现
源码分析
MyBatis底层源码解析 (详细)
整合Spring
目前来说,Mybatis常与Spring、SpringMVC一起组成SSM框架,也是目前主流的MVC框架来完成一个JavaWeb项目的开发,因此学习整合Mybatis和Spring(SpringMVC是Spring下的模块,整合Spring就行)是十分必要的。
使用 Spring IoC 可以有效的管理各类的 Java 资源,达到即插即拔的功能;通过 Spring AOP 框架,数据库事务可以委托给 Spring 管理,消除很大一部分的事务代码,配合 MyBatis 的高灵活、可配置、可优化 SQL 等特性,完全可以构建高性能的大型网站。
毫无疑问,MyBatis 和 Spring 两大框架已经成了 Java 互联网技术主流框架组合,它们经受住了大数据量和大批量请求的考验,在互联网系统中得到了广泛的应用。使用 MyBatis-Spring 使得业务层和模型层得到了更好的分离,与此同时,在 Spring 环境中使用 MyBatis 也更加简单,节省了不少代码,甚至可以不用 SqlSessionFactory、 SqlSession 等对象,因为 MyBatis-Spring 为我们封装了它们。
–《Java EE 互联网轻量级框架整合开发》
篇幅原因这里不进行实现
Spring:
Java学习_Spring_IoC
Java学习_Spring_AOP
MyBatis-Spring
MyBatis 与 Spring 整合
MyBatisPlus
- 占坑