MyBatis解决的核心问题:
1:使用连接池对连接进行管理
2:SQL和代码分离,集中管理
3:结果集映射
4:参数映射和动态SQL
5:重复SQL的提取
6:缓存机制
7:插件管理
SqlSessionFactoryBuilder:用于构建SqlSessionFactory,SqlSessionFactory只需要一个,所以生命周期存在方法的局部
SqlSessionFactory:用于构建SqlSession,存在于整个生命周期中,值需要一个实例来创建,采用单例模式
SqlSession:是一个会话,不是线程安全的,生命周期在一次请求或者操作中
Mapper:实际上是一个代理对象,生命周期在方法级别上
常用标签:
Configuration:跟标签
properties:配置参数信息,可以引用外部配置文件利于提取和多处引用,配置文件在外部避免修改后重新打包,只需重启应用,程序和配置分离,提升数据安全性
settings
cacheEnabled:是否使用缓存,整个工程中所有映射器配置缓存的开关,全局缓存开关,默认值true
aggressiveLazyLoading:是否需要侵入式延迟加载,开启时候无论调用什么方法加载某个对象时候都会加载该对象的所有属性,关闭后按需加载,默认false
defaultExecutorType:默认执行器,总共有三种执行器,SIMPLE,REUSE,BATCH(默认SIMPLE)
lazyLoadTriggerMethods:指定哪个方法触发一次延迟加载,配置需要触发延迟加载的方法的名字,该方法就会触发一次延迟加载(逗号分隔的方法名称列表)[equals,clone,hashCode,toString默认值]
localCacheScope:Mybatis利用本地缓存机制防止循环引用和加速重复嵌套查询,默认值为Session,这种情况下会缓存一个会话中执行的所有查询,如果设置值为STATEMENT,本地会话仅在语句执行上,对相同SqlSession的不同调用将不会共享数据[Session和Statement]
logImpl:日志实现
typeAliases:类型别名
typeHandler:Java类型和JDBC类型的对照表,Mybatis已经默认实现多种转换策略
objectFactory:当数据库返回的结果集转换成实体类时候,需要创建对象的实例,ObjectFactory有一个默认的实现DefaultObjectFactory,采用反射方式生成对象
创建DefaultResultSetHandler时候和创建对象时候调用objectFacotry.create(),在调用getRowValue()方法时候进行了属性覆盖,返回结果时候先创建对象,然后进行类型转换(先ObjectFactory后TypeHandler)
plugins:插件机制可以拦截Executor(update,query,flushStatements,commit等方法),ParameterHandler,ResultSetHandler,StatementHandler[MyBatis四大对象]
environments,environment:用于管理数据库环境,一个environment就是一个数据源,代表一个数据库
transactionManager:如果配置的是JDBC则会使用Connection对象的commit(),rollback(),close()管理事务,如果配置成MANAGED则会将事务交给容器来管理
dataSource:与Spring集成时候,事务和数据源都会交给Spring进行管理
mapper:Mapper.xml的路径,目的是让MyBatis在启动时候去扫描这些映射器,创建映射关系
Mapper.xml中映射文件配置:
cahce---给命名空间的缓存配置(是否开启二级缓存)
cache-ref---其他命名空间缓存配置的引用
resultMap
ParameterMap
insert
update
delete
select
MyBatis的动态标签
if,choose(when,otherwise),trim(where set).foreach
一对一的关联查询两种配置方式
1:嵌套结果,resultMap中嵌套另一个对象的结果
2:嵌套查询,分两次查询,我们只执行了一次查询SQL,如果返回了N条记录,那么就会再发送N条数据到数据库查询关联的SQL,这个就是所谓的N+1问题,解决这个问题就使用懒加载
lazyLoadingEnabled决定了是否开启延迟加载
aggressiveLazyLoading决定了是不是对象的所有方法都会触发查询
使用proxyFactory配置一个代理工具(JavaAsist或者CGLIB)
当开启了延迟加载开关,返回对象变成了代理对象
MyBatis整体流程:
MyBatis工作层次:
接口层:核心对象是SqlSession,是上层应用和MyBatis的桥梁,定义了众多的数据库的操作方法,在收到请求后调用核心层来完成具体操作流程
核心层:主要处理下列几件事
1:将接口传入的参数解析成JDBC类型
2:解析XML文件中的SQL语句,包括插入参数,和动态SQL的生成
3:执行SQL语句
4:处理结果集,映射成Java对象
基础支持层:
抽取出通用的功能实现复用,例如IO,事务,缓存,日志等
缓存:所有缓存总体上分为三类,基本缓存,淘汰算法缓存,装饰器缓存
MyBatis有一个缓存默认的实现类PerpetualCache,使用HashMap实现,默认使用LRU策略的缓存
一级缓存:一级缓存在一个会话(SqlSession)中,默认开启,将localCacheScope=STATEMENT将关闭一级缓存
1:一级缓存在同一个session中共享,不同session不共享
2:同一个会话中,update(delete)会导致一级缓存清空,select时候添加flushCache=true也会清空一级缓存
3:其他会话更新了数据,会导致读取到过时的数据(一级缓存不能跨会话共享)
不足:一级缓存不能跨会话共享,不同的会话之间对于相同的数据可能会有不同的缓存,在多个会话或者分布式环境下会存在查到过期数据的问题,解决办法可以使用二级缓存
二级缓存:解决一级缓存不能跨会话共享问题,范围是namespace级别,可以被多个session共享
MyBatis使用了一个装饰器来维护二级缓存CachingExecutor,如果启用了二级缓存则会对Executor进行装饰
开启二级缓存方式:
1:在mybatis-config.xml中配置cacheEnabled=true(默认开启)
2:每个Mapper.xml中配置<cache/>标签,如果为配置该标签,此时会对Executor进行装饰,但是没有cache对象不会走二级缓存,如果某些查询有实时性要求可以添加useCache=fasle
3:事务不提交,二级缓存不生效
第三方缓存做二级缓存:redis-cache,ehcache
源码分析:
SqlSessionFactory sqlSessionFactory=SqlSessionFactoryBuilder().build(inputStream);
SqlSession session=sqlSessionFactory.openSession();
BlogMapper mapper=session.getMapper(BlogMapper.class);
Blog blog=mapper.selectBolgById();
1:创建一个工厂类,配置文件在此解析(包括mybatis-config.xml和Mapper.xml映射器文件)
解析时候做了什么?产生了什么对象?解析结果存放在哪里?
获取Configuration,里面存放全部的配置信息
返回一个DefaultSqlSessionFactory,里面持有Configuration实例
2:通过SqlSessionFactory创建了一个SqlSession
a:首先创建Transaction如何使JDBC则使用Connection对象的commit().rollback() close()管理事务,如果是MANAGE则不会有任何事务
b:创建Executor--->缓存装饰CachingExecutor---->插件代理
c:返回一个SqlSession实现类
SqlSession定义了各种API提供给客户端调用,返回了什么实现类?除了创建SqlSession还创建了什么?
返回了一个DefaultSqlSession,除了SqlSession还创建了各种包装的执行器Executor,Executor是SQL实际的执行对象
3:获取了一个Mapper对象
Mapper是一个接口,没有实现类,不能被实例化,那获取到的Mapper是什么对象?为什么从SqlSession中获取?为什么传入一个接口还要用一个接口类型来接收
返回的Mapper是一个JDK动态代理对象,
4:调用方法
接口没有实现类,为什么可以调用方法?调用的是什么方法?
接口返回的是代理类,调用时候直接执行代理类中的invoke()方法,然后执行对应mapper.xml中的SQL语句,得到返回结果
总结:
MyBatis核心对象
Configuration---->包含MyBatis所有的配置信息
MapperRegistry TypeAliasRegistry TypeHandlerRegistry
SqlSession------->对数据库操作的API进行了封装,提供给应用层使用
SqlSessionFactory DefaultSqlSession
Executor----------->MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存
BaseExecutor SimpleExecutor BatchExecutor ReuseExecutor
StatementHandler----->封装了JDBC Statement操作,负责对JDBC Statement的操作,如设置参数,Statement结果转换成List结合
BaseStatementHandler SimpleStatementHandler PreparedStatementHandler CallableStatementHandler
ParameterHandler----->将用户传递的参数转换成JDBC Statement所需要的参数
DefaultParameterHandler
ResultSetHandler------>将JDBC返回的ResultSet结果集转换成List类型的集合
DefaultResultSetHandler
MapperProxy----------->触发管理类,用于代理Mapper接口方法
MapperProxyFactory
MappedStatement------>MappedStatement维护了一条<select|update|insert|delete>节点的封装,表示一条SQL,包括了SQL信息,入参,出参
SqlSource BoundSql
MyBatis插件:
MyBatis可以拦截的对象:Executor StatementHandler ParameterHandler ResultSetHandler
Executor是先被装饰后再被代理
插件实现原理:
1:代理类何时被创建
SqlSessionFacatory.openSession()---> Configuration.newExecutor()---->InterceptorChain.pluginAll()--->interceptor.plugin()
执行此方法时候创建了代理类,InterceptorChain中的数据是在解析时候将对应的拦截器加入到InterceptorChain中[
pluginElement(root.evalNode("plugins"));
]
执行interceptor.plugin()方法时候调用对应拦截器,在对于拦截器的plugin()方法中创建了代理对象(Plugin.wrap(trarget,this))
2:被代理后的执行流程
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
如果方法被拦截到,则会执行invoke()方法,然后执行interceptor.intercept(new Invocation(target, method, args));也就是我们自己定义的interceptor()方法,这里使用了Invocation类来进行简化操作,它提供了了proceed()方法来执行后续操作
插件的配置顺序和执行顺序:
插件定义顺序:插件1--->插件2---->插件3
代理顺序: 插件1--->插件2---->插件3
代理执行顺序:插件3--->插件2---->插件1
与Spring的整合:
在Spring中我们除了引用MyBatis的依赖之外还要引入MyBatis和Spring整合的jar包(mybatis-spring),通过此包将MyBatis整合到Spring中,所有它只是对MyBatis做了一些封装并没有替换MyBatis的核心对象
1:SqlSessionFactory哪里创建?
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {}
SqlSessionFactoryBean实现了三个接口FactoryBean,InitializingBean,ApplicationListener
InitializingBean-->在afterPropertiesSet()方法中执行了buildSqlSessionFactory()方法,此方法中创建了一个Configuration对象,创建用于全局配置解析的XMLConfigBuilder对象,解析完配置文件后返回一个DefaultSqlSessionFactory对象
FactoryBean--->可以让用户自定义实例化Bean的逻辑,在获取SqlSessionFactoryBean的时候会调用getObject()方法返回DefaultSqlSessionFactory
ApplicationListener--->监控应用发出的一些事件通知,此类中监听了ContextRefreshedEvent(上下文刷新事件),会在Spring容器加载完后执行,检查statements是否加载完毕
2:SqlSession哪里创建?
此时我们获取到了SqlSessionFactory,可以通过openSession()方式获取DefaultSqlSession,但是DefaultSqlSession是线程不安全的,如果每次都通过openSession()方式获取SqlSession又很麻烦,所以在mybatis-spring包中提供给了一个线程安全的SqlSession包装类SqlSessionTemplate
private final SqlSession sqlSessionProxy;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
session = sessionFactory.openSession(executorType);
通过JDK动态代理获取到DefaultSqlSession,MyBatis还自带一个线程安全的类SqlSessionManager
Spring提供了一个SqlSessionTemplate包装了用来替代直接获取DefaultSqlSession,那么如何获取SqlSessionTemplate?
在使用MyBatis时候需要定义一个@MapperScan("")注解,此注解上有个@Import(MapperScannerRegistrar.class),用于引入其他类,MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,会调用registerBeanDefinitions()方法,扫描对应包下的所有接口后执行scanner.doScan(StringUtils.toStringArray(basePackages));在执行processBeanDefinitions()方法时候会修改Bean的定义definition.setBeanClass(this.mapperFactoryBean.getClass());此时所有的Mapper接口在容器中被注册成了一个支持泛型的MapperFactoryBean了
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}
MapperFactoryBean继承了SqlSessionDaoSupport类,而SqlSessionDaoSupport中持有SqlSession的引用;同时该类也实现了FactoryBean接口,获取此类时候会执行getObject()方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
最终调用DefaultSqlSession的getMapper()方法
Spring集成MyBatis总结:
1:提供了SqlSession的替代品SqlSessionTemplate,里面实现了InvocationHandler的内部SqlSessoinInterceptor,本质上是对SqlSession的代理
2:提供了获取SqlSessionTemplate的抽象类SqlSessioinDaoSupport
3:扫描Mapper接口,注册到容器中的是MapperFactoryBean,该类继承SqlSessionDaoSupport,可以获取SqlSessionTemplate
4:Mapper注入的时候调用getObject()方法,实际上是调用了SqlSessionTemplate的getMapper()方法,注入了一个JDK动态代理
5:执行Mapper接口的任意方法会走到触发管理类MapperProxy进入SQL处理流程