简介

基本介绍

官网文档

  • MyBatis 是一款优秀的持久层框架,它支持 自定义 SQL、存储过程以及高级映射
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML或注解 来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失

ORM 思想

ORM 是指(Object Relationship Mapping)对象关系映射

  • 对象:Java 的实体类对象
  • 关系:关系型数据库
  • 映射:二者之间的对应关系
Java概念 数据库概念
属性 字段/列
对象 记录/行

日志

日志工厂

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j (3.5.9 起废弃)
  • JDK logging

    MyBatis 内置日志工厂基于运行时自省机制选择合适的日志工具。它会使用第一个查找得到的工具(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志工具,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择别的日志工具。

1
2
3
4
5
6
7
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>

配置实例

导入依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

log4j.properties

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
# 将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file 的定义在下面的代码
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=%d%p[%c]-%m%n

# 文件输出的相关设置
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./logs/server.log
log4j.appender.file.MaxFileSize=10000KB
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{MM-dd HH:mm:ss.SSS}[%24F:%-3L:%-5p]%x %m%n

# 日志 输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG

log4j.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}%m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>

mybatis-config.xml

1
2
3
4
5
6
7
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>

动态SQL

if

if 标签通过 test属性 给出判断的条件,如果条件成立,则将执行标签内的 SQL 语句

1
2
3
4
5
6
7
8
9
10
11
<select id="queryBlogIf" parameterType="map" resultType="blog">
SELECT * FROM blog
<where>
<if test="title != null">
AND title = #{title}
</if>
<if test="author != null">
AND author = #{author}
</if>
</where>
</select>

choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

当某个 when 标签的条件满足时将对应的 SQL 语句返回,如果都不满足并且有 otherwise 标签时,才会返回 otherwise 标签中的 SQL 语句

where

考虑 if 标签中的范例出现的一种情况:当 第一个 if 标签条件不成立而第二个条件成立时,拼接成的 SQL 语句中 where 后面连着的是 and,会造成 SQL 语句语法错误,而 where 标签可以解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName != null and empName != ''">
emp_name = #{empName}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
</where>
</select>

where 标签 只会在子标签返回任何内容的情况下才插入 WHERE 子句。而且,若子句的开头有多余的 and 或者 or,where 标签也会将它们去除,但是子句末尾的 and 或者 or 不能去除

trim

trim 标签用于 去掉或添加 标签中的内容

属性 说明
prefix 在trim标签中的内容的前面添加某些内容
prefixOverrides 在trim标签中的内容的前面去掉某些内容
suffix 在trim标签中的内容的后面添加某些内容
suffixOverrides 在trim标签中的内容的后面去掉某些内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" prefixOverrides="and">
<if test="empName != null and empName != ''">
emp_name = #{empName}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
</trim>
</select>

set

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateBlog" parameterType="map">
UPDATE blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
WHERE id = #{id}
</update>

sql

sql 元素可以用来 定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值

  • 基于 单表 定义 SQL 片段
  • 不能存在 where 标签

  • 用于记录一段通用的 SQL 语句片段,在需要用到该 SQL 语句片段的地方中通过 include 标签将该 SQL 语句片段插入

  • sql 标签通过 id 属性唯一标识一个 SQL 语句片段,include 标签通过 refid 属性指定使用某个 SQL 片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<sql id="if-title-author">
<if test="title != null">
AND title = #{title}
</if>
<if test="author != null">
AND author = #{author}
</if>
</sql>

<select id="queryBlogIf" parameterType="map" resultType="blog">
SELECT * FROM blog
<where>
<include refid="if-title-author"/>
</where>
</select>

foreach

它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。

它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符

可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach

  • 当使用 可迭代对象或者数组 时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。

  • 当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

foreach 标签可以用的属性

属性 说明
collection 指定需要遍历的集合或数组
item 当前遍历到的元素(可迭代对象或者数组);值(Map对象)
index 当前遍历到的元素的序号(可迭代对象或者数组);键(Map对象)
open 指定遍历开始前添加的字符串
close 指定遍历开始后添加的字符串
separator 指定每次遍历之间的分隔符

collection 属性值注意事项

  • 如果遍历的是 List 时,属性值为 list
  • 如果遍历的是数组时,属性值为 array
  • 如果遍历的是 Map 时,属性值可以是 map.keys()map.values()map.entrySet()
  • 除此之外,还可以在映射方法的参数中使用 @Param() 注解自定义 collection 属性值

批量添加数据

1
2
3
4
5
6
<insert id="addMoreEmp">
insert into t_emp values
<foreach collection="list" separator="," item="emp">
(null,#{emp.empName},#{emp.age},#{emp.gender},null)
</foreach>
</insert>

批量删除数据

1
2
3
4
5
6
<delete id="deleteMoreEmp">
delete from t_emp where emp_id in
<foreach collection="array" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>

SELECT * FROM blog WHERE 1=1 AND (id=1 OR id = 2 OR id = 3)

1
2
3
4
5
6
7
8
9
<!--SELECT * FROM blog WHERE 1=1 AND (id=1 OR id = 2 OR id = 3)-->
<select id="queryForeach" parameterType="map" resultType="blog">
SELECT * FROM blog
<where>
<foreach collection="ids" item="id" open="AND (" close=")" separator="OR">
id = #{id}
</foreach>
</where>
</select>

Java API

Resources

Resources 类由 MyBatis 提供用于获取来自核心配置文件的输入流

InputStream getResourceAsStream(String filepath),注意这是一个静态方法

SqlSessionFactoryBuilder

由 MyBatis 提供用于获取 SqlSessionFactory 的实例对象,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的

1
2
3
4
5
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
  1. 第一种方法是最常用的,它接受一个指向 XML 文件的 InputStream 实例。可选的参数是 environment 和 properties。environment 决定加载哪种环境,包括数据源和事务管理器
  2. 如果你调用了带 environment 参数的 build 方法,那么 MyBatis 将使用该环境对应的配置。当然,如果你指定了一个无效的环境,会收到错误。如果你调用了不带 environment 参数的 build 方法,那么就会使用默认的环境配置(在上面的示例中,通过 default=”development” 指定了默认环境)。
  3. 如果你调用了接受 properties 实例的方法,那么 MyBatis 就会加载这些属性,并在配置中提供使用。绝大多数场合下,可以用 ${propName} 形式引用这些配置值。

属性优先级: 如果一个属性存在于下面的多个位置,那么 MyBatis 将按照以下顺序来加载它们:

  • 首先,读取在 properties 元素体中指定的属性;
  • 其次,读取在 properties 元素的类路径 resource 或 url 指定的属性,且会覆盖已经指定了的重复属性;
  • 最后,读取作为方法参数传递的属性,且会覆盖已经从 properties 元素体和 resource 或 url 属性中加载了的重复属性。

    通过方法参数传递的属性的优先级最高,resource 或 url 指定的属性优先级中等,在 properties 元素体中指定的属性优先级最低。

SqlSessionFactory

由MyBatis提供用于获取SqlSession对象,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的

1
2
3
4
5
6
7
8
9
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:

  • 事务作用域将会开启(也就是不自动提交)。
  • 将由当前环境配置的 DataSource 实例中获取 Connection 对象。
  • 事务隔离级别将会使用驱动或数据源的默认设置。
  • 预处理语句不会被复用,也不会批量处理更新
参数 说明
autoCommit true 值即可开启自动提交功能
Connection 若要使用自己的 Connection 实例,传递一个 Connection 实例给 connection 参数即可
TransactionIsolationLevel 事务隔离级别支持 JDBC 的五个隔离级别(NONEREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE),并且与预期的行为一致。
ExecutorType.SIMPLE 该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。
ExecutorType.REUSE 该类型的执行器会复用预处理语句
ExecutorType.BATCH 该类型的执行器会批量执行所有更新语句

SqlSession

SqlSession 类由 MyBatis 提供用于执行 SQL、管理事务、接口代理,它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。

使用映射器

1
<T> T getMapper(Class<T> type)

SqlSession 还有很多有关数据库增删改查的方法,但是这些方法繁琐而且不符合类型安全,所以使用 getMapper() 方法来获取一个 Mapper 接口的代理实现类来执行映射语句是个比较方便的做法

事务控制方法

1
2
3
4
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)

默认情况下 MyBatis 不会自动提交事务,如果你没有使用这些方法提交修改,那么你可以在 commit 和 rollback 方法参数中传入 true 值,来保证事务被正常提交(注意,在自动提交模式或者使用了外部事务管理器的情况下,设置 force 值对 session 无效)。大部分情况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。不过,当你要在一个可能多次提交或回滚的 session 中详细控制事务,回滚操作就派上用场了。

确保 SqlSession 被关闭

1
void close()

清空本地缓存

1
void clearCache() 

生命周期和作用域

SqlSessionFactoryBuilder

一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是局部方法变量)

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的范围是请求或方法范围

  • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
  • 绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SqlSessionUtil {

private static SqlSessionFactory sqlSessionFactory;

static{
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}

public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}

缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制 。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,需要在你的 SQL 映射文件中添加一行:

1
<cache/>

一级缓存

一级缓存是 SqlSession级别 的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,一级缓存是 默认开启的

一级缓存失效的四种情况:

  • 使用另一个 SqlSession
  • 同一个 SqlSession 但是查询条件不同
  • 同一个 SqlSession 但是两次查询中间执行了任何一次增删改操作
  • 同一个 SqlSession 但是两次查询中间手动清空了缓存,手动清空缓存的方法是调用 SqlSession 的 clearCache() 方法

二级缓存

二级缓存是 SqlSessionFactory 级别,通过 同一个 SqlSessionFactory 创建 的 SqlSession 查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取

二级缓存 开启 的条件:

  • 在核心配置文件中,设置全局配置属性 cacheEnabled="true",默认为true,不需要设置
  • 在映射文件中设置标签 <cache/>;
  • 二级缓存 必须 在 SqlSession 关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

二级缓存 失效 的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

<cache/>可以设置的一些属性:

eviction 属性:缓存回收策略,默认的是 LRU

缓存回收策略 说明
LRU(Least Recently Used) 最近最少使用的:移除最长时间不被使用的对象
FIFO(First in First out) 先进先出:按对象进入缓存的顺序来移除它们
SOFT – 软引用 移除基于垃圾回收器状态和软引用规则的对象
WEAK – 弱引用 更积极地移除基于垃圾收集器状态和弱引用规则的对象

flushInterval 属性:刷新间隔,单位是毫秒,默认情况下不设置也就是没有刷新间隔,缓存仅仅调用语句时刷新

size 属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出

readOnly 属性:只读, 取值是true/false

  1. true:只读缓存,会给所有调用者返回 缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势
  2. false:读写缓存,会返回 缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此 默认是false

缓存顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用

  2. 如果二级缓存没有命中,再查询一级缓存

  3. 如果一级缓存也没有命中,则查询数据库

  4. 注意 SqlSession 关闭之后,一级缓存中的数据才会写入二级缓存