核心流程
核心流程搞懂:主线,涉及的模块不深究。再去基础支持层,再回来核心。
/**
* MyBatis API 的使用
* MyBatis 在启动的时候会做哪些操作?
* 1.加载全局配置文件
* 2.加载映射文件
* 3.加载的内容存储在了那个Java对象中? Configuration
* @throws Exception
*/
@Test
public void test1() throws Exception{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("mapper.UserMapper.selectUserList");
for (User item: list
) {
System.out.println(item.toString());
}
// 5.关闭会话
sqlSession.close(); // 关闭session 清空一级缓存
}
mybatis在启动的时候做什么操作?
- 加载全局配置文件
- 加载映射文件
- 加载的内容存在什么java对象中
下面的题目分别对应着五步来进行
获取配置文件
// 1.使用的时候获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
这一步调用了io包下的Resources类的静态方法getResourceAsStream,里面调用了一个重载的getResourceAsStream方法,两个参数是类加载器和路径,其中类加载器为空。
/**
* io.Resources
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to fetch the resource
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
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;
}
而后还是重载嵌套,其中的getClassLoaders加载了五种加载器,封装到一个类加载器数组中
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
而后通过classLoaderWrapper中的getResourceAsStream生成字节输入流。
/**
* io.ClassLoaderWrapper
* Try to get a resource from a group of classloaders
*
* @param resource - the resource to get
* @param classLoader - the classloaders to examine
* @return the resource or null
*/
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) { // 不为空就加载
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue; // 猜测是变量放到里面节省内存空间?
}
}
}
return null;
}
这样就拿到了字节流。
加载解析配置文件
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
首先是SqlSessionFactory,可以看到他是一个接口,里面有若干的方法,比如selectOne等。而他的实现有两个,分别是 DefaultSqlSessionFactory
和 SqlSessionManager
。而我们没有直接通过DefaultSqlSessionFactory直接获取,而是通过Builder(建造者)模式进行获取。
为什么通过建造者获取呢?
1. SqlSessionFactory的目的是生产SqlSession的,当我们系统启动之后我们要获取工厂对象的时候,SqlSessionFactory工厂对象应该是单例。
2. 全局配置文件和映射文件也只需用在系统启动的时候完成加载操作。
由于1、2点,我们可以直接在工厂中直接完成配置文件的加载解析和工厂的创建。
但是这样会导致我们的职责不单一,所以用建造者模式来进行拆分。构建出这个复杂的对象。
SqlSessionFactoryBuilder的build方法重载了build方法,变成三个变量InputStream,String类型的环境,Properties三个变量,后两个为空。具体的解析给到了XMLConfigBuilder里。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最终返回一个 DefaultSqlSessionFactory >>
// 全局配置文件中的信息都被封装到了Configuration对象中
// 映射文件中的配置信息同样的也被封装到了Configuration对象中
// 一个具体的CRUD标签的信息被封装到MappedStatment对象中
return build(parser.parse());// 调用的下面的build
} 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.
}
}
}
// 这里暴露了一个事实,信息是放到Configuration中的。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
然后通过XMLConfigBuilder把字节流转为了XMLConfigBuilder对象(暂时不看),只需要知道他继承自BaseBuilder,在里面新建了一个Configuration实例,Configuration构造函数对配置进行了初始化,而后调用其中的parse方法,进行加载解析。保证只加载一遍,而后解析。
public Configuration parse() {
// 保证加载解析只能做一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// XPathParser,dom 和 SAX 都有用到 >>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
再往后就是parseConfiguration,里面的参数XNode就是对应着我们的mapper-config.xml的内容,在里面的dtd文件中可以看到他们规定规定内容。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 对于全局配置文件各种标签的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 读取文件
loadCustomVfs(settings);
// 日志设置
loadCustomLogImpl(settings);
// 类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于创建对象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于对对象进行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 子标签赋值,默认值就是在这里提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 创建了数据源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
以propertiesElement举例,可以看到他的作用是把提取到的信息放到Configuration中的variables中去。
private void propertiesElement(XNode context) throws Exception {
if (context != null) { // 意味着这个属性可以没有
// 创建了一个 Properties 对象,后面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// System.out.println(resource+": adsf");
// System.out.println(url +": adsf");
if (resource != null && url != null) {
// url 和 resource 不能同时存在
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 加载resource或者url属性中指定的 properties 文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
// 和 Configuration中的 variables 属性合并
defaults.putAll(vars);
}
// for (Map.Entry item: defaults.entrySet()
// ) {
// System.out.println(item.getKey()+":"+item.getValue());
// }
// 更新对应的属性信息
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
这样我们就知道了我们解析的配置文件都存到什么地方(Configuration),怎么存的(Mapper)了。
打开session
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
SqlSessionFactory是一个接口,他的实现类是DefaultSqlSessionFactory。里面实现openSession的方法如下:
@Override
public SqlSession openSession() { // 执行器才是真和数据库操作,干活的getDefaultExecutorType
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
openSessionFromDataSource代码如下,通过事务控制,创建了一个执行器和一个DefaultSqlSession类的实例。
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();
}
}
执行器executor才是真正的完成数据库的操作。点进去newExecutor,里面负责配置执行器的类型,和二级缓存还有插件。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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 {
// 默认 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存开关,settings 中的 cacheEnabled 默认是 true
// 映射文件中的<cache>标签只是创建了Cache对象,
// cacheEnabled才会真正对Excutor缓存的增强。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
执行sqlSession中的方法
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("mapper.UserMapper.selectUserList");
这里指定了全路径,然后就属于sqlSession的方法就执行完成了,程序把具体的执行过程交给了执行器executor进行。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// statement就是我们的全路径名?
MappedStatement ms = configuration.getMappedStatement(statement);
// 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
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();
}
}
执行器是一个接口有两个实现方法,其中第一个是BaseExecutor,另一个是CachingExecutor,先看CachingExecutor,因为整体流程是有cache就用cache,没有就是base。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建CacheKey:什么样的SQL是同一条SQL? >>
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
query具体的执行是看到有缓存就用二级缓存,如果有二级缓存,就直接把缓存中的数据加载进来。如果没有的话,就执行BaseExecutor中的query,也就是查询数据库。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement()
// 由 <cache> 标签决定
if (cache != null) {
// flushCache="true" 清空一级二级缓存 >>
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 获取二级缓存
// 缓存通过 TransactionalCacheManager、TransactionalCache 管理
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 写入二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
一级缓存查询的逻辑:先查询一级缓存,如果一级缓存中没有结果(list为空)就进行真正的数据库查询
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 异常体系之 ErrorContext
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()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 防止递归查询重复处理缓存 queryStack代表深度
queryStack++;
// 查询一级缓存
// ResultHandler 和 ResultSetHandler的区别 localCache.getObject(key)是一级缓存中存在的数据
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
为什么先二级缓存再一级缓存而不是先一级缓存?
二级缓存是进程级别的,一级缓存是会话级别的(session),生命周期长短是不一样的,二级缓存只要服务没重启就在,一级缓存一次会话结束就无了。二级缓存命中的概率是99%,一级缓存命中可能只有1%概率。
标签:缓存,resource,核心,流程,ms,MyBatis,return,null,加载
From: https://www.cnblogs.com/ylw-blogs/p/17785219.html