Mybatis源码阅读(三)SqlSession的创建和运行
接上一篇文章SqlSessionFactory的创建
https://www.jianshu.com/p/eb3d06a7c77d
SqlSession的创建过程
既然已经得到了SqlSessionFactory
,那么SqlSession
将由SqlSessionFactory
进行创建。
SqlSession sqlSession=sqlSessionFactory.openSession();
这样,我们就来看看这个SqlSessionFactory
的 openSession
方法是如何创建SqlSession对象的。根据上面的分析,这里的SqlSessionFactory
类型对象其实是一个DefaultSqlSessionFactory
对象,因此,需要到DefaultSqlSessionFactory
类中去看openSession
方法。
// DefaultSqlSessionFactory 类
@Override
public SqlSession openSession() {
return openSessionFromDataSource(
configuration.getDefaultExecutorType(), null, false);
}
/**
* 这里对参数类型进行说明
* ExecutorType 指定Executor的类型,分为三种:SIMPLE, REUSE, BATCH
* TransactionIsolationLevel 指定事务隔离级别
* 使用null,则表示使用数据库默认的事务隔离界别
* autoCommit 是否自动提交
*/
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);
// 创建Executor,即执行器
// 它是真正用来Java和数据库交互操作的类,后面会展开说。
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象返回,因为SqlSession是一个接口
// 可以类比DefaultSqlSessionFactory
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();
}
}
这样我们就得到了DefaultSqlSession
(SqlSession)。可以看到基本覆盖数据库的各种操作,增删查改,以及简单的事务的操作。
接下来就要看看它的执行过程。
SqlSession
的执行过程
获取到了SqlSession
之后,则需要执行下面的语句
CountryMapper countryMapper=
sqlSession.getMapper(CountryMapper.class);
因此我们要看看getMapper这个方法干了什么。上面的分析知道,这里的sqlSession其实是DefaultSqlSession
对象,因此需要在DefaultSqlSession
中去查看这个方法。
// DefaultSqlSession 类
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
这里的configuration
是Configuration
类的实例,在SqlSessionFactory
中创建并完成所有配置的解析后,初始化DefaultSqlSession
时,SqlSessionFactory
将配置作为属性传给DefaultSqlSession
,因此前面解析的所有配置,都能在这里查到。
因此我们继续往下看,这里就要定位到Configuration
类的getMapper
方法了。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// mapperRegistry 是一个mapper配置的容器,前面有提到
// 配置路径下所有扫描到的mapper在初始化完成Configuration以后,都会加载进来
// 每一个mapper都被存储在了MapperRegistry的knownMappers中了
// 在初始化配置的时候执行addMapper,在获取Mapper的时候执行getMapper
return mapperRegistry.getMapper(type, sqlSession);
}
因此,我们就要来看看MapperRegistry
的getMapper
方法
// MapperRegistry的getMapper方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从knownMappers集合中获取mapper,创建MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 获取代理对象,并返回
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
看到了MapperProxyFactory
很明显这是一个工厂类,所以肯定会有MapperProxy
这么一个类,而看到Proxy
,这里肯定用到了代理,也肯定就是动态代理了。
我们来看看获取代理对象的方法 newInstance
// MapperProxyFactory 类
...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// MapperProxy实现了InvocationHandler,扩展了invoke方法,维护代理逻辑。
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
...
这里我们可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象。这里最终返回了CountryMapper
接口的代理对象。
而代理对象则被放到了MapperProxy
中。通过idea
打断点,来查看CountryMapper
的详细信息,我们也可以看到这是一个MapperProxy
对象。
因此,在执行countryMapper.selectAll()
方法时,便会进入到MapperProxy
的invoke方法中来。
我们来看一下MapperProxy
的部分代码。
// MapperProxy类
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断是否是一个类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 显然,我们这里是一个接口,则执行下面的流程
// 生成MapperMethod对象,通过cachedMapperMethod初始化
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行execute方法, 把sqlSession和当前参数传递进去
return mapperMethod.execute(sqlSession, args);
}
...
接着来看看execute方法
// MapperMethod 类的方法
// MapperMethod采用命令模式运行,根据上下文跳转
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
...
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 主要看这个方法
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
...
}
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 将Java参数转换为Sql命令行参数
Object param = method.convertArgsToSqlCommandParam(args);
// 是否需要分页
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 通过SqlSession对象执行查询,带分页
// command.getName() 获取Mapper接口当前执行方法selectAll的全路径名
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
// 通过SqlSession对象执行查询,不带分页
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
至此已经基本可以明白了,Mybatis为什么只用Mapper接口就可以运行SQL,因为映射器的XML文件的命名空间对应的便是这个接口的全路径,它能根据全路径和方法名绑定起来,通过动态代理技术,让这个接口跑起来。然后采用命令模式,根据上下文跳转,最终还是使用SqlSession接口的方法使得它能够进行查询。
接着我们就来看看selectList
的源码。
result = sqlSession.<E>selectList(command.getName(), param);
注意这里的sqlSession,其实是DefaultSqlSession
的对象,因此要去看DefaultSqlSession
的selectList方法。
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 分页参数选择默认,表示不分页
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从Configuration配置中获取MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 使用executor进行查询
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();
}
}
接着就进入了Executor
查询之后,将结果返回。那么Executor
是如何进行查询的呢?
下一篇文章再见!