阅读本文默认您对mybatis有一定的认识,所以在此一些概念性的东西就不贴了,以一个查询为例,复现MyBatis如何通过封装和动态代理调用底层JDBC代码,在此期间经历了什么。
需要注意的点:
- 对于mybatis使用的查询方式和类型的实现类,这里使用的是SimpleXXXXXX,不同的实现类有不同的实现方式
- 主要介绍使用sqlSession执行查询时,底层代码流转,使用mapper方法查询的放到动态代理中,因为底层也是使用的sqlSession。
一、MyBatis如何执行一条sql?
先来看看我们平时是怎么使用sqlSession来进行查询的。
1.sqlSession执行查询代码
public static void main(String[] args) {
// 读取主配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = factory.openSession();
String name = "tom";
// 执行查询
List<User> list = sqlSession.selectList("com.demo.mapper.UserMapper.getUserByName",params);
}
2.初始化SqlSessionFactory
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这里又调用了一个重载方法。parser.parse()的返回值是Configuration对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} //省略部分代码
}
// 最终调用此方法默认返回DefaultSqlSessionFactory,这个类只有一个Configuration 属性
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
Configuration对象:
- 其实就相当于是mybatis-config.xml的对象映射。只要是在xml文件中定义的节点类型,在Configuration中都可以找到对应的属性;
- parser.parse() 这一步我们可以理解为将配置文件加载到内存中并存储,看下源码:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 1.解析<configuration></configuration>内部内容
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 以下分别对应mybatis-config.xml文件中的各个节点配置
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
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);
}
}
到此,我们就可以将xml文件中的配置采用对象的方式加以利用了。到此,SqlSessionFactory对象已经创建好了。
3.通过SqlSessionFactory获取SqlSession对象(一般与线程绑定)
来看openSession()方法,调用了openSessionFromDataSource
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
// 此处先声明了sqlSession对象所需的属性,然后返回新建对象
// 大多数是配置信息的再次赋值
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);
// 离jdbc查询方法最近的一层封装
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需要注意一下,我们即将使用的是SimpleExecutor。至此,一个SqlSession已经创建完毕。
4.执行sqlSession.selectList(String statement)
// 外部调用的方法
// 我们从调用可以看到这里传入的是mapper接口全路径名+方法名
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
//重载的方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 通过statement获取到了唯一的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();
}
}
首先:我们没有传入sql语句,myBatis怎么知道该执行哪一个sql?
答:MyBatis是通过接口全路径名+方法名来进行识别的,通过这个值可以得到唯一的一个MappedStatement对象,
那么也就是说我们需要(在启动时,或者使用XXXXXXBuilder手动创建)读取全部的*.xml文件,将文件统统转化为MappedStatement,并存到一个key-value的集合中。最后将这个集合存放到Configuration对象的mappedStatements中。
MappedStatement对象其实就是用来表示*.xml文件中标签信息的:
- <select/>;
- <insert/>;
- <update/>;
- <delete/>等
5.调用executor.query(XXX)
最终会使用BaseExecutor类中的query方法:
- 有缓存直接取缓存数据
- 没有缓存调用具体实现类查询数据库
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
// 这里创建了一个缓存键,在正式的查询过程中,会将查询结果缓存起来
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// 调用重载
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
//数据库查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 类似于先将这个key占住的意思
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 这里调用的是SimpleExecutor的doQuery方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除key
localCache.removeObject(key);
}
// 加入缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
6.SimpleExecutor实现JDBC查询数据库
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);
// 1.我们知道Connection信息在配置文件中是存在的,在此获取PreperedStatement
stmt = prepareStatement(handler, ms.getStatementLog());
// PreperedStatement执行结果处理
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// 2.获取获取PreperedStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// JDBC所使用的Connection对象
Connection connection = getConnection(statementLog);
// 获取stmt
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
// 注:BaseStatementHandler类中的方法
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 经过千辛万苦,终于获取到了statement对象,具体获取如下图。
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}

// 最终还是按照JDBC的方式完成了statement对象的创建
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
7.返回到SimpleExecutor的doQuery()方法(最后一步)
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());
// 1.调用下边代码
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
public class SimpleStatementHandler extends BaseStatementHandler {
/**其他代码省略*/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
// 执行常规JDBC操作
statement.execute(sql);
// 处理结果集并返回
return resultSetHandler.<E>handleResultSets(statement);
}
}
至此,一个查询流程就走完了。总结一下MyBatis的代码执行顺序:
- 创建SqlSessionFactory对象:SqlSessionFactoryBuilder会通过流的形式读取mybatis-config.xml文件,将配置封装成Configuration对象中,然后利用Configuration对象创建DefaultSqlSessionFactory(只有一个Configuration属性)。
- 打开SqlSession:通过SqlSessionFactory初始化SqlSession配置,设置一些默认值,比如默认使用SimpleExecutor等等
- 执行sql语句:SqlSession对象执行增删改查,本质是调用Executor接口的实现类
- 这里通过传入的全路径名+方法名参数,找到对应的MappedStatement对象,执行相应操作。
- Executor的具体实现类,对JDBC进行了各自的封装,包括:
- 缓存结果集,如果通过缓存键在缓存中查询到,就直接取缓存的结果,
- 创建PreperedStatement对象,执行查询,
- 对返回结果集进行处理。
初次阅读,有错误的请指正!!
本文详细解析了MyBatis执行SQL的过程,从SqlSessionFactory的创建到SqlSession的使用,再到Executor的查询逻辑,深入探讨了MyBatis如何通过封装和动态代理调用JDBC执行SQL,以及查询结果的缓存机制。
507

被折叠的 条评论
为什么被折叠?



