1. 传统方式的源码剖析
1.1 初始化过程
// 1.读取配置文件,加载为字节输入流(还没解析)
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
// 2.解析配置文件,封装成Configuration对象;创建DefaultSqlSessionFactory对象
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 2.1 XML解析,创建DefaultSqlSessionFactory对象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder是专门解析Mybatis配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行XML解析,创建DefaultSqlSessionFactory对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 2.2 parser.parse解析
public Configuration parse() {
// 如果不是第一次解析,则抛出只能解析一次异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析XML的configuration节点(Mybatis配置文件的根节点)
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 2.3 具体的解析过程
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析<properties/>标签
propertiesElement(root.evalNode("properties"));
// 解析<settings/>标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 解析<typeAliases/>标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins/>标签
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
public SqlSessionFactory build(Configuration config) {
// 构建者设计模式
return new DefaultSqlSessionFactory(config);
}
1.2 sql执行流程
SqlSession介绍
- SqlSession是⼀个接⼝,它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager (弃⽤,不做介绍)。
SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀个SqlSession,并且在使⽤完毕后需要close
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
}
SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执⾏器
- 通过sqlsessionFactory生产了DefaultSqlSession实例对象,设置了事务不自动提交,完成了executor的创建 ```java SqlSession sqlSession = sqlSessionFactory.openSession();
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建executor final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException(“Error opening session. Cause: “ + e, e); } finally { ErrorContext.instance().reset(); } }
3. 根据statementId从configuration中获取到获取到MappedStatement对象对象,然后将查询交给executor执行器
```java
List<User> users = sqlSession.selectList("com.lagou.entity.User.findAll");
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取到MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- executor执行器查询,创建一级缓存的cacheKey,判断缓存是否存在,不存在则像数据库查询
```java
@Override
public
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 根据传入的参数动态获得sql语句,封装在BoundSql中 BoundSql boundSql = ms.getBoundSql(parameter); // 为本次查询创建缓存的key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
@Override
public
5. executor执行器像数据库查询
```java
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 缓存占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 把查询出来的数据加入一级缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
真正向数据库查询,执行jdbc的组件StatementHandler
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler通过ParameterHandler设置参数
- StatementHandler通过resultSetHandler进行返回结果集的处理
2. Mapper代理方式的源码剖析
2.1 getMapper
- 使用JDK动态代理对mapper接口产生代理对象
```java //DefaultSqlSession 中的 getMapper publicUserDao mapper = sqlSession.getMapper(UserDao.class);
T getMapper(Class type) { return configuration. getMapper(type, this); }
//configuration 中的给 getMapper
public
//MapperRegistry 中的 getMapper
public
- e, e); } }
//MapperProxyFactory 类中的 newInstance ⽅法
public T newInstance(SqlSession sqlSession) {
//创建了 JDK动态代理的Handler类
final MapperProxy
//MapperProxy 类,实现了 InvocationHandler 接⼝
public class MapperProxy
2. 通过代理对象调用方法时,都会执行MapperProxy中的invoke方法
<a name="ywXEt"></a>
## 3. 二级缓存源码剖析
1. 二级缓存的构建在一级缓存之上,收到查询请求时,先查询二级缓存,未命中再查询一级缓存,还未命中再查询数据库。二级缓存 ---》一级缓存 ----》数据库
2. sqlsession1查询完结果后,需要commit或者close结果才会被缓存到二级缓存。
```java
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = build.openSession();
SqlSession sqlSession2 = build.openSession();
List<User> users1 = sqlSession1.selectList("com.lagou.entity.User.findAll");
System.out.println(users1);
//如果不先commmit或者close那么二级缓存还没有储存结果
sqlSession1.commit();
List<User> users2 = sqlSession2.selectList("com.lagou.entity.User.findAll");
System.out.println(users2);
}
启用二级缓存(分三步走;第一步和第三步都是默认为true,所以可以只配置第二步
再sqlMapConfig.xml中开启全局二级缓存配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在Mapper中配置二级缓存标签
<cache></cache>
在具体CURD标签上配置 useCache=true
<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
select * from user where id = #{id}
</select>
解析
标签过程 查询过程解析:分析为什么收到查询请求时先走二级缓存再走一级缓存,两次查询之间需要commit一次二级缓存才会有数据
从sqlSession.selectList()入手
Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行executor的query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
当开启二级缓存时,executor的实现类不再是BaseExecutor,而是CachingExecutor ```java @Override public
List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建缓存的CacheKey CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
// 这个方法分析了如果二级缓存存在,新请求先查询二级缓存再查询一级缓存
@Override
public
3. 如上,注意⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中tcm 变量对应的类型。下⾯分析⼀下。
4. 上面代码的tcm实际上是TransactionalCacheManager类,维护了Cache与TransactionalCache的映射关系,TransactionalCache就是解决二级缓存的脏读问题的。TransactionalCache类维护的几个属性:
```java
// 实际的二级缓存,tcm.getObject()就是查询这个
private final Cache delegate;
private boolean clearOnCommit;
// 在事务未提交时,所有从数据库查询的结果存到此集合中,tcm.putObject()是先保存到这
private final Map<Object, Object> entriesToAddOnCommit;
// 在事务未提交时,当缓存未命中,Cachekey将缓存到此集合中
private final Set<Object> entriesMissedInCache;
- TransactionalCache的查询是直接从实际二级缓存delegate查的,存储的时候存的是entriesToAddOnCommit,这也就解释了为什么两个查询之间如果事务没有提交,二级缓存是不会命中的 ```java public Object getObject(Object key) { // issue #116 Object object = delegate.getObject(key); if (object == null) { entriesMissedInCache.add(key); } // issue #146 if (clearOnCommit) { return null; } else { return object; } }
@Override public void putObject(Object key, Object object) { entriesToAddOnCommit.put(key, object); }
// 执行了commit那么二级缓存才真正存了刚刚查询的结果 public void commit() { if (clearOnCommit) { delegate.clear(); } // 把entriesToAddOnCommit和entriesMissedInCache的值刷入delegate flushPendingEntries(); reset(); }
6. 二级缓存查询过程总结:在CachingExecutor的执行查询中,先通过TransactionalCacheManager类来维护Cache和TransactionalCache的映射关系,二级缓存的存储与获取实际是通过TransactionalCache类来进行的,里面维护了真正的二级缓存Cache,存储二级缓存的时候实际先放到TransactionalCache.entriesToAddOnCommit的一个map集合中,但是查询二级缓存的时候是直接通过TransactionalCache.delegate去查询的,所以这个二级缓存查询后,设置的缓存值没有立即生效,主要是直接存到delegate会产生脏读问题。
6. 二级缓存生效机制,只要看sqlSession的commit()方法做了什么
```java
@Override
public void commit(boolean force) {
try {
// 主要是这句
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction.
Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// CachingExecutor.commit()
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();// 在这⾥
}
// TransactionalCacheManager.commit()
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();// 在这⾥
}
}
// TransactionalCache.commit()
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();//这⼀句
reset();
}
// TransactionalCache.flushPendingEntries()
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 在这⾥真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,⼆
级缓存才真正的⽣效
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
- 二级缓存的刷新,主要看sqlSession的update方法
```java
public int update(String statement, Object parameter) {
int var4;
try {
} catch (Exception var8) {this.dirty = true;
MappedStatement ms = this.configuration.getMappedStatement(statement);
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} finally {throw ExceptionFactory.wrapException("Error updating database. Cause:
"
+ var8, var8);
} return var4; }ErrorContext.instance().reset();
public int update(MappedStatement ms, Object parameterObject) throws SQLException { this.flushCacheIfRequired(ms); return this.delegate.update(ms, parameterObject); }
private void flushCacheIfRequired(MappedStatement ms) { //获取MappedStatement对应的Cache,进⾏清空 Cache cache = ms.getCache(); //SQL需设置flushCache=”true” 才会执⾏清空 if (cache != null && ms.isFlushCacheRequired()) { this.tcm.clear(cache); } }
8. MyBatis⼆级缓存只适⽤于不常进⾏增、删、改的数据,⽐如国家⾏政区省市区街道数据。⼀但数据变更,MyBatis会清空缓存。因此⼆级缓存不适⽤于经常进⾏更新的数据。
9. 总结:在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的装饰器。
1. ⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
2. ⼆级缓存具有丰富的缓存策略。
3. ⼆级缓存可由多个装饰器,与基础缓存组合⽽成
4. ⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor和 ⼀个事务型预缓存TransactionalCache完成。
<a name="nS9BZ"></a>
## 4. 延迟加载源码分析
<a name="S6Cnp"></a>
### 4.1 什么是延迟加载
1. 延迟加载:就是需要用到数据的时候才去加载,不需要用到的数据就不去加载,也叫懒加载。
2. 优点:先从单表查询,需要时再去关联表查询,大大提高数据库性能,因为查询单表比查询多表快的多。
3. 缺点:因为用到数据时才去数据库查询,这样在大批量数据查询时,因为查询工作也需要消耗时间,所以可能造成用户等待时间过长,用户体验下降。
4. 在多表中:
1. 一对多,多对多通常建议延迟加载。
2. 一对一,多对一通常情况采用立即加载。
5. 注意:延迟加载是采用嵌套查询实现的。
<a name="M3bam"></a>
### 4.2 实现延迟加载
1. 局部延迟加载:在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
```xml
<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
-->
<collection property="orderList" ofType="order" column="id"
select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
</select>
全局延迟加载:在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
-
4.3 延迟加载的原理实现
它的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的 getting ⽅法时,进⼊拦截器⽅法。⽐如调⽤ a.getB().getName() ⽅法,进⼊拦截器的invoke(…) ⽅法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B对象的 SQL ,把 B 查询上来,然后调⽤ a.setB(b) ⽅法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理。
- 总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。