
MyBatis:持久层技术、ORM框架
回顾 JDBC开发
优点:简单易学,上手快, 非常灵活构建SQL,效率高
缺点:代码繁琐,难以写出高质量的代码(例如:资源的释放,SQL注入安全性等)
开发者既要写业务逻辑,又要写对象的创建和销毁,必须管底层具体数据库的语法
(例如:分页)。
- 适合于超大批量数据的操作,速度快
性能:jdbc(80%代码只完成20%的事) > mybatis > hibernate:(from Employee) > jpa
什么是mybatis,有什么特点
基于上述二种支持,我们需要在中间找到一个平衡点呢?结合它们的优点,摒弃它们的缺点,这就是myBatis,现今myBatis被广泛的企业所采用。
MyBatis,前身ibatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
持久层技术:
jdbc/dbutils/springDAO
ORM ( Object Relationship Mapping)框架:ORM 框架是持久化框架有mybatis、hibernate、springORM、Java Persistence API (JPA)、toplink、EJB3 EntityBean
1.准备Maven Pom.xml
在IntelliJ IDEA中配置 pom.xml 引入类库,这样在创建项目时直接引用类库即可。
1 | <!-- 常量声明 --> |
2. 创建POJO实体类
1 | // lombok类库 生成getter, setter, toString |
3. 创建 mybatis-config.xml配置文件
在 src/main/resources 目录下新建mybatis-config.xml 配置文件,内容如下:
(可选)配置相关属性:
<properties resource="jdbc.properties"/>引入连接信息(可选)配置实体类别名:
<typeAliases> ... </typeAliases>
配置连接池环境:
<environments default="..."> ... </environments>注册SQL Mapper映射文件:
jdbc.properties
1 | jdbc.driver=com.mysql.cj.jdbc.Driver |
mybatis-config.xml
1 |
|
**<typeAliases>**类型别名
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:
1 | <typeAliases> |
注意:
**<settings>**特性配置
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。具体配置项参考 Mybatis中文参考手册。
1 | <settings> |
注意:
4. 创建 SqlSessionFactory
通过加载核心配置文件创建SqlSessionFactory实例,SqlSessionFactory是一个应用程序内全局单一的实例,支持多线程安全操作
通过SqlSessionFactory 获得 SqlSession 操作数据库,所有的数据库操作通过SqlSession 完成。
1 | public static void main(String[] args) { |
5. 编写XML映射文件
SQL语句元素有:
- select
- insert
- update
- delete
- sql
Mybatis XML映射文件中可用来生成动态 SQL 的 XML 元素有:
- where
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
- bind
创建一个包:com.lanqiao.mapper
创建 XML 映射文件,编写sql语句
编写结果映射配置:
主要的XML元素:
5.1 **<mapper>**根元素
- namespace: 命名空间,通常是接口映射的全类名。
1 |
|
5.2 **<select> ** 与 **<where>**元素
<select>元素用来编写select语句。
<where>元素会生成一个 WHERE关键字。
<select resultType="POJO全类名">: 自动查询结果映射,要求结果集的列与实体类的属性名一致不区分大小写。<select resultMap="REF_ResultMap_ID">:手动结果映射,这种是推荐结果映射方式,这种方式比较灵活,而合适多表查询的结果映射。<bind name="变量名" value="">: 绑定生成新变量在后面用于
1 | <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> |
注意:以上where 条件中SAL <=#{sal} 的小于号是 XML 的无字符,需要放在XML CDATA 节点内,
<![CDATA[ AND SAL<=#{sal} ]]>
5.3 **<resultMap>**结果映射元素
<resultMap>元素用来将查询结果集映射到实体。<resultMap>内部有以下几个主要元素:
<id>元素专门用于映射主键字段- javaType: 映射的 Java 属性类型,可以mybatis 预定义的类型别名(在第 8 节)或类型的全类名。
- jdbcType: jdbc的类型名称(在第 7 节)。
- typeHandler:类型处理器。无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis 已定义了一些默认的类型处理器。
<result>元素用于映射普通字段。元素的属性与 元素的的属性一样。 <association>元素用于M:1多对一关联中映射 1方的实体<collection>元素用于1:M一对多关联中映射 M方的实体
1 | <resultMap id="BaseResultMap" type="com.lanqiao.domain.Employee"> |
5.3.1 **<association>**多对一关联映射
常用的两种实体关联映射有:
- M:1(多对一关联映射)
- 1:1(一对一关联映射)
实体的 M:1(多对一)关联中使用<association></association>映射,关联元素处理“有一个”类型的关系,如一个员工属于一个部门。
多对一关联有以下两种映射方式:
- (推荐)嵌套结果
使用嵌套结果映射来处理重复的联合结果的子集。首先让我们来查看这个元素的属性。所有的结果集你都会看到,它和普通的只由 select 和 resultMap 属性的结果映射不同。
嵌套结果要求使用一条SQL语句查询出实体与关联实体(关联表)的数据,效率好。
1 | <resultMap id="baseResultMap" type="Employee"> |
- 嵌套查询
嵌套查询 (性能差): 通过执行另外一个 SQL 映射语句来返回预期的复杂类型(JavaBean)。也就是说每个关联的实体都通过一条单独的 SQL来查询。
这种方式很简单, 但是对于大型数据集合和列表将不会表现很好。问题就是我们熟知的 “N+1 查询问题”。
(面试题)在mybatis的查询中什么是N+1次查询,即嵌套查询中的N+1问题:这种关联会发生N+1次查询问题。N指的是执行了多少条SQL语句获取关联实体,1指的是主查询。如下映射所示:
一条语句为查询员工实体:
select * from scott.emp你执行了一个单独的 SQL 语句来获取员工结果列表(就是“+1”)。
N条语句为查询每个员工关联的部门实体
对返回的每条记录, 你执行了一个查询语句来为每个实体加载细节(就是“N”)。
当一条语句查询出员工的结果集时,员工结果集中关联的每个部门 (10,20,30,40) 都会导致执行了N 条 SQL语句查询部门实体,已被查询出的部门实体会被缓存在mybatis的一级缓存 SqlSession中:
select * from scott.dept=10;select * from scott.dept=20;......1
2
3
4
5
6
7
8
9
10<resultMap id="baseResultMap_2" type="employee">
<!-- 使用单独的语句查询关联实体,column="deptno"员工表外键 -->
<association property="dept" select="selectDeptByPrimaryKey"
column="deptno"/>
</resultMap>
<select id="selectDeptByPrimaryKey" parameterType="int"
resultMap="deptResultMap">
select * from dept where deptno=#{id}
</select>
什么是N+1问题的第二个例子:
例如,假设有一个用户(User)和多个订单(Order)的关系,如果你先查询出所有用户,然后再对每个用户查询他们的订单,那么查询的次数将是用户数量加1。
如果用户先查询出所有用户,然后再对每个用户查询他们的订单,那么总的查询次数将是用户数量加1。这里的“加1”指的是最初查询所有用户的那一次查询。
接下来,我们分析为什么总的查询次数会是用户数量加1。
- 首次查询:首先,系统会执行一次查询来获取所有的用户。这是首次查询,也是“加1”中的“1”所代表的那一次查询。
- 后续查询:在获取了所有用户之后,系统需要对每个用户执行一次查询来获取他们的订单。因为用户数量是未知的,所以我们用“用户数量”来表示这部分的查询次数。
将首次查询和后续查询的次数相加,就得到了总的查询次数:用户数量 + 1。
5.3.2 **<collection>**一对多关联映射
1:M(一对多)关联使用<collection ofType="元素类型" column="外键字段"></collection>元素,要映射Many端结果到List集合中,我们使用集合元素。
映射集合也有两种方式:
1(推荐使用)嵌套结果
<collection property="属性名" ofType="集合元素的类型" column="外键列">
</collection> 结果映射引用了
EmployeeMapper.xml中的baseResultMap结果映射配置.1
2
3
4
5
6
7
8
9
10<resultMap id="baseResultMap" type="departemnt">
<id property="deptno" column="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="employeeList"
ofType="employee"
column="deptno"
resultMap="com.lanqiao.mapper.EmployeeMapper.baseResultMap">
</collection>
</resultMap>2. 嵌套查询
通过在
<collection></collection>元素上指定select="查询 Many端实体的语句"属性,该属性会使用当前实体的主键(Many端为外键)来执行另一条查询语句获取Many端实体,并将获取到的Many端实体存入集合中。
<collection property="属性名" ofType="属性类型" column="外键列" select="查询 Many端实体的语句">
</collection>
1 | <resultMap id="baseResultMap_2" type="departemnt"> |
优点:编写SQL简单,无需做多表的连接查询;关联的实体通过单独的SQL语句查询并单独封装。
缺点:执行了N+1条件语句。性能差
5.4 **<sql>**通用 SQL 片段
通用 SQL片段,可被其他语句引用的可重用语句块。
1 | <sql id="Base_Column_List"> |
5.5 **<if>**元素
<if>元素用以根据条件判断生成动态SQL。
1 | <where> |
5.6 **<choose>**元素
分支结构
1 | <!-- 类似 java的 switch --> |
5.7**<foreach>**元素
<foreach>元素通过遍历集合来产生动态 SQL。
- java 代码
1 |
|
- xml 映射
1 | <if test="deptnoList != null"> |
5.8**<trim>**元素
<trim> 元素用于删除空格或删除指定字符,并添加上指定的前缀字符 和 后缀字符。
1 | <trim prefix="加上前缀字符" suffix="加上的后缀字符" |
示例:用来前置SET关键字和清除最后的逗号
1 | <update id="update_2" parameterType="employee"> |
5.9**<update>**元素、set元素
<set>元素会前置一个SET关键字,同时会删除最后多余的逗号
1 | <update id="update" parameterType="employee"> |
5.10**<insert>**元素
insert语句没有resultMap,默认返回整数即插入的行数.
如果某些字段允许为 null 时,安全的做法就是对这样的列做 空值处理,这不是 mybaits 需要的,而是不同数据的驱动需要。
useGeneratedKeys="true": 说明主键值由数据库生成keyProperty="empno": 自动生成的主键值赋给 POJO 的哪个属性keyColumn="empno": 自动生成主键值的列
示例 1:使用 JDBC null 值处理
1 | <insert id="insert" parameterType="employee" useGeneratedKeys="true" keyProperty="empno" keyColumn="empno"> |
示例 2:使用<trim>元素
1 | <!-- 动态 insert语句,就是说页面过来有值的字段生成SQL,无值的字段不生成SQL --> |
6. 接口映射器
映射器接口执行SQL语句的规则:
XML 映射文件 *.xml的名字空间(namespace)必须和映射器接口全类名一致,必须放在与映射器接口相同的包下。
接口映射器类全名:
org.glut.mybatis.mapper.EmployeeMapper(名字空间)
- 接口映射器 Java
1 | package org.glut.mybatis.mapper; |
- XML映射文件
1 | <mapper namespace="org.glut.mybatis.mapper.EmployeeMapper"> |
selectByPrimaryKey(Integer empno)方法名对应 *.xml中的语句ID
1 | <select id="selectByPrimaryKey" ....> |
selectByPrimaryKey(Integer empno)参数类型对应SQL语句所需的参数类型
1 | <select parameterType="int" ....> |
Employee selectByPrimaryKey(Integer empno)返回值 对应 SQL语句的查询结果类型
1 | <select resultType="Employee" ....> |
如果是 insert,update,delete 操作那么方法声明返回 int 类型
1 | int insert(Employee parameter); |
- 在
mybatis-config.xml配置文件中配置接口映射器
1 | <mappers> |
- 在程序中使用接口映射器
1 | EmployeeMapper mapper = SqlSession.getMapper(EmployeeMapper.class); |
7.支持的 JDBC 类型
为了未来的参考,MyBatis 通过包含的 jdbcType枚举型,支持下面的 JDBC 类型。
java.sql.Types中定义的常量。
| BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
|---|---|---|---|---|---|
| TINYINT | REAL | VARCHAR | BINARY | BLOG | NVARCHAR |
| SMALLINT | DOUBLE | LONGVARCHAR | VARBINARY | CLOB | NCHAR |
| INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
| BIGINT | DECIMAL | TIME | NULL | CURSOR | ARRAY |
8. 支持的 Java类型
mybatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理“带下划线”。
| 别名 | 映射的Java类型 |
|---|---|
| _byte | byte |
| _long | long |
| _short | short |
| _int | int |
| _integer | int |
| _double | double |
| _float | float |
| _boolean | boolean |
| string | String |
| byte | Byte |
| long | Long |
| short | Short |
| int | Integer |
| integer | Integer |
| double | Double |
| float | Float |
| boolean | Boolean |
| date | Date |
| decimal | BigDecimal |
| bigdecimal | BigDecimal |
| object | Object |
| map | Map |
| hashmap | HashMap |
| list | List |
| arraylist | ArrayList |
| collection | Collection |
| iterator | Iterator |
9. #{expr}与 ${expr} 的区别
1 | #{ename}: mybatis会将该表达解析成预译语句的 ?号(占位符)。JDBC中的preparedStatement的Set方法 |
1 | and ename like '${ename}' #生成--> and ename like '%J%' |
10. SqlSession 持久化操作
1 | SqlSession.selectList("语句ID" [, parameter]); |
parameter 参数对象为调用 SQL语句时所需的占位符参数,一般为 POJO对象。
11.(重要)mybatis工作流程
通过
Reader对象读取mybatis-config.xml配置文件(该文本的位置和名字可任意)通过
SqlSessionFactoryBuilder对象创建SqlSessionFactory(类似javax.sql.DataSource)对象。
注:如果要在应用程序中使用二级缓存那么可利用第三方的缓存(ehcache等),mybatis提供集成方案。
- 从
SqlSessionFactory中获取SqlSession(封装了java.sql.Connection)对象,将sqlSession称为一级缓存。
说明:
SessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。
SqlSessionFactory
SqlSessionFactory的缓存称为:二级(全局)缓存
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用程序作用域级。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
SqlSession的缓存称为:一级(线程级、事务级)缓存
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
事务开始,在mybatis中事务是默认开启的, 但自动提交是关闭的。
通过
SqlSession对象读取EmployeeMapper.xml映射文件中的操作语句ID,从而读取sql语句。事务提交。
sqlSession.commit(): 提交事务sqlSession.rollback():回滚事务
关闭
SqlSession对象,释放数据库连接,提高数据库连接的重用性。让mybatis能响应更多的用户请求。1
2
3
4
5//使用try-finally 结构自动关闭 SqlSession
try ( SqlSession sqlSession = MybatisUtil.getSqlSession() ) {
} catch (Exception e) {
}
12. 二级缓存配置
MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)_直接修改数据库数据会使mybatis一级缓存和二级缓存的使用场景失效吗-CSDN博客

咱们得知道,做数据查询是可以进行缓存的。注意,是查询,dml语句没办法缓存的吧?
SqlSession级的缓存称为一级缓存、事务级缓存、线程级缓存。
SqlSessionFactory级的缓存称为二级缓存、应用级缓存、全局缓存。Mybatis除了自身提供二级缓存的实现外,同时也提供对第三方缓存的支持,如:Redis, Ehcache, Memory Cache等。
12.1 修改mybatis-config.xml
默认二级缓存是开启的,如果要启用二级缓存,需要在mybatis-config.xml文件中加入下面设置:
1 | <settings> |
12.2 在SQL Mapper映射文件中
1 | <!-- 缓存实体,此元素只对当前名字空间内的实体查询有效 --> |
映射语句文件中的所有
select语句将会被缓存。映射语句文件中的所有
insert,update和delete语句会刷新缓存。缓存会使用 Least Recently Used (LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
可用的收回策略(eviction)有:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU收回策略。
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
12.3 实体类实现java.io.Serializable接口
如果二级缓存想要命中实现,则必须要将上一次sqlSession commit之后才能生效
缓存实体,只对当前名字空间内的实体查询有效。默认数据被缓存在java.io.tmpdir (JVM的一个属性)
注意:
多表连接查询时关联实体不做缓存
mybatis的缓存是基于SQL语句id(namespace + select_id)做标识的
查询后要调用SqlSession.commit(); 数据才会被缓存





