一.Mybatis的前世今生
Mybatis的前身是Apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为Mybatis。2013年11月迁移到Github,目前Mybatis由Github维护。
Mybatis是一个基于Java的持久层框架,它几乎能做到JDBC所能做到的所有事情,并(在注意一些规则的基础上)可以完成自动映射,而无需再写任何的映射规则,大大提高了开发效率和灵活性。
二.Mybatis的基本构成
- SqlSessionFactoryBuilder:它会根据配置信息或者代码生成SqlSessionFactory
- SqlSessionFactory: 依靠工厂来生成SqlSession
- SqlSession:
非线程安全;
获取Mapper,让Mapper通过命名空间(namespace)和方法名称找到对应SQL,发送给数据库执行后返回结果。
直接通过命名信息去执行SQL返回结果(iBatis版本留下的方式)
- SQL Mapper:它是由一个Java接口和XML文件(或注解)构成的,需要给出对应的SQL和映射规则。它负责发送SQL语句,并返回结果
三.SqlSession构成与运行原理
SqlSession下有四大对象,Mapper的执行就是它们来完成数据库操作和结果返回的。
- Executor代表执行器,由它来调度StatementHandler、ParameterHandler、ResultHandler等来执行对应的SQL.
- StatementHandler的作用是使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用。
- ParameterHandler用于SQL对参数的处理。
- ResultHandler是进行最后数据集(ResultSet)的封装返回处理的。
Executor会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一些基本运行的参数。然后用parameterize()方法启用ParameterHandler设置参数,完成预编译。然后就执行select或update,如果是select,还会用ResultHandler封装结果返回给调用者。
四.插件
Mybatis可以拦截SqlSession下四大对象的任意一个,根据功能来确定需要拦截什么对象。
Executor是执行SQL的全过程,包括组装参数,组装结果集返回和执行SQL过程,都可以拦截,较为广泛,一般用得不算多。
StatementHandler是执行SQL的过程,我们可以重写执行SQL的过程。这是我们最常用的拦截对象。
ParameterHandler主要是拦截执行SQL的参数组装,可以重写组装参数规则。
ResultHandler用于拦截执行结果的组装,可以重写组装结果的规则。
#稍后DEMO
五.常规操作
Generator
Mapper,POJO,XML
配置
Configuration(#DEMO TypeHandler)
六.批处理
用foreach实现(#DEMO)
foreach主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有item,index,collection,open,separator,close。
item表示集合中每一个元素进行迭代时的别名,
index指定一个名字,用于表示在迭代过程中,每次迭代到的位置,
open表示该语句以什么开始,
separator表示在每次进行迭代之间以什么符号作为分隔符,
close表示以什么结束,
collection:在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
- 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
- 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
- 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key
下面分别来看看上述三种情况的示例代码:
1) 单参数List的类型:
<select id="dynamicForeachTest" resultType="Blog"> #{item} |
上述collection的值为list,对应的Mapper代码如下:
public List<Blog> dynamicForeachTest(List<Integer> ids); |
2)单参数array数组的类型:
<select id="dynamicForeach2Test" resultType="Blog"> |
上述collection为array,对应的Mapper代码如下:
public List<Blog> dynamicForeach2Test(int[] ids); |
3)自己把参数封装成Map的类型
<select id="dynamicForeach3Test" resultType="Blog"> |
上述collection的值为ids,是传入的参数Map的key,对应的Mapper代码如下:
public List<Blog> dynamicForeach3Test(Map<String, Object> params); |
七.缓存
全局开关:默认是true,如果它配成false,其余各个Mapper XML文件配成支持cache也没用。
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
7.1一级缓存
如果没将上述cacheEnabled的值显式设置为false, Mybatis默认会开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
当执行SQL时两次查询中间发生了增删改操作,则SqlSession的缓存清空。
7.2 二级缓存
二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。
各个Mapper XML文件,默认是不采用cache。在配置文件加一行就可以支持cache:
<cache />
在同一个namespace下的mapper文件中,执行相同的查询SQL,第一次会去查询数据库,并写到缓存中;第二次直接从缓存中取。当执行SQL时两次查询中间发生了增删改操作,则二级缓存清空。
7.3 开启二级缓存
Mybatis默认开启一级缓存。
二级缓存的开启如下:
1、 在配置文件中开启缓存总开关(cacheEnabled设置为true,默认为true,不设置也行):
2、在映射(mapper)文件中,加入cache配置,开启二级缓存:
注意1,由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。如果该类存在父类,那么父类也要实现序列化。
注意2,Mapper XML文件配置支持cache后,文件中所有的Mapper statement就支持了。此时若要个别对待某条statement禁用二级缓存,该statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询。默认情况下是useCache=true,即该statement使用二级缓存。
7.4 注意事项
1) 只能在【只有单表操作】的表上使用缓存
不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace
下。
2) 在可以保证查询远远大于insert,update,delete操作的情况下使用缓存
因为insert,update,delete操作会清空所在namespace
下的全部缓存
7.5 此时请避免用二级缓存
在符合上述【2.4 注意事项】的要求时,使用二级缓存并没有什么危害。但是,请记住以下三点特性:
- 缓存是以
namespace
为单位的,不同namespace
下的操作互不影响。 - insert,update,delete操作会清空所在
namespace
下的全部缓存。 - 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的
namespace
。
以下两种情况请不要使用二级缓存:
Case1:针对一个表的某些操作不在其独立的namespace
下进行。
例如在UserMapper.xml
中有大多数针对user
表的操作。但是在一个XxxMapper.xml
中,还有针对user
单表的操作。
这会导致user
在两个命名空间下的数据不一致。如果在UserMapper.xml
中做了刷新缓存的操作,在XxxMapper.xml
中缓存仍然有效,如果有针对user
的单表查询,使用缓存的结果可能会不正确。
更危险的情况是在XxxMapper.xml
做了insert,update,delete操作时,会导致UserMapper.xml
中的各种操作充满未知和风险。
Case2:多表操作一定不能使用缓存
首先不管多表操作写到哪个namespace
下,都会存在某个表不在这个namespace
下的情况。
例如两个表:role
和user_role
,如果我想查询出某个用户的全部角色role
,就会涉及到多表的操作。
<select id="selectUserRoles" resultType="UserRoleVO">
select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>
不管是写到RoleMapper.xml
还是UserRoleMapper.xml
,或者是一个独立的XxxMapper.xml
中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
如果user_role和role的Mapper都使用同一个namespace(通过<cache-ref>)来避免脏数据,那就失去了缓存的意义。
注意:
1、在SqlSession未关闭之前,如果对于同样条件进行重复查询,此时采用的是local session cache,而不是上面说的二级cache。
2、MyBatis缓存查询到的结果集对象,而非结果集数据,是将映射的PO对象集合缓存起来。
7.6 Advices
如果不符合上述【7.4 注意事项】的要求时,在业务层使用可控制的缓存代替。
根据项目/业务实际情况,在set入缓存的时候配置合理的缓存失效时间,提高缓存的利用率。
八.未完待续
- MySQL数据库开发规范:http://10.0.50.99:8090/pages/viewpage.action?pageId=360989
- Java以及MySQL开发规范(阿里巴巴版本):http://10.0.50.99:8090/pages/viewpage.action?pageId=360999