1、读取MyBatis配置文件,构造SqlSessionFactory (实际默认实现是DefaultSqlSessionFactory )
String configLocation= "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(configLocation);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2、在SqlSessionFactoryBuilder中构造SqlSessionFactory,最终返回的是SqlSessionFactory的实现类DefaultSqlSessionFactory
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
下面方法有几步:
1、先用MyBatis配置文件对应的输入流(inputStream)构造一个XMLConfigBuilder的实例(parser )
2、用parser来进一步解析MyBatis配置文件来给对应的Java类Configuration实例填充属性
3、用Configuration实例作为参数构造一个DefaultSqlSessionFactory实例作为整个方法的返回
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用MyBatis配置文件来构造XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 1、parser.parse()==》解析MyBatis配置文件,转化为对应的Java类Configuration的实例
// 2、 build(parser.parse())==》实际执行的是new DefaultSqlSessionFactory(config),构造并返回一个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.
}
}
}
3、最后在DefaultSqlSessionFactory中调用openSession()中生成SqlSession,最终调用到openSessionFromDataSource,实际返回的是DefaultSqlSession。
@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);
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();
}
}
4、执行SQL
SqlSession session = sqlSessionFactory.openSession();
Demo demo = (Demo) session.selectOne("com.insomsia.dao.mapper.DemoMapper.selectDemo");
session.close();
selectOne方法实现如下:
@Override
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
实际调用的是selectList:
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 1、根据statement获取到要执行的SQL,statement就是你查询时传入的SQLID
MappedStatement ms = configuration.getMappedStatement(statement);
// 2、用执行器去执行相应SQL并返回结果
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();
}
}
看下MappedStatement ms = configuration.getMappedStatement(statement)的实现逻辑:
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
可以看到主要实现逻辑在public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements)方法中。而重点又在mappedStatements.get(id)这一句。
mappedStatements是configuration的一个属性,类型为StrictMap,key为String的SQLID,继承自HashMap,value为要执行的SQL体对应的Java类实例。
程序在解析mapper文件时会先将各个SQL节点的内容以key-value的方式缓存到mappedStatements中:
public V put(String key, V value) {
// 是否有相同key已存在,即是否有相同SQLID的值已存在
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key);
}
// 如果输入的是namespace+SQLID形式的,也就是SQLID的全路径,类似com.insomsia.dao.mapper.DemoMapper.selectDemo这种
if (key.contains(".")) {
// 以.来划分,把SQLID(不包含namespace,即selectDemo)截出来
final String shortKey = getShortName(key);
// 如果mappedStatements中还没有这个shortKey
if (super.get(shortKey) == null) {
// 把shortKey和value以key-value的形式存到mappedStatements里面去
super.put(shortKey, value);
} else {
// 若mappedStatements中已有shortKey,将shortKey和new Ambiguity(shortKey)以key-value的形式存入map
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
// 只要key在mappedStatements中不存在,则把key和value以key-value的形式存到mappedStatements里面去
// 所以是用全路径SQLID(namespace+SQLID)和短路径SQLID(不包含namespace)存了两次?
return super.put(key, value);
}
然后在执行SQL的时候,再以输入的SQLID作为key,从mappedStatements取出对应的mappedStatement:
public V get(Object key) {
// 获取要要执行的SQL对应的Java类实例
V value = super.get(key);
// 为空抛出异常
if (value == null) {
throw new IllegalArgumentException(name + " does not contain value for " + key);
}
// 若类型为Ambiguity(也就是在put时发现已有shortKey时存入的value)则抛出异常
if (value instanceof Ambiguity) {
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
}
return value;
}
可能有人会问为什么要这么设计?为什么不在put时发现已有重复shortKey的时候就抛出异常呢,非要等到真正执行SQL才抛出异常?
个人觉得可能是为了程序的兼容性和可执行性吧。
比如现在有这么一种场景,两张数据库表A和B,对应两个mapper的namespace分别为com.insomsia.dao.mapper.AMapper和com.insomsia.dao.mapper.BMapper。然后mapper文件内都有一个select节点,ID为"selectById"。
在解析这两个mapper文件将对应select节点的SQL内容缓存到mappedStatements时的过程如下:
1、假设先解析AMapper,则会先在mappedStatements放入key为com.insomsia.dao.mapper.AMapper.selectById和selectById的两个键值对。
2、然后轮到BMapper,先将selectById这个shortKey截出来,然后判断发现mappedStatements已存在selectById的key了,则覆盖再存一次,只不过value的变成了new Ambiguity(shortKey)。然后再往mappedStatements存入key为com.insomsia.dao.mapper.BMapper的键值对。
然后到了程序调用执行SQL的阶段,有两种场景:
1、假设你输入的SQLID是全路径,即namespace+SQLID,那么程序能正常执行
2、假设你输入的单单的一个shortKey(就是一个selectById),那么才会抛出异常
其实正常情况下,我们一般都是会输入全路径的~
接着再分析executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)这一句:
查看query方法实现,发现Executor是个接口,那么要先看下这里具体调用的是哪个子类的query方法。
代码往上找,可以看到在DefaultSqlSessionFactory构造DefaultSqlSession的openSessionFromDataSource方法中有这么一行:
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
可得知是由configuration构造的,点进去看看:
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor, autoCommit);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
executor是根据executorType来创建的,最常用的是SimpleExecutor。然后会再判断cacheEnabled是否为true,这个就是MyBatis配置文件中cacheEnabled的值,默认为true。所以默认情况下会在SimpleExecutor的上面再包装一层CachingExecutor。
然后去看CachingExecutor的query方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
if (!dirty) {
cache.getReadWriteLock().readLock().lock();
try {
@SuppressWarnings("unchecked")
List<E> cachedList = (List<E>) cache.getObject(key);
if (cachedList != null) return cachedList;
} finally {
cache.getReadWriteLock().readLock().unlock();
}
}
List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
return list;
}
}
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
ms.getCache()是获取二级缓存,默认情况下是不开启的,所以为空,然后代码直接走到return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)。这里的delegate实际就是SimpleExecutor,但是SimpleExecutor并没有实现query方法,所以会执行它的父类BaseExecutor的query方法:
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 查询缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 为空查数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
这里大概的逻辑就是会先用key从一级缓存里面取value,看看是否有值。没有再从数据库查询,然后将查询的结果加入到一级缓存里面再返回。
所以MyBatis的大概运行流程就这样的~