Mybatis作为一个半自动的ORM框架, 可以通过xml或者注解的方式完成SQL语句的编写, 消除了大量JDBC冗余代码,不需要手动开关连接. 让我们可以直接基于SQL编程, 不必关系数据库相关操作, 极大方便了我们的开发过程.
在使用过程中, 我们只需要定义Mapper接口和对应的Mapper.xml文件, 通过namespace和SQL-id就可以将Mapper中的接口方法和对应的SQL语句关联起来.
那么有没有想过, 是如何实现这个巧妙的关联过程的. 针对Spring/Mybatis的整合, 提出了以下几个问题, 让我们带着问题去探究这个巧妙的实现.
- Mapper接口如何被BeanFactory实例化成Bean实例的
- MapperBean如何关联到Mapper文件中的SQL语句的
- MapperBean中的数据库链接Connection如何获取的, 和事务切面中获取管理事务的代码是否一致
- Mapper是否能够被再次代理, 如何对Mapper进行切面处理?
- #和$的区别
- Spring整合mybatis的配置,如何将Mybatis整合进入Spring的
MapperFactoryBean
(提前剧透)在Spring-Mybatis的整合中, Spring是通过FactoryBean机制:MapperFactoryBean来将Mapper_(接口)_注入到Spring容器中.
FactoryBean#getObject()可以让我们在Spring容器getBean的时候改变对象的实例化(同样也就能改变方法的实现). MapperFactoryBean就是利用了这点, 将Mapper注入到Spring容器, 让我们可以在Service里面注入Mapper.
接下来就翻阅MapperFacotory的源码,看Spring是如何实例化Mapper实例的
MapperFactoryBean#getObject
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
可以看到MapperFactoryBean#getObject将实例化的操作委托给SqlSession#geteMapper.
那么这个SqlSession是从哪里来的, 它的getMapper方法是怎么实现的?
SqlSession我们可以发现有3个实现类:
- DefaultSqlSession
- SqlSessionManager
- SqlSessionTemplate
可以看到DefaultSqlSession和SqlSessionTemplate中的getMapper都是委托给Configuration#getMapper(class, SqlSession) .
DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
SqlSessionTemplate#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
我们就直接跳入Configuration#getMapper(class,sqlSession)查看是如何构建Mapper实例的. 关于如何获取SqlSession的在 SqlSession&SqlSessionFactory章节中解释.
Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
可以看到, Configuration#getMapper依然是将委托给了mapperRegistry.
不过可以看到getMapper对应的操作有个addMapper操作,它的操作也是委托给MapperRegistry.
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
不过查看Configuration#getMapper的调用链,可以看到它在MapperFactoryBean#checkDaoConfig中被调用.
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
这段代码大致内容就是: 1. 检查SqlSession是否为空,如果为空,抛出异常 2. 判断configuration中是否存在需要获取的Mapper, 如果没有则添加(addMapper).
checkDaoConfig继承自父类, 在InitializingBean#afterProperties实现中被调用. 这样实现就保证了MapperFactoryBean#getObject的时候不会出现异常.
MapperRegistry
既然Configuration#getMapper和addMapper的实现都委托给了MapperRegistry,继续看MapperRegistry是怎么处理getMapper和addMapper的.
MapperRegistry#getMapper
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}
// ....
}
可以看到getMapper(type,sqlSession)从knownMappers中获取了一个MapperProxyFactory实例, 然后通过MapperProxyFactory.newInstance(sqlSession)方法完成了Mapper的实例化.
这里的的knownMappers的put操作在下面的MapperRegistry#addMapper中被调用.
MapperRegistry#addMapper
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
// ....
}
前面的MapperRegistry#getMapper中使用的MapperProxyFactory就是在这里被实例化的.
MapperAnnotationBuilder见名知意, 应该是处理Mapper上面的@Select @Update @Delete @Insert注解的.
MapperProxyFactory
前面已经了解到Mapper的实例是通过MapperProxyFactory#newInstance(class)创建的. 通过命名,可以大致得到MapperProxyFactory是一个工厂,用来生产Mapper实例. 我们想要知道的Mapper实例是如何创建的,大致应该就在这里了, 再也不是委托操作了.
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
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(sqlsession)是使用的Jdk动态代理创建的Mapper实例, 对应的InvocationHandler实现对应的就是MapperProxy.
到这里, Mapper实例如何被创建出来的就明确了, 下面需要继续了解Mapper中方法的实现是怎样的, 也就是InvocationHandler#invoke是如何在MapperProxy中实现的.
在这里我们了解了Mapper(无实现类)对应的实例是如何被创建的, 你也可以实现一个InvokcationHadler来为一个接口创建代理实现类实例.
public interface User {
String getName();
}
public static void main(String[] args){
User user =(User) Proxy.newProxyInstance(User.class.getClassLoader(), new Class[]{User.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return "Baby" ;
}
});
System.out.println(user.getName());
}
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
可以看到, Mapper接口Method的行为转换为MapperMethod.execute的执行. 就看MapperMethod.execute怎么转变为真正的SQL执行.
MapperMethod
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
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;
}
// ... other case
}
}
}
阅读MapperMethod的源码,可以发现有其有2个成员变量 SqlCommand 和 MethodSignature.
SqlCommand是对Mapper中Method映射的mapper statement(XML中的insert/update)的抽象.
MethodSignature则是对Mapper Method的结构的抽象, 比如 xxx(@Param("userName") String userName, @Param("age") Integer age) . MethodSignature就是对Mapper-Method参数结构的抽象, 在使用的时候会转换为执行SQL需要的参数. 在书写mybatis相关的代码会出现的常见错误: Parameter 'ew' not found. Available parameters are [pkid, param1] 就是在这里处理的,Object param = method.convertArgsToSqlCommandParam(args);会将Mapper-Method上声明的参数转换为Map(详细信息在这个方法里面已经比较清晰的描述了).
弄明白MapperMethod的结构之后, 就可以看execute里面完成了什么, 发现execute里面其实也就是将参数做了一下转换, 将Mapper Method name转换为xml中statement的ID; 具体的执行操作其实还是委托给SqlSession.insert/update/delete/select来完成的.
SqlSession#insert/update/delete/select
前文中, MapperMethod将SQL的执行委托给SqlSession执行, 这里就以update操作为例来继续深入, 在DefaultSqlSession的实现中,可以看到insert/delete其实也是转换为update操作来执行的.
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
statement: 这里的statement就是前文中SqlCommand中转换出来的statement-id. 可以在方法体里面通过这个statement-id去找对应的MappedStatement.
parameter: 也是前文中MethodSignature转换出来的参数(多个参数的时候是Map , 只有一个参数的时候就是参数本身的Value).
(?为什么这里只考虑DefaultSqlSession的实现, 还得继续看下面SqlSession&SqlSessionFactory章节)
在SqlSession#update里面,可以看到通过前面的statement-id找到对应的MappedStatement, 然后将其交给executor去执行. 这里的executor就是mybatis的执行器了(SimpleExecutor & BatchExecutor等)
Executor
BaseExecutor#update
@Override
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);
}
SimpleExecutor#doupdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
SimpleExecutor#preStatement
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;
}
可以看到在SimpleExecutor中,依然不是具体的执行的地方, 在SimpleExecutor中只是对Connection做了处理. 真正的之心操作还是交给了StatementHandler来执行.
既然操作都是交给StatementHandler来执行,那么看看StatementHandler是怎么得到的?
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Configuration#newStatementHandler
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;
}
Note: 这里的interceptorChain.pluginAll其实就是支持的mybatis拦截器机制.
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());
}
}
可以看到最终得到的是一个RoutingStatementHandler实例, 不过RoutingSatementHandler其实也只将操作委托,在构造的时候会根据MappedStaement在xml中对应的类型(STATEMENT/PREPARED/CALLABLE) 构造一个委托对象StatementHandler.
在mapper.xml中, insert/select/update/select默认的statement类型都是STATEMENT. 可以通过标签上的statementType类型进行自定义. 比如下面就定义了CALLABLE 的statement
<insert id="test" parameterType="com.xxxxxx.modules.vo.sz.test" statementType="CALLABLE">
{call app_create_check_file(
#{id,mode=INOUT,jdbcType=VARCHAR},
#{serviceVersion,mode=IN,jdbcType=VARCHAR}
)}
</insert>
所以上面在通常情况下,委托的StatementHandler都是SimpleStatementHandler.
SimpleStatementHandler
StatementHandler在SimpleExecutor#doUpdate中被调用的方法分别是:
-
prepare
-
parameterize
-
update
1. prepare: 得到JDBC API中的Statement
BaseStaementHandler#prepare
@Override
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);
}
}
SimpleExecutor#instantiaStatement
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() != null) {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
可以看到也是通过Conection.createStatement来得到的. 和JDBC操作数据库的时候是一样的.
2. parameterize
在SimpleStatementHandler中是空实现, 不需要.
在CallableStatementHandler和PreparedStatementHandler中需要进行实现.
3. update
这里终于进入到SQL的执行阶段了.
SimpleStatementHandler#update
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
可以看到这里拿着前面prepare创建的Statement进行SQL的执行操作: statement.execute(sql). 这里也和JDBC API操作数据库的时候一样.
那么这里的Sql是哪里得到的, 回过头看Sql实例化的地方:
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
可以看到boundSql是在StatementHandler构建的时候通过MappedStatement#getBoundSql(parameterObject)得到的. (这里就是Statement.execute真正执行的sql了)
具体怎么通过MappedStaement#getBoundSql(parameterObject)获取到真正执行的SQL语句, 就要看MappedStatement是怎么实现的了.
到这里,Mapper-Method怎么转换成SQL执行的过程就明白了, 下面需要只需要弄清楚怎么得到SQL语句就行了.
MappedStatement#getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
可以看到这里#getBoundSql只是对从mapper.xml获取到的原始sql进行加工, 将# { } 或者${}参数变为实际方法执行的参数.
真正原始的SQL还是通过sqlSource.getBoundSql得到的, 还得看sqlSource怎么得到的.
回溯sqlSource被赋值的地方, 可以看到是在MappedStatement.Builder中被赋值的, 查看Buidler被调用的地方. (只发现唯一一处地方, MapperBuilderAssisant#addMappedStatement)
可以看到MapperBuilderAssisant#addMappedStatement在XMLStatementBuilder和MapperAnnotationBuilder都有被调用. 很显然,这2个类分别是用来解析XML和注解方式的SQL的.
在XMLStatementBuilder可以看到使用LangDriver#createSqlSource得到XML中SQL对应的SqlSource对象.
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
进入XMLLanguageDriver的实现, 可以看到就是对mapper.xml文件的节点的读取.
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
到这里, 怎么得到mapper.xml中的原始SQL的地方也明白了. (ps:具体的xml解析就不继深入了)
SqlSession & SqlSessionFactory
在前文中知道了,Spring容器中Mapper实例的获取是交给SqlSession#getMapper来完成的.
还有Mapper-Method的执行也是委托给SqlSession#select/update/delete/insert等操作的, 然后再由SqlSession委托给mybatis Executor来执行.
在SqlSession & SqlSessionFactory体系中有这么多实现, 那么在具体使用中是使用的那个实现? 这个一般都是在Spring集成Mybatis的时候配置的. 下面是项目中使用的配置:
@Bean("sqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
return bean.getObject();
}
@Bean("sqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
所以这里的SqlSession使用的也是SqlSessionTemplate
SqlSessionTemplate
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());
}
可以看到SqlSessionTemplate是将selectOne/update/delete/insert操作委托给sqlSessionProxy变量完成的.
sqlSessionProxy是通过Jdk动态代理得到的一个代理实例. 具体的selectOne/update/delete/insert等行为还是得看InvocationHandler.invoke代理的实现逻辑.
SqlSessionInteceptor
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);
}
}
}
}
可以看到在代理实现逻辑里面,也是通过获取到一个SqlSession对象, 然后反射执行对应的方法.
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
Object result = method.invoke(sqlSession, args);
那么是怎么从哪里得到的这个用来执行的SqlSession对象?又是对应的那个类的实例?
#getSqlSession
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;
}
可以看到这里是从TransactionSynchronizationManager里面以SqlSessionFactory作为key来获取到SqlSessionHolder, 从而从里面获取到SqlSession.
TransactionSynchronizaationManager其实就是用来管理事务相关对象的线程上下文. 会将上线文中事务管理用到的JDBC Connection以及这里Mybatis用到的SqlSession放入到ThreadLocal中.
可以看到#getSqlSession中如果没有TransactionSynchronizationManager在中获取到SqlSession. 则通过SqlsessionFactory构建一个SqlSession对象, 并将其注册到上下文中. 在执行下一个SQL语句的时候就能够从TransactionSynchronizationManager获取创建的SqlSession对象了。
那么这里用来创建SqlSession的SqlSessionFactory又是从哪里得到的?是那个类的对象?
DefaultSqlSessionFactory
前面SqlSession&SqlSessionFactory配置中可以看到SqlSessionFactory是通过SqlSessonFactoryBean#getObject得到的. 这里也是Spring FactoryBean<T>的一处应用.
SqlSessionFactoryBean#getObject
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
查看赋值的地方afterPropertiesSet
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
#bulildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// 前面的大部分代码忽略
return this.sqlSessionFactoryBuilder.build(configuration);}
可以看到SqlSession是通过SqlSessionFactorBuilder构建出来的.
SqlSessionFactoryBuilder#build
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
可以看到这里最终得到的SqlSessionFactory是一个DefaultSqlSessionFactory实例。
那么继续探索DefaultSqlSessionFactory#openSession就可以得到的SqlSession是什么了.
DefaultSqlSessionFactory#openSession
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
#openSessionFromDataSource
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();
}
可以看到这里得到的最终执行的SqlSession实例对象是DefaultSqlSession的实例.
事务Transaction
在【Spring】如何完成事务传播特性中,通过了解TransactionInterceptor知道, 也是通过TransactionSynchronizationManager来维护的上下文的JDBC Connection. 那么在SqlSession中执行SQL的JDBC Connection是如何与TransactionInterceptor中的JDBC Connection关联的.
SimpleExecutor#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;
}
BaseExecutor#getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
在前面SimpleExecutor中执行SQL语句的时候,这里可以看到数据库执行操作的Connection是从Tranasaction对象中获取的. Transacion是Mybatis对数据库事务的抽象, 区别于Spring中的事务抽象.
要想知道Connection是从哪里来的,就得看这个Transaction对象是从哪里来的了?
BaseExecutor
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
可以看到Transaction是在Mybatis Executor构造的时候传递过来的, 还得继续向上追溯.
这里追溯到构建SqlSession的时候,会同时创建Excecutor对象,也会同时得到一个Transaction对象. 即前文中的如何得到的SqlSession对象代码:
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();
}
}
可以看到这里的Transaction是从TransactionFactory中获取到的, TransactionFactory也有好几个实现, 那么具体是那个实现了?
-
JdbcTransactionFactory
-
ManagedTransactionFactory
-
SpringManagedTransactionFactory
跟踪#getTransactionFactoryFromEnvironment代码, 可以看到TransactionFactory是从Environment环境变量中获取的.
#getTransactionFactoryFromEnvironment
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
那么这个environment.getTransactionFactory中的TransactionFactory又是在哪里被赋值的了?
最后追踪到SqlSessionFactoryBean#buildSqlSessionFactory中:
#buildSqlSessionFactory
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
找到如上的代码片段,最终得到DefaultSqlSession中构造的时候使用的TransactionFactory就是SpringManagedTransactionFactory.
SpringManagedTransactionFactory
找到了SpringManagedTransactionFactory,继续看它是怎么得到Mybatis的Transaction对象的.
public class SpringManagedTransactionFactory implements TransactionFactory {
/**
* {@inheritDoc}
*/
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
/**
* {@inheritDoc}
*/
@Override
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}
/**
* {@inheritDoc}
*/
@Override
public void setProperties(Properties props) {
// not needed in this version
}
}
可以看到这里直接就通过数据库DataSource构建了一个SpringManagedTransaction. 这里得到Mybatis-Transaction的代码比较简单容易理解, 下面就继续看看这个SpringManagedTransaction是如何获取JDBC Connection的.
SpringMangedTranaction#getConnection
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
#openConnection
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
在openConenction可以看到Connection是通过DataSourceUtils.getConnectoin(datasource)方式获取到的.
深入DatasourceUtils#getConenction
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
}
catch (IllegalStateException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
}
}
DataSourceUtils#doGetConenction
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
catch (RuntimeException ex) {
// Unexpected exception from external delegation call -> close Connection and rethrow.
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
可以看到这里也是使用TransactionSynchronizationManager来管理的JDBC Connection, 和TransactionInterceptor中呼应上了. 他们是使用的一套体系来管理的上下文中的Connection.