目录
- mybatis的基本概念
- mybatis如何构建和执行的
- mybatis的缓存
- mybatis的插件系统
- mybatis的日志系统
- mybatis用到的设计模式
- myabtis集成到spring
- mybatis集成springboot自动化配置
1. mybatis的基本概念
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
上面是mybatis官方介绍,从介绍我们可以得知mybatis有以下特点:
- 它是一个持久化框架
- 它支持sql、存储过程、高级映射
- 它支持手动设置参数并且分装结果集
- 它支持xml和注解两种配置方式
以下为mybatis内的一些基本概念:
- SqlSessionFactory:SqlSession类的工厂类
- SqlSession:数据库会话类,为用户提供数据库操作方法
- Executor:数据库操作的执行器,SqlSession通过Executor操作数据库
- MappedStatement:是一个sql操作的抽象
- 映射接口:具体的业务模块接口,映射接口不需要有实现类,接口内定义了一些列方法,每个方法对应一个sql操作,方法名就是sql操作的id
- 映射文件:当配置方式为xml时,可以将sql写在xml配置文件中,一个映射文件对应一个映射接口
- Cache:mybatis内部缓存实现
- Configuration:全局配置信息(以及配置信息解析的结果)存放处,该实例全局共享,该实例是SqlSessionFactory的属性
2. mybatis如何构建和执行的
那mybatis是如果构建和执行的呢,先看一个小例子(这里以xml配置方式为例):
- 创建一个maven项目
- 引入mybatis和mysql连接工具依赖
org.mybatis
mybatis
3.4.5
mysql
mysql-connector-java
6.0.6
3.编写mybatis配置文件
/p>
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
4.编写映射接口
public interface UserMapper { List selectUser();}
5.编写映射xml文件(resources/mapper/UserMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?> select * from user
6.编写启动类
public class App { public static void main(String[] args) throws Exception { SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream resource = Resources.getResourceAsStream("config.xml"); SqlSessionFactory sessionFactory = factoryBuilder.build(resource); SqlSession sqlSession = sessionFactory.openSession(); /* 这里通过jdk的动态代理获取UserMapper接口的代理类 */ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List list = userMapper.selectUser(); System.out.println(list.size()); }}
以上就是搭建纯mybatis运行环境的过程,程序配置过程不详述,这里说一下mybatis的启动构建和执行过程。
- 先是创建SqlSessionFactoryBuilder实例,改实例的唯一作用就是用来构建SqlSessionFactory的,一但创建了SqlSessionFactory实例SqlSessionFactoryBuilder实例就没用了。构建SqlSessionFactory的过程如下:
- 加载mybatis配置文件
- (XMLConfigBuilder.parse)解析配置文件:解析过程是将xml配置文件内的所有配置标签都解析并包括
- 解析每个标签调用不同的方法处理该标签的配置,例如解析标签是会把内配置的所有映射记录解析将mapper记录添加到Configuration的MapperRegistry中去,并且将对应mapper配置文件里的所有的sql操作解析成MapperStatement(XMLMapperBuilder.parse),同时也会解析resultMap和缓存配置。
- 解析xml配置文件最终会将所有配置信息放到Configuration实例中去,该实例是全局共享的,后续获取Mapper接口代理、获取MapperStatement、获取Executor都会从这个Configuration实例中获取。
- 解析完之后创建DefaultSqlSessionFactory实例,这里创建DefaultSqlSessionFactory实例比价简单就是调用一个参数为Configuration的构造函数即可,因为所有的信息都已经存放到Configuration实例中去了
- 获取SqlSession会话对象,调用SqlSessionFactory.open()方法即可,该方法最终会调用SqlSessionFactory.openSessionFromDataSource方法根据Configuration配置信息创建一个SqlSession实例。
- 有了SqlSession实例后,获取映射接口的代理类,例如这里的sqlSession.getMapper(UserMapper.class),这里其实就是通过jdk的动态代理获取得到UserMapper接口的代理类,实际代理的InvocationHandler是MapperProxy,在MapperProxy.invoke方法中会拦截映射接口的方法调用,然后创建(可能会被缓存)MapperMethod实例通过执行MapperMethod.execute方法执行sql操作,接着会调用SqlSession内的一系列方法如selectList、insert、query等,根据调用的接口和方法组合的全限定名例如:com.test.UserMapper.getUser来获取MappedStatement,最后通过Executor来作sql的操作(当然其内部也有些封装执行操作,详情可看Executor的实现类BaseExecutor、CachingExecutor的源码)。
- Executor执行sql的操作的过程,会将sql执行的结果例如是insert、update、delete操作会返回执行的影响的条数,如果是query操作会将结果封装成对应的sql配置文件配置的类型(如pojo类型、map、resultMap等)返回List或者单个对象并返回。这里mybatis大量使用了范型。
以上就是Mybatis大致的启动构建和执行过程,只能将主要的节点描述,很多细节还需阅读源码。
下图为mybatis启动示意图:
3. mybatis的缓存
mybatis内置了两种缓存,一种是一级缓存(默认开启),一种是二级缓存(默认开启),一级缓存是会话级别的也就是一个SqlSession实例内的缓存,而二级缓存是namespace级别的,所谓namespace就是一个映射接口的范围,也就是说如果开启了二级缓存那么多个会话如果调用的是同一个映射接口那么是有可能命中二级缓存的。下面详细描述。
- 一级缓存:在上一部分我们知道对于SqlSession里的一系列操作方法,实际上最终会调用Executor执行器内的方法来进行sql操作,Executor在mybatis种提供了几个实现类,在不开启二级缓存的情况下默认使用SimpleExecutor实现类,SimpleExecutor是集成的BaseExecutor抽象类,大部分的方法已在BaseExecutor实现,我们关注BaseExecutor,当作查询操作的时候最终会执行BaseExecutor.query方法,在BaseExecutor类的152行有这样的代码list = resultHandler == null ? (List) localCache.getObject(key) : null;这里就是一级缓存实现的地方,即一级缓存是保存在BaseExecutor内部属性localCache中,而localCache其实就是个PerpetualCache而该类是mybatis缓存的一个实现类,下钻到PerpetualCache内可以发现其内部有个类型为Map的cache属性其中key为CacheKey值就是查询结果。当执行了update、commit等方法后一级缓存会被清空。我们可以看到,一级缓存只提供了简单的缓存更新的策略,如果使用一个SqlSession实例作同一个查询不管查询多少此其结果都不会变,这就有可能出现脏数据,所以需要斟酌使用一级缓存,如果对数据实时性要求高可以在mybatis配置文件配置标签里设置来关闭一级缓存。
- 二级缓存:二级缓存是默认开启的,如果要关闭可以在mybatis配置文件配置标签里设置,开启二级缓存后SqlSession内的Executor为CachingExecutor,实际CachingExecutor是使用装饰器模式将包了一层,具体sql操作委托给其他的Executor执行(其实默认是委托给SimpleExecutor),CachingExecutor只做二级缓存的处理。源码CachingExecutor第95行,在执行查询之前先从MappedStatement中获取cache(如果对应mapper映射文件中未配置那么此处的cache是空的,其实这里的cache在mybatis启动构建解析配置文件的时候就已经创建好了,这个cache实例是和namespace一一对应的)。如果部位空那么就从cache中获取值。但是这里不是直接从cache中获取值而是通过CacheExecutor内部的TransactionalCacheManager来获取,之所以这样是为了保证事务成功或失败后缓存的正常保存和清理。例如这里如果开启二级缓存做一次查询其实没发真正保存缓存,此时缓存是保存在TransactionalCache中的,TransactionalCache内保存了所有本次事务操作需有需要缓存的值,只有调用SqlSession.commit方法后将commit传递到TransactionalCache.commit才能真正保存缓存到namespace的cache实例中。在作insert、update、delete时二级缓存也会被清除,想比一级缓存二级缓存有淘汰策略,默认策略上LRU(淘汰最急最少使用),可以在映射配置文件的配置标签中自定义,除此之外还有:
- FIFO:先进先出:按对象进入缓存的顺序来移除它们
- SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
- 例如:
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- Cache:Cache是mybatis在一二级缓存是对缓存的抽象,Cache接口有一系列的实现类,这些实现类使用装饰器模式来实现对不能缓存功能的包装和功能叠加。
4. mybatis的插件系统
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
以上是官方的对plugin的介绍,本质上plugin在sql操作的执行周期中作用的,可以作用的点包括Executor、ParameterHandler、ResultSetHandler、StatementHandler内部的一系列方法。mybatis通过动态代理实现对作用点前后的自定义操作。在Configuration中有个interceptorChain属性,即插件作用链,在Configuration中newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor这些方法都会调用InterceptorChain.pluginAll方法通过动态代理的方式将每个插件穿起来,生成插件动态代理链是通过插件工具类Plugin来实现,调用Plugin.wrap这个静态方法来创建代理类,代理InvocationHandler类就是Plugin(Plugin本身实现了InvocationHandler接口),当然在创建插件代理类的过程中还会判断插件类的签名信息即插件类的@Intercepts注解配置信息,该配置信息里配置了该插件的作用点(实际上就是作用的函数调用点)。例如我们想把查询出来为List类型的结果内部的Map字段转成驼峰形式(如:user_name转成userName)我们可以使用插件来实现。
@Intercepts({@Signature(
type= ResultSetHandler.class,
method = "handleResultSets