mybatis源码整理
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
1、初始化SqlSessionFactory
1.1、创建Configuration
SqlSessionFactoryBuilder().build组要的目的就是从xml中读取信息创建configuration
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
mybatis-config.xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
从这里可以看出mybatis-config.xml经过XMLConfigBuilder解析最后被封装到Configuration中
configuration是mybatis最核心的类,xml所有的配置包括mapper.xml中的信息都被封装到这里了
至此,我们刚完成SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);的解析。
1.2、创建MappedStatement
MappedStatement是mapper.xml中select|insert|update|delete编写的sql所映射的对象,mappedStatements是存放MappedStatement的Map集合。保存在Configuration对象中。
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement完成对mapper的解析并将其封装成MappedStatement。
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode这里完成对sql语句的解析。
mappedStatements是一个map其中的key是(namespace.id)组成的,命名空间加上sql的id从而保证key的唯一。
2、创建SqlSession
2.1、如何创建SqlSession的
代码可以看出SqlSessionFactory顶层设计是一个接口,主要的工作还是DefaultSqlSessionFactory来做。
DefaultSqlSessionFactory.openSessionFromDataSource主要就是来创建sqlSession,这里sqlSession是接口,默认的创建的是其实现DefaultSqlSession。其中创建执行器(Executor)很重要,因为这里最终是要调用jdbc连接完成和数据库的交互的。
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);
// configuration核心配置类
// executor核心执行器
// autoCommit是否自动提交
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();
}
}
2.2、如何创建Executor的
final Executor executor = configuration.newExecutor(tx, execType)这里进入Executor的创建
- 执行器的类型默认是SIMPLE。
- 如果配置了执行器的类型就按照指定的类型生成。
- 如果开启了二级缓存,者默认采用CachingExecutor执行器。
配置可以参考mybatis中文官网: https://mybatis.net.cn/configuration.html#settings
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
// 这里执行器的类型默认是SIMPLE,但是可以设置ettings中有配置项
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 {
executor = new SimpleExecutor(this, transaction);
}
//settings中有配置项
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
3、动态代理
3.1、接口动态代理
创建sqlSession后想要实现对某个dao的查询,往往我们会定义一个接口,但是通常情况下我们并不会去实现这个接口但是我们却能通过和数据库进行交互,主要是因为这里采用了动态代理的方式,为我们动态创建了代理对象并通过代理对象实现对底层jdbc的操作,从而实现和数据库的交互。
BlogMapper mapper = session.getMapper(BlogMapper.class);
org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper这里DefaultSqlSession的getmapper传入接口的class并调用org.apache.ibatis.session.Configuration#getMapper函数最终在代理工场中创建了代理对象org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy)
MapperProxy实现了InvocationHandler接口,这不就是jdk自带的创建代理的方式,通过创建的代理对象调用jdbc实现数据库的交互。
3.2 、sqlSession调用jdbc
代理对象中传入的参数包含sqlsession,目的就是希望通过sqlsession来帮助接口完成数据库的交互操作。
org.apache.ibatis.binding.MapperMethod#execute识别是sql是增删改查那种类型(这里我们只跟踪查询类型)。
org.apache.ibatis.binding.MapperMethod#executeForCursor这里调用了sqlsession的查询接口。
org.apache.ibatis.executor.SimpleExecutor#doQueryCursor。这里就是获取到jdbc连接的Statement,通过jdbc完成数据库交互操作
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
// 这里就是获取到jdbc连接的Statement,通过jdbc完成数据库交互操作。
Statement stmt = prepareStatement(handler, ms.getStatementLog());
//这里的handler就是对返回结果的处理
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
4、mybatis缓存
mybatis 的缓存分为两级缓存,一级缓存的作用范围是sqlsession,也就会话级别的,如果如果jdbc执行了commit获取callback会话就会被销毁。
如果开启了二级缓存,默认查询会走org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
默认先查询二级缓存,在查询一级缓存的,因为二级缓存一般都是保存在内存中的,例如:采用redis作为二级缓存。二级缓存不像一级缓存哪像生命周期比较短,如果先查询一级缓存,很大概率sqlsession已经被关闭,一级缓存已经被清除了,但是如果先查询二级缓存,大概率是能查询的到,这样就很大程度少减少对一级缓存的查询。总之,二级缓存的命中率更高,大概率情况下不需要查询一级缓存。
@Override
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, boundSql);
@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;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}