SqlSession获取
一切从new SqlSessionFactoryBuilder().build(inputStream)
说起,build
方法最终通过xml配置文件解析生成一个Configuration
对象,注入到DefaultSqlSessionFactory
对象中返回,在这个过程中,Configuration
根据配置文件完成了以下配置:
properties
主要是把<properties>标签下的属性-值或者外部properties文件中的属性-值加载进来,供后面替换变量
settings
加载常见的配置如defaultExecutorType
typeAliases
配置类的别名
plugins
配置插件
objectFactory
objectWrapperFactory
reflectorFactory
environments
配置dataSource和transactionManager
databaseIdProvider
typeHandlers
mappers
接下来openSession
方法默认会根据配置的dataSource
来创建:
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();
}
可以看到,生成了一个DefaultSqlSession
对象
Executor
在生成DefaultSqlSession
对象的过程中,传入了一个Executor
对象,该对象是真正执行查询的对象,我们看看MyBatis中都有哪些Executor类型
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里的ExecutorType
就是我们在配置文件中配置的,默认为SIMPLE
,可以看出,一共有3种类型,包括SIMPLE
、BATCH
、REUSE
,在返回executor之前,加入了拦截链,我们可以利用这个来编写插件(参考作者其他文章),对各种类型的Executor
说明可以查看官网或者自行阅读源码,这里以SIMPLE
为例说明
<hr />
到目前为止,SqlSession
对象就已经准备好了,我们可以直接用它来进行查询,但是不推荐这么做,而是通过编写Mapper
接口的方式
Mapper的动态代理
比如我们编写了一个Mapper
接口DemoMapper
并且正确在配置文件中进行了相关的配置,那么我们执行下面的语句:
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
首先,sqlSession
将getMapper
方法委托给configuration
的getMapper
方法,最终委托给MapperRegistry
的getMapper
方法:
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
对象来生成代理对象,MyBatis会在加载配置文件时为每个Mapper生成一个MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
可以看到,MapperProxyFactory
的newInstance
方法利用JDBC的动态代理技术生成了一个T
泛型对象(最终就是我们请求的DemoMapper.class
)
MapperProxy对象
此对象实现了InvocationHandler,拦截方法调用
@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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
可以看出,当定义method的是一个接口的时候,会生成一个MapperMethod
对象,调用它的execute
方法,截取execute
方法片段:
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
最终还是回到了sqlSession
的方法调用上面,因此,我们通过编写并配置Mapper
来执行查询,而不是直接通过sqlSession
最终的实现--Executor的底层实现
以查询为例,最终会跳转到Executor
的query
方法,以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);
}
}
首先,创建一个StatementHandler对象,然后预编译SQL语句生成Statement对象,最后执行
预编译的过程如下
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;
}
包括了两个过程,prepare和parameterize
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);
}
}
prepare过程主要是初始化和设置一些连接参数如超时时间、最大返回行数
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
parameterize就非常简单了,委托给ParameterHandler
设置每个占位符上面的参数和值,最后执行query
:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
到这里什么都已经准备好了,所以执行查询变得非常简单,就是我们编写JDBC
时对PreparedStatement
的一般操作,然后通过ResultSetHandler
来搜集返回结果
总结一下Executor的执行流程:
- 新建
StatementHandler
对象对SQL进行初始化和预编译 - 利用
ParameterHandler
对象对预编译的SQL填参数 - 利用
PreparedStatement
对象执行查询(JDBC) - 利用
ResultSetHandler
对象搜集结果返回
对于具体的Handler有哪些类型、怎么实现的,读者可以自行查阅相关源码或文档,到这里,整个MyBatis执行的过程已经很清晰了