参加了GitChat一个关于Mybatis活动:《一步步学习 Mybatis:缓存的使用及源码分析》,文中着重讲解了Mybatis缓存配置及使用,对Mybatis源码也稍有讲解。虽自工作以来MyBatis一直有在使用,然源码很少有阅读,随借此机会把部分源码大概看了下,主要从系统启动mapper实例的创建到一条语句的执行。
示例代码:
public interface UserMapper {
User findUserById(long id);
}
此处UserMapper为一个接口,其中定义了我们要对实体bean User的数据库持久化操作,此处仅为一个根据id查询实例的方法。接口UserMapper 的实例是通过MapperFactoryBean 来创建的, MapperFactoryBean 实现了Spring的FactoryBean接口:
public class MapperFactoryBean extends SqlSessionDaoSupport
implements FactoryBean
其实例是通过Java的动态代理来实现的,实现代码见下:
public class MapperProxyFactory {
...
...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
从上面代码可以看到 userMapper实例是通过Java动态代理Proxy来实现的,其InvocationHandler 为MapperProxy 实例,而在MapperProxy实例中包含了一个SqlSession实例:SqlsessionTemplate,而在SqlSessionTemplate实例中则包含了初始化创建的SqlSessionFactory:DefaultSqlSessionFactory, 在通过userMapper调用findUserById时,也即是执行mapperProxy的invoke方法:
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
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 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);
}
从代码mapperMethod.execute(sqlSession, args)追踪下去看到:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
这里的sqlSession 其实是SqlSession的实例:SqlSessionTemplate,下面是SqlSessionTemplate中的selectOne方法:
@Override
public T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy. selectOne(statement, parameter);
}
这里的sqlSessionProxy是 SqlSession的一个代理实现:SqlSessionInterceptor(SqlSessionTemplate的内部类)
private class SqlSessionInterceptor implements InvocationHandler {
@Override
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;
...
跟踪上面代码,查看getSqlSession,最终在DefaultSqlSessionFactory类中看到创建的是一个DefaultSqlSession:
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();
}
}
从上面两段代码可以看到mybatis为mapper里的每一次执行创建了独立的sqlSession实例。
继续追踪代码往下看,在DefaultSqlSession类中看到他的查询方法:
@Override
public List 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();
}
}
其中:
MappedStatement: 对发往数据库执行的指令的抽象表示。
Executor: 用来和数据库交互的执行器,其接受MappedStatement作为参数。
如果继续往下看就可以在BaseExecutor看到其在查询之前先从缓存中查询,如果未命中则前往数据库查询:
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);
}