一、引言
手动使用Mybatis的四个步骤:
获取SqlSessionFactory对象
获取sqlSession对象
获取接口的代理对象(MapperProxy)
执行增删改查方法
前三篇详细分析了第一步、第二步和第三步,下面在此基础上,继续来分析代理对象是如何执行增删改查Sql的。
二、源码分析
// 4、执行增删改查方法
Department dept = mapper.getDeptById(1);
接下来,要真正去看sql的执行过程了。
在第三步,我们拿到了MapperProxy,每个MapperProxy对应一个Dao接口(DepartmentMapper.class), 那么我们在使用接口中方法的时候,MapperProxy是怎么做的呢?
在第三步的源码分析中,我们知道第三步获取到的其实是接口(DepartmentMapper.class)的代理对象MapperProxy(该对象中有SqlSession和Configuration)。因为MapperProxy实现了InvocationHandler接口,所以在执行接口中的目标方法之前,要执行invoke()方法,在invoke()方法中去执行目标方法。
1、执行代理对象MapperProxy的invoke()方法
//MapperProxy的invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断是不是Object方法
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);
}
//不是Object的方法,将Method封装为MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//传入sqlSession,args为sql需要的参数
return mapperMethod.execute(sqlSession, args);
}
2、将Method封装为MapperMethod后,调用execute()
MapperMethod中的execute()方法,先判断CRUD类型,然后根据类型去选择到底执行SqlSession中的哪个方法,绕了一圈,又转回SqlSession了。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断增删改查类型
switch (command.getType()) {
case INSERT: {//insert
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {//update
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {//delete
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT://select
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {//返回结果多个
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//返回结果为Map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {//返回结果为Cursor
result = executeForCursor(sqlSession, args);
} else {//返回结果不是上面的情况,此处查询的1条数据,所以进入属于这种情况
//将参数转换一下
Object param = method.convertArgsToSqlCommandParam(args);
//调用SqlSession的selectOne()方法
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
//调用SqlSession中的selectOne()方法
@Override
public <T> T selectOne(String statement, Object parameter) {
//最终还是调用selectList()方法
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;
}
}
3、SqlSession的selectList()方法
既然又回到SqlSession了, 那么下面就看看SqlSession的CRUD方法了,这里为了省事,我们选择了selectList()方法。
//selectList()方法
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从Configuration中获取得到MappedStatement,
// 我们知道,一个MappedStatement对象,就代表一个增删改查标签的详细信息。
MappedStatement ms = configuration.getMappedStatement(statement);
// CRUD实际上是交给Excetor去处理, Excutor其实也只是穿了个马甲而已
// wrapCollection(parameter)方法,用于包装传入参数
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();
}
}
// 包装参数的wrapCollection()方法
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {//参数为Collection类型
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);//将参数放入Map中,键为collection,值为参数
if (object instanceof List) {//参数为List类型
map.put("list", object);//将参数放入Map中,键为list,值为参数
}
return map;
} else if (object != null && object.getClass().isArray()) {//参数类型为数组
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);//将参数放入Map中,键为array,值为参数
return map;
}
return object;
}
4、调用Executor的query()方法
//BaseExecutor类中的query()方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//BoundSql代表当前SQL的详细信息
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
//BaseExecutor类中的query()方法
@SuppressWarnings("unchecked")
@Override
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 {
//从数据库中查询数据,ms代表一条sql的详细信息,parameter是参数
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;
//放入缓存
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//执行查询操作!!!最终执行sql的地方
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;
}
5、调用doQuery()方法
然后,通过上面一层一层的调用,最终会来到doQuery方法, 这儿就随便找个Excutor看看doQuery()方法的实现,这儿选择了SimpleExecutor:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//得到Configuration
Configuration configuration = ms.getConfiguration();
//得到StatementHandler!!!!!
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//得到Statement 的前置工作
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler封装了Statement, 让 StatementHandler 去处理
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//得到与数据库的连接
Connection connection = getConnection(statementLog);
//得到Statement
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//到此,原形毕露, PreparedStatement
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//结果交给了ResultSetHandler 去处理
return resultSetHandler.<E> handleResultSets(ps);
}
6、查询流程总结
作用说明:
(1)、StatementHandler:处理sql语句预编译,设置参数等相关工作;
(2)、ParameterHandler:设置预编译参数用的
(4)、ResultHandler:处理结果集
(5)、TypeHandler:在整个过程中,进行数据库类型和javaBean类型的映射
三、小结
在下面的时序图中,需要注意StatementHandler、ParameterHandler、ResultSetHandler的创建
四、总结
终于把四个流程分析完啦,下面来总结一下:
1、根据配置文件(全局,sql映射)初始化出Configuration对象;
2、创建一个DefaultSqlSession对象;
SqlSession里面包含Configuration以及Executor,
根据全局配置文件中的defaultExecutorType创建出对应的Executor
3、DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
MapperProxy里面有(DefaultSqlSession);
4、执行增删改查方法:
1)、调用DefaultSqlSession的增删改查(Executor);
2)、会创建一个StatementHandler对象。(同时也会创建出ParameterHandler和ResultSetHandler)
3)、调用StatementHandler预编译参数以及设置参数值;
使用ParameterHandler来给sql设置参数
4)、调用StatementHandler的增删改查方法;
5)、ResultSetHandler封装结果
4、注意:
四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)
每个创建的时候都有用interceptorChain.pluginAll()进行封装后,才得到对应的对象。
对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;
}
//创建StatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
//ParameterHandler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
//StatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}