使用mybatis作为dao层框架是目前较主流的一种方案,与spring框架整合下的实践一般是先编写mapper接口,再编写mapper的xml文件,最后在service层中调用mapper接口进行数据库层面操作。举个系统岗位实例的curd做例子:
- mapper接口
public interface SysPostMapper
{
public List selectPostList(SysPost post);
public List selectPostAll();
}
- mapper的xml文件
select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark from sys_post AND post_code like concat('%', #{postCode}, '%') AND status = #{status} AND post_name like concat('%', #{postName}, '%')
- service层调用
@Service
public class SysPostServiceImpl implements ISysPostService
{
@Autowired
private SysPostMapper postMapper;
@Autowired
private SysUserPostMapper userPostMapper;
@Override
public List selectPostList(SysPost post)
{
return postMapper.selectPostList(post);
}
@Override
public List selectPostAll()
{
return postMapper.selectPostAll();
}
}
举例完毕,本文要探究的是为什么调用mapper接口就可以操作数据库?
1 mapper接口不是接口,是动态代理类接口自然是无法直接调用,真正在调用的其实是增强了该接口的动态代理类。mybatis使用的动态代理是基于jdk自带的那种,即基于接口的。下面举个例子说明下:
- mapper接口
public interface StudentMapper {
void study();
}
- 增强器(即实现了InvocationHandler)
public class MapperProxyimplements InvocationHandler { private final Class mapperInterface; public MapperProxy(Class mapperInterface) { this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("虽然是个接口,没有任何实现类,我也能直接执行!"); System.out.println("我还能知道你正在调用的方法:" + method.getName()); return null; } }
- demo演示
public class Demo {
public static void main(String[] args) {
MapperProxy mapperProxy = new MapperProxy<>(StudentMapper.class);
StudentMapper studentMapper = (StudentMapper) Proxy.newProxyInstance(mapperProxy.getClass().getClassLoader(), new Class[]{StudentMapper.class}, mapperProxy);
studentMapper.study();
}
}
studentMapper是个接口,也没有任何实现类,但也可直接调用,因为实际调用的是动态代理类,其具体逻辑定义在MapperProxy的invoke方法中。例子虽简单,但mybatis的做法其实也是类似的,下面一起探究。
首先从DefaultSqlSession类的getMapper方法切入,其实与spring整合的场景下一般感知不到这个步骤,因为mapper的动态代理类已经交给spring容器管理了。
// org.apache.ibatis.session.defaults.DefaultSqlSession @Override publicT getMapper(Class type) { return configuration.getMapper(type, this); }
// org.apache.ibatis.session.Configuration publicT getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
// org.apache.ibatis.binding.MapperRegistry publicT getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) 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); } }
// org.apache.ibatis.binding.MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
// MapperProxy实现了InvocationHandler接口
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
// 基于jdk自动的动态代理生成
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
看看MapperProxy的具体逻辑把
public class MapperProxyimplements InvocationHandler, Serializable { private static final long serialVersionUID = -4724728412955527868L; private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC; private static final Constructor lookupConstructor; private static final Method privateLookupInMethod; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @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 { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } // ...................省略...................... }
由上面的源码分析可知,mapper接口执行的实际逻辑定义在MapperProxy的invoke方法中
2 mapper接口执行查询类sql的过程 2.1 mapper接口的执行入口上面说过mapper接口是个动态代理类,分析过增强的逻辑即可知道真正执行逻辑封装在类MapperMehod中,故从此类切入
// org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// command这类信息的封装在前面的节点已处理,大体可理解为从mapper的xml文件解析所得
switch (command.getType()) {
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;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
// 以select类型的sql为例子
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
// ...............省略..............
}
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
Object param = method.convertArgsToSqlCommandParam(args);
// 判断是否有分页信息数据
// 实际使用的是SqlSessionTemplate处理
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.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;
}
// org.mybatis.spring.SqlSessionTemplateorg.mybatis.spring.SqlSessionTemplate // 与spring整合的环境下使用这个组件,其被SqlSessionInterceptor增强 @Override public2.2 获取SqlSessionList selectList(String statement, Object parameter) { return this.sqlSessionProxy.selectList(statement, parameter); } // -----增强的逻辑---------- // org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在此获取正在的sqlSession,后续详细分析 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 调用目标方法 Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
说明下获取sqlSession的过程
// org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 从事务同步管理器中观察是否已经有本线程之前放过的sqlSession对象,有则直接返回
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
// 没有则新获取一个session对象
session = sessionFactory.openSession(executorType);
// 把session绑定到线程上,当然这需要看是否开启配置 TransactionSynchronization
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
TransactionSynchronizationManager这个对象是spring-tx包下的,与spring的事务相关,可存放很多线程相关的变量,值得关注,后期将mybatis和事务结合起来的时候再重点分析。
接下来重点分析下openSession的过程
//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
// 其实大部分情况都是调用openSessionFromDataSource此方法获取session
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 与spring结合的话则是SpringManagedTransactionFactory,具体可看mybatisAutoConfiguration的配置
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// SpringManagedTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// CachingExecutor 代理对象SimpleExecutor 封装变量transaction configuration
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();
}
}
session中必然要有数据库的信息,transaction封装了dataSource,executor中封装Transaction,最终在DefaultSqlSession中封装executor,链式调用,最终就有此信息
看看获取Executor的过程
//org.apache.ibatis.session.Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据不同类型生成,一般都是 Simple那种,然后再用Caching套上
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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2.2.1 值得关注的插件增强
用过分页插件的都知道其原理是增强了executor的实现类,增强的插入点就在这里:
//org.apache.ibatis.plugin.InterceptorChain
//这个chain啊,其实myatis的configuration中addInterceptor的时候会往里头加插件
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 返回的target是个代理对象,经过遍历后就会,,,一层套一层.....
target = interceptor.plugin(target);
}
return target;
}
//org.apache.ibatis.plugin.Interceptor
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// org.apache.ibatis.plugin.Plugin
// 最核心的逻辑在这里
// plugin类实现了InvocationHandler接口,很显然有要进行动态代理增强了
// 这里的target一般是simpleExecutor
public static Object wrap(Object target, Interceptor interceptor) {
// 一般inteceptor会在类开头写下这类注解元信息,可参考PageInterceptor
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
// 获取type实现的接口中在signatureMap中定义的,说直白点就是判断是否要拦截这个接口
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//-----------------------------------分隔符-------------------------------------------
// plugin既然实现了InvocationHandler接口,那就看看他的增强逻辑吧
//org.apache.ibatis.plugin.Plugin
// 成员变量
private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
// 增强的逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 只有定义的方法才拦截
Set methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
总结:executor是有可能被插件增强过的。
上面这一大段都是如何获取sqlSession的逻辑,为啥这么复杂呢?因为与spring整合后有事务这种概念,同个事务使用相同的sqlSession,引入了spring-tx中的组件TransactionSynchronizationManager用于获取同个线程中先前存过的sqlSession(如果存在)。梳理下来有这么几点:
- 获取组件:executor transaction sqlsession
- 组件的依赖,executor需要transaction,transaction中封了datasource的信息,sqlSession需要executor
- 其他的点:
- 如果TransactionSynchronizationManager有SqlSession支持直接返回,没有得新获取
- executor是可以被plugin增强的
有了sqlSession总算可以查数据了,下面开始分析!
回到逻辑:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
通过反射的方式调用类org.apache.ibatis.session.defaults.DefaultSqlSession的selectList方法
privateList selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); // wrapCollection的逻辑比较简单,如果parameter是collection或array类型的则套一层返回ParamMap对象来返回 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
经过多个重载方法后最终进入executor.query(ms, wrapCollection(parameter), rowBounds, handler);
如果statement有使用cache则会先从cache中取,即配置二级缓存,不过目前没遇到配cache的情况,故简化处理,具体逻辑在cachingExecutor的deltegate成员,即simpleExecutor中
// org.apache.ibatis.executor.SimpleExecutor publicList 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 list; try { queryStack++; // 先从一级缓存取 list = resultHandler == null ? (List ) 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; } // 从数据库中取 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List 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; } public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); // 比较关键的组件StatementHandler,后面细讲 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 预编译statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); // 查询获取结果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
总结下获取数据的过程:一般都是通过simpleExecutor此组件获取数据,先从一级缓存取,没有则从数据库取,然后放入缓存中;
2.4 StatemnetHandler组件真干活从获取数据到封装数据有个很重要的组件StatementHandler,接下来重点说明:
2.4.1 获取StatemnetHandler过程// org.apache.ibatis.session.Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 实际上的类型是RoutingStatementHandler,根据ms.getStatementType让其包裹一个delegate做真正的活,一般是PreparedStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 一样的,statementHandler也可以被plugin增强,但很少这么搞,比如PageInterceptor就不增强statementHandler接口
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
2.4.2 基于StatementHandler获取prepareStatement
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;
}
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 一般情况都是直接 connection.prepareStatement(sql);
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);
}
}
//org.apache.ibatis.scripting.defaults.DefaultParameterHandler
//从handler.parameterize(stmt); 最终将到此处的逻辑
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 这东西和预编译语句中的?一一对应
List parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 输入属性进此逻辑
Object value;
// 获取属性名
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//这里的parameterObject就是指xxxxMapper中接口被调用时上送的参数
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// 是否有对应的类型处理器
value = parameterObject;
} else {
//这种情况指的是上送的若是个对象,一般都没有类型处理器嘛
// 此时就通过属性名获取对应对象的getxxx的返回值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 设置参数,最终底层执行jdbc的操作,如ps.setLong(i,value)
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
总结下获取statement的过程:
- 获取connection
- 通过connection和sql获取prepareStatement
- 将参数填充到statement中,参数已经在mappedStatemnt的bounedSql的ParameterMappings中封好,依次遍历封装进去
// org.apache.ibatis.executor.statement.PreparedStatementHandler publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); } // -----------------分割线-------------------- // org.apache.ibatis.executor.resultset.DefaultResultSetHandler public List
上面分析了使用preparedStatementHandler获取对象的具体过程,总结成下面几点:
- 调用statement.execute,其实就是进行查库
- 使用DefaultResultSetHandler的handleResultSets方法处理数据,其实就是遍历resultset,通过反射方式创建对象,设置对象值
- 理一下几个组件:metaObject rowValue DefaultResultHandler的list变量 multipleResults,先创建对象作为rowValue,把rowValue封到metaObject中,根据resultSet封属性的时候操作的是metaObject,那么其实也封到了rowValue中,rowValue后面会封到DefaultResultHandler的list变量,从DefaultResultHandler的list变量赋值到multipleResults后返回
到此,获取数据的整个过程结束,后续操作一般是善后类的,如关闭sqlSession之类
3 mapper接口执行更新类sql的过程前面介绍了查询语句的逻辑,下面介绍下更新语句的逻辑,有了上面的分析过程,看update就轻松很多。
还是从MapperProxy切入
// org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
// .................省略
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 最终调用sqlSessionTemplate进行update操作
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
// .................省略
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// -----------------------分割--------------
// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
// sqlSession此对象被SqlSessionInterceptor增强,故先入此逻辑,基本都是这种操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
mapperMethod最终也是调用DefaultSqlSession的update方法,具体分析下此方法
// org.apache.ibatis.session.defaults.DefaultSqlSession
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
// caChingExecutor,但其实可能被plugin增强过
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// org.apache.ibatis.executor.SimpleExecutor
// cachingExecutor最终使用的也是SimpleExecutor组件
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 和查询操作一样,也是使用statementHandler,具体使用的是RoutingStatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
获取prepareStatement的过程和查询是一样的,三步走:1获取连接connection,2基于connection生成prepareStatement,3使用statementHandler填充prepareStatement中的参数
下面具体分析statementHandler的update逻辑
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行jdbc的查询
ps.execute();
int rows = ps.getUpdateCount();
// 获取当初mapper中上送的参数
Object parameterObject = boundSql.getParameterObject();
// key生成器,update操作一般没配,则是 NoKeyGenerator,无逻辑的类
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
更新操作的逻辑很简单,没有查询那么复杂。关于key生成器的使用虽然update操作没用到,但是insert操作时经常使用,具体表现就是如果主键是自增的,则插入完成后可将自增生成的主键自动设置到对象中,下面具体分析下其逻辑
3.1 关于KeyGeneratorkeyGenerator是一个接口,mybatis提供了几个实现类:
- Jdbc3KeyGenerator 自增主键那一类使用这种
- SelectKeyGenerator oracle那类需要指定selectKey的使用这种
- NoKeyGenerator 空操作
分析下Jdbc3KeyGenerator 的逻辑
// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// ms里已经封好了主键属性
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
try (ResultSet rs = stmt.getGeneratedKeys()) {
// 获取rs的元信息
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
// 映射键,逻辑较复杂,不深究了,打住
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
4 sql执行过程总结
画图总结
参考自 《一本小小的MyBatis源码分析书》
5 最后本文从源码角度分析了mybatis执行sql的过程,可能有不严谨和理解不正确的地方,在以后有更多使用经验后不断修正。
参考文章:
MyBatis 源码分析系列文章合集



