为了理清楚
ibatis一次sql过程中发生了什么,本文将对ibatis一次sql过程做简要的分析。
String resource = "mybatis.cfg.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = ssf.openSession();
try {
UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
首先分析下SqlSession session = ssf.openSession(); 这条语句里面发生了什么,其实这条语句就是通过SqlSessionFactory工厂获取一次查询的Session。其中上一句SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader); 这句代码通过xml文件获取到SqlSessionFactory工厂,深入进去发现最终获取色SqlSessionFactory最终调用的是下面这个方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
拿到SqlSessionFactory后我们再去看看SqlSession session = ssf.openSession();这句代码究竟干了什么?深入代码发现ssf.openSession()最终调用了一下方法。
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, autoCommit);
return new DefaultSqlSession(configuration, executor);
} 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();
}
}
可以从方法中看出,首先获取加载配置文件的环境信息,为后面获取环境信息中配置的数据源做准备,从环境配置中获取事务工厂。以JDBD事务为例。在JdbcTransaction中封装了数据库连接操作,因此此处并没有
创建数据库连接。接着根据获取到的事务和执行类型以及是否提交获取Executor实例。其中newExecutor(tx, execType, autoCommit)代码如下所示;
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;
}
这里主要做了三件事
(1)判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE。
(2)根据执行器类型返回不同类型的执行器(执行器有三种,分别是 BatchExecutor、SimpleExecutor和CachingExecutor,后面我们再详细看看)。
(3)跟执行器绑定拦截器插件。
最后将返回DefaultSqlSession(configuration, executor)。 DefaultSqlSession实现了SqlSession接口,而SqlSession接口装封装了大量跟数据操作有关的方法,可以断言,所有的操作将在
DefaultSqlSession中发生,例子中UserInfo user = (UserInfo) session.selectOne(“User.selectUser”, “1”); 为例进去看看,这个代码最终会执行以下几个方法。
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;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
其中MappedStatement ms = configuration.getMappedStatement(statement);这句话是根据“User.selectUser”找到在Mapper配置文件的信息。
接着根据配置文件信息也就是MappedStatement 去执行executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
进入到executor.query()方法中可以看出:首先调用BaseExecutor中这个方法;
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
这个方法中进行了sql参数绑定。并且做了相应的缓存处理。然后执行了下面这个重载方法
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();
}
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
这个方法中我着重看下这个语句list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);意思是从数据库中获取查询,具体如下所示:
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;
}
暂时还没有看到时如何查询,进一步深入到doQuery(ms, parameter, rowBounds, resultHandler, boundSql);这个方法中是个抽象方法在子类中有实现,我们挑选其中一个实现看了下具体如下所示:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
其中prepareStatement(handler, ms.getStatementLog());方法如下所示,可以看出真正创建数据库链接在这里。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
后面基本就是JDBC操作数据库的内容了。到此一次SQL语句到此完毕。