在使用Mybatis时,首先创建SqlSessionFactory对象,然后通过SqlSessionFactory的openSession()方法创建一个SqlSession实例
在之前已经分析过创建过SqlSessionFactory过程,现在来看下SqlSession完成sql查询的过程把.
openSession()方法
SqlSession对象是通过SqlSessionFactory的openSession方法创建的
SqlSession sqlSession = sessionFactory.openSession();
看下openSession的源码
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
其参数如下
- boolean autoCommit:是否开启JDBC事务的自动提交,默认为false。
- Connection:提供连接。
- TransactionIsolationLevel:定义事务隔离级别。
- ExecutorType:定义执行器类型。
实际上,openSession方法是通过SqlSessionFactory的实现类DefaultSqlSessionFactory覆盖openSession方法创建的
详细源码如下:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
调用了openSessionFromDataSource方法,继续追下去
openSessionFromDataSource源码:
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();
}
}
这里的作用主要是创建的东西如下,其返回对象为一个SqlSession的实现类—DefaultSqlSession
- 用TransactionFactory 创建一个Transaction
- 创建一个执行器Executor;(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的)
- 创建一个默认的SqlSession: new DefaultSqlSession(configuration, executor, autoCommit);
DefaultSqlSession:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
...
...
SqlSession方法具体实现...
}
其他对象咱们先不管,现在SqlSession已经创建完成了
SqlSession执行sql语句过程
SqlSession之selectOne
项目代码:
String statement = "mapper.UserMapper.selectById";
User user = sqlSession.selectOne(statement, 1);
System.out.println("通过id查找"+user);
System.out.println();
<select id="selectById" resultType="domain.User" parameterType="int">
select * from user where id = #{id}
</select>
我们知道不同的XML映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复。 原因是namespace+id是作为Map<String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复就会导致数据互相覆盖,有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
所以statement即是namespace+id组合而成,目的就是让Mybatis通过statement去寻找sql语句
点进selectOne查看源码
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;
}
}
观察源码可以很清楚的看见,其实所谓的selectOne,最后查找的还是一个list集合,在根据list的长度判断是否为1,如果长度大于1,就抛出了异常
继续进入
List<T> list = this.<T>selectList(statement, parameter);
该方法源码:
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);
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();
}
}
现在好戏开始了,RowBounds.DEFAULT是用来分页,wrapCollection是判断输入类型的源码如下
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
观察下面这行代码, statement不就是我们之前的namespace+id组合的值,
打个断点,验证下是不是查找的sql语句
MappedStatement ms = configuration.getMappedStatement(statement);
对比之前的xml中的sql语句,果然是获得mapper.UserMapper.selectById下的内容
现在已经获得了sql语句,观察源码,下一步的执行就是executor(执行器)了,所以在Mybatis中,它完成了sql语句的执行
Executor—执行器
继续进入,观察源码
executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
欧吼,不知道是进哪个方法了,因为它们都实现Executor接口,其实再打个断点,可以发现现在调用的是CachingExecutor类中的方法
源码:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
根据上一步,ms就是我们通过namespace+id组合得到的xml,sql内容根据方法名,可以判断出boundSql是真实执行的sql语句
其中key是缓存项,关于缓存以后会写的
继续进入方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
源码如下:
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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
因为我们是第一次执行的sql,所以缓存并不存在,直接进入下一个方法
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
点入现在调用的就是BaseExExecutor中的方法了,源码如下
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;
}
在一开始调用时,我们使用的resultHandler为null(Executor.NO_RESULT_HANDLER)观察代码,大概理解其他都暂时不用看,直接进入下一个方法
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;
}
现在依然直接进入下一层方法,进入
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
现在它调用的是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());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
呀,这是嘛呀,这不是jdbc里面的Statement么
JDBC之Statement
Statement statement = connection.createStatement();
接着来看下Statement是怎么创建的把
根据代码stmt = prepareStatement(handler, ms.getStatementLog());
咱们点进去瞅一眼
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;
}
发现代码stmt = handler.prepare(connection, transaction.getTimeout());
但是,我们的Connection是怎么创建的?好像没创建过这玩意呀
JDBC之Connection
点入getConnection方法
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
transaction这个对象哪来的???
再看看openSession方法
小样原来最开始就创建出来了,可是我们还没创建过Connection对象呀,咱们继续点入方法
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
终于知道了,原来是在这里创建的,有点懒汉的感觉嗷
进入openConnection方法
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
一目了然
Statement实例创建
Connection我们知道是从哪来的了,但是还是没有看见调用connection.createStatement(),继续下面走
进入 handler.prepare(connection, transaction.getTimeout());
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
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);
}
}
继续进入方法instantiateStatement(connection);
源码如下:
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);
}
}
一大堆判断的语句,其实最后是直接运行的最后一句connection.prepareStatement(sql)
好了Statement出来了使用的是它的子类PreparedStatement,一切明了了
正式调用,获得数据库数据
现在Statement对象的由来也清楚了,回到doQuery方法
进入下一个handler.
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
好了一切都清楚了,最后调用resultSetHandler.
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
很明显返回值存在ArrayList中,发现方法handleResultSet(rsw, resultMap, multipleResults, null);
点进去
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
果然multipleResults.add(defaultResultHandler.getResultList());遍历结果,将结果add入list中
最后,就终于获得了数据库的值
总结
- MyBatis是一个半ORM框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等复杂等过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高
- MyBatis可以使用XML或注解来配置和映射原生信息,将POJO映射成数据库中等记录,避免了几乎所有等JDBC代码和手动设置参数以及获取结果集。
- 通过xml文件或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
- Mybatis主要开始执行的sql语句的是Executor(执行器),Executor是跟SqlSession绑定在一起的,每一个SqlSession都拥有一个新的Executor对象,由Configuration创建。
- Executor分成两大类:
-
BaseExecutor
- SimpleExecutor:每执行一次update或select,就开启一个 Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
- ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
- BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
-
CachingExecutor
- 先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
-
评论区