MyBatis源码分析
1. Mybatis的初始化
在利用spring配置mybatis时,需要做出如下配置,mybatis初始化时便利用如下配置生成SqlSessionFactory,SqlSessionTemplate等实例。
<!--数据源信息-->
<bean id="testSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdatabase"/>
<property name="username" value="root"/>
<property name="password" value="********"/>
<property name="initialSize" value="5"/>
<property name="maxIdle" value="1800"/>
<property name="maxWaitMillis" value="1000"/>
</bean>
<!--mybatis相关设置包括数据源,mapper文件路径,mapper文件所对应的dao路径-->
<bean id="sqlSessionFactory" name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--dataource-->
<property name="dataSource" ref="testSource"/>
<!--Mapper files-->
<property name="mapperLocations" value="classpath:test-sqlmap/*Mapper.xml"/>
<property name="typeAliasesPackage" value="com.hewenkai.controller.bean"/>
</bean>
<!--mybatis所映射的mapper接口路径-->
<bean id="mapperScannerConfiguration" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hewenkai.controller.mapper"/><!-- 多个使用逗号分隔 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
1.1 SqlSessionFactory
SqlSessionFactory为工厂类,负责在进行具体的sql语句执行时生成对应的SqlSession对象。每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。在具体执行sql语句时,mybatis会为每个执行过程通过SqlSessionFactory生成一个SqlSession去具体执行sql。
1.2 SqlSessionTemplate
SqlSessionTemplate 是 MyBatis-Spring 的核心,SqlSessionTemplate完全负责SqlSession的创建,执行以及关闭等生命周期管理,其利用SqlSessionFactory进行SqlSession的创建,可以看到SqlSessionTemplate实现了SqlSession接口。
1.3 MapperProxy
在初始化阶段,会针对每个mapper接口生成一个代理,在获取接口所对应的实例时 实际上获取的便是此代理,通过此代理来进行真正的执行,在代码里可以看到,myBatis是利用的动态代理机制进行的,调用mapper接口的方法时实际调用的是MapperProxy的invoke方法。
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);
}
2. SqlSession的生成
2.1 MapperProxy
上文说过,在使用mapper接口的方法时,实际上是调用的MapperProxy的invoke方法,其代码如下:
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 Exception Util.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
可以看到,如果调用method的不是接口而是个实例类时,直接调用实例的method方法,在很久之前常常会这么使用MyBatis,现在一般不会这么使用了。最后生成了,MapperMethod实例,当mapper方法被调用的时候对应的MapperProxy会生成相应的MapperMethod并且会缓存起来,这样当多次调用同一个mapper方法时候只会生成一个MapperMethod,提高了时间和内存效率。在调用MapperMethod的execute方法时,会根据sql语句的类型去调用不同的方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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;
}
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;
case FLUSH:
result = sqlSession.flushStatements();
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;
}
2.2 MapperMethod
进一步分析可知,最后是调用了MapperMethod域中的SqlSession去具体执行,而SqlSession又是从什么地方传过来的呢?再往上翻源码可知,此SqlSession是从MapperProxy中传递过来的,通过调试可知此SqlSession为SqlSessionTemplate中调用getConfiguration().getMapper(type, this);
将自身传递过去,前文中也说过,SqlSessionTemplate也实现了SqlSession接口,此时在这边用上了。在其实现SqlSession接口时,都是将调用转发到其成员变量sqlSessionProxy上,从构造函数中可以看出,sqlSessionProxy也是一个生成的一个动态代理,最终将会把调用转发到SqlSessionInterceptor的invoke方法中。
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
此时终于能够生成最后能够真正执行的SqlSession,此时mybatis将SqlSession的获取交由Spring的事务管理,将会保证使用的 SqlSession 与当前 Spring 的事务相关,如果当前spring事务中存在SqlSession则直接获取SqlSession,否则生成一个新的SqlSession并将其注册到spring事务中,因此可以保证了SqlSessionTemplate的线程安全性,从而也知道非事务环境下,每次操作数据库都使用新的sqlSession对象。
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;
} 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);
}
}
}
}
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
3. SqlSession的执行
历经这么多,终于生成了最终的SqlSession实例,其一般是DefaultSqlSession类型,具体执行过程如下(以selectList为例):
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@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 {
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();
}
}
3.1 MappedStatement
首先生成了一个MappedStatement实例,MappedStatement维护一条<select|update|delete|insert>节点的封装,即代表mapper文件中的一个sql语句,其中封装了SqlSource,负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,BoundSql表示动态生成的SQL语句以及相应的参数信息。
3.2 Executor
Executor是MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护。
3.2.1 BaseExecutor
BaseExecutor是抽像类,在创建时会根据传过来的ExecutorType创建不同的类,如下:
- SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
- ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
- BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
3.2.2 CachingExecutor
先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
3.2.3 DefaultSqlSession中executor执行过程
生成Executor时,如果允许缓存,则生成CachingExecutor,其内部封装了具体的BaseExecutor,进行查询时,首先会调用CachingExecutor的query方法,此方法首先利用MappedStatement生成BoundSql,然后利用相关信息生成缓存key,再根据此key从缓存中获取结果,如果缓存中不存在再调用相应的BaseExecutor进行实际的查询过程。否则直接根据ExecutorType生成具体的BaseExecutor。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
实际查询时调用BaseExecutor的query接口,之后调用到queryFromDatabase接口,最终调用doQuery接口。在doQuery接口中使用了StatementHandler以及ResultHandler
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 {
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;
}
@Override
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);
}
}
3.3 StatementHandler
StatementHandler负责处理Mybatis与JDBC之间Statement的交互,而JDBC中的Statement负责与数据库进行交互的对象。StatementHandler接口的实现大致有四个,其中三个实现类都是和JDBC中的Statement响对应的:
- SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理;
- PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;
- CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;
- RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。
在doQuery接口中创建的是RoutingStatementHandler,而RoutingStatementHandler则根据传入的mappedStatement.getStatementType创建具体类型的StatementHandler,作为自己的delegate成员,在执行StatementHandler.doQuery时将其直接转发到delegate上。回到Executor.doQuery方法上,在创建了StatementHandler后,又在此基础上生成了Statement对象,这个对象我们比较熟悉,是JDBC用来执行Sql语句的对象,将其传入StatementHandler.doQuery接口。此时以SimpleStatementHandler为例,就是直接调用Statement.execute方法。
/**BaseExecutor**/
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;
}
/***RoutingStatementHandler*/
public RoutingStatementHandler(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
/**SimpleStatementHandler**/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
3.4 ResultSetHandler
ResultSetHandler比较简单,就是将Statement实例执行之后返回的ResultSet结果集转换成我们需要的类型形式。