1. 类讲解
1.1. org.mybatis.spring.SqlSessionFactoryBean
1.1.1. 回顾
前面简单的讲过了SqlSessionFactoryBean,用于构造SqlSessionFactory的FactoryBean,其实mapper.xml 文件的解析也是由这个类完成的;
1.1.2. afterPropertiesSet
最后一行代码是:this.sqlSessionFactory = buildSqlSessionFactory();其中分包括解析mapper.xml生成相关对象的逻辑;
1.1.3. buildSqlSessionFactory
构建SqlSessionFactory的核心业务逻辑:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// ......
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
}
}
这段解析mapper.xml的核心代码,主要业务逻辑交给XMLMapperBuilder来完成了,先是一个对象构造,然后一次parse()函数调用;
1.2. org.apache.ibatis.builder.xml.XMLMapperBuilder
看一下核心代码:
public class XMLMapperBuilder extends BaseBuilder {
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
上面这一部分是SqlSessionFactoryBean的buildSqlSessionFactory()方法调用的核心部分;
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
}
configurationElement函数里的“builderAssistant.setCurrentNamespace(namespace);”,其中,builderAssistant是org.apache.ibatis.builder.MapperBuilderAssistant的类对象;
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
}
bindMapperForNamespace()函数是XMLMapperBuilder类里需要重点讲的一个函数,“boundType = Resources.classForName(namespace);”,这一句是把namespace视为一个类的全限定名,试图加载一个这样的类:
1.3. org.apache.ibatis.session.Configuration
public class Configuration {
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
}
Configuration类保存了所有Mybatis的配置信息,一般情况下Mybatis在运行过程中只会创建一个Configration对象,并且配置信息不能再被修改。
2. 一次基于SqlSession的查询过程
2.1. 代码
2.1.1 所使用的mapper代码片断
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="aa" >
<resultMap id="BaseResultMap" type="com.jd.dis.entity.po.pay.PayOdr" >
<id column="id" property="id" jdbcType="BIGINT" />
<!--......-->
</resultMap>
<sql id="Base_Column_List" >
<!--......-->
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long" >
select
<include refid="Base_Column_List" />
from pay_odr
where id = #{id,jdbcType=BIGINT}
</select>
</mapper>
这里mapper的namespace随便设置一个值,它不是一个类的全限定名;
2.1.2 所使用的java代码片断
@Service
public class PayServiceImpl implements PayService {
@Autowired
private SqlSession sqlSession;
@Override
@UseDataSource("pay")
public APIResponse<PayOdr> getPayOrder(Long id) {
PayOdr payOrder = this.sqlSession.selectOne("aa.selectByPrimaryKey", id);
return APIResponse.successResp(payOrder);
}
}
2.2. 执行查询过程
2.2.1 org.apache.ibatis.session.SqlSession
2.2.2 Service层使用SqlSession查询
在上面的业务代码里使用SqlSession查询的地方打断点,step into进去:
2.2.3 org.mybatis.spring.SqlSessionTemplate
会调用SqlSessionTemplate中的查询方法,可以看到进入到了org.mybatis.spring.SqlSessionTemplate,它对应“mybatis-spring:1.3.2”(实际的jar包是:mybatis-spring-1.3.2.jar)。Spring喜欢对各种第三方框架封装出*Template,看到这样一个类,也不要奇怪。
看看SqlSessionTemplate的部分代码:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(
sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
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());
}
@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
}
上面断点里可以看到,执行sql的动作是交给sqlSessionProxy来完成的,扫一下这个类里代码的其它部分,可以发现,实际上在这个spring封装的template中,所有的sql执行都是交给sqlSessionProxy来完成的,它在类里被定义为是final的;
sqlSessionProxy虽然被定义为final的,但它实际代表的实现了SqlSession接口的类对象却是“经常性”变动的,只是在一次sql语句执行的过程中是不变的,所有不同的sql执行过程所使用的是同一个SqlSessionTemplate;
sqlSessionProxy是一个代理类强转换过来的SqlSession接口的实现,不同线程的调用,此对象的引用会不同,调用代理类对象sqlSessionProxy所实现的SqlSession接口的方法,会引起对SqlSessionInterceptor类的invoke方法的调用;
关注一下SqlSessionInterceptor:
2.2.3.1 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
// ......
import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;
import static org.mybatis.spring.SqlSessionUtils.getSqlSession;
import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;
// ......
public class SqlSessionTemplate implements SqlSession, DisposableBean {
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);
}
}
}
}
}
SqlSessionInterceptor可以认为是Spring整合MyBatis的核心实现,是SqlSessionTemplate能成为Spring容器管理的Bean并能对外提供线程安全的SqlSession服务的坚强依赖;
2.2.4 org.mybatis.spring.SqlSessionUtils
前面讲的SqlSessionTemplate类使用到了SqlSessionUtils中的静态方法,来简单看看:
public final class SqlSessionUtils {
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//从从前线程的threadLocal 中获取sqlSessionHolder
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;
}
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
}
//增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加
holder.requested();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
}
session = holder.getSqlSession();
}
return session;
}
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
//如果当前会话处在事务当中,则将holder 绑定到ThreadLocal 中
//以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
//将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
//设置当前holder和当前事务同步
holder.setSynchronizedWithTransaction(true);
//holder 引用次数+1
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
}
}
2.2.5 org.mybatis.spring.SqlSessionHolder
public final class SqlSessionHolder extends ResourceHolderSupport {
private final SqlSession sqlSession;
public SqlSession getSqlSession() {
return sqlSession;
}
}
一个持有SqlSession等对象实例的计数器,继承自ResourceHolderSupport;
2.2.6 org.springframework.transaction.support.ResourceHolderSupport
public abstract class ResourceHolderSupport implements ResourceHolder {
private boolean synchronizedWithTransaction = false;
private boolean rollbackOnly = false;
@Nullable
private Date deadline;
private int referenceCount = 0;
private boolean isVoid = false;
}
2.2.7 org.springframework.transaction.support.TransactionSynchronizationManager
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
/**
* Actually check the value of the resource that is bound for the given key.
*/
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
// 当前线程事务同步机制是否是激活状态
public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
}
}
2.2.7.1 resources
private static final ThreadLocal<Map<Object, Object>> resources
保存当前线程所用的数据操作资源,存储到Map里,当时是间接持有,来看一个程序运行中当前线程中map的数据:
其中,有一个键值对,DefaultSqlSessionFactory对象为key,值为SqlSesseionHolder对象,通过SqlSesseionHolder可以获取SqlSession;
2.2.8 org.springframework.transaction.support.TransactionSynchronizationUtils
public abstract class TransactionSynchronizationUtils {
static Object unwrapResourceIfNecessary(Object resource) {
Assert.notNull(resource, "Resource must not be null");
Object resourceRef = resource;
// unwrap infrastructure proxy
if (resourceRef instanceof InfrastructureProxy) {
resourceRef = ((InfrastructureProxy) resourceRef).getWrappedObject();
}
if (aopAvailable) {
// now unwrap scoped proxy
resourceRef = ScopedProxyUnwrapper.unwrapIfNecessary(resourceRef);
}
return resourceRef;
}
private static class ScopedProxyUnwrapper {
public static Object unwrapIfNecessary(Object resource) {
if (resource instanceof ScopedObject) {
return ((ScopedObject) resource).getTargetObject();
}
else {
return resource;
}
}
}
}
2.2.9 org.springframework.core.InfrastructureProxy
/**
* Interface to be implemented by transparent resource proxies that need to be
* considered as equal to the underlying resource, for example for consistent
* lookup key comparisons. Note that this interface does imply such special
* semantics and does not constitute a general-purpose mixin!
*
* <p>Such wrappers will automatically be unwrapped for key comparisons in
* {@link org.springframework.transaction.support.TransactionSynchronizationManager}.
*
* <p>Only fully transparent proxies, e.g. for redirection or service lookups,
* are supposed to implement this interface. Proxies that decorate the target
* object with new behavior, such as AOP proxies, do <i>not</i> qualify here!
*
* @author Juergen Hoeller
* @since 2.5.4
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public interface InfrastructureProxy {
/**
* Return the underlying resource (never {@code null}).
*/
Object getWrappedObject();
}
透明资源代理要实现的接口,为了被认为等同于底层资源,例如用于一致的查找的关键字比较。
注意,这个接口意味着这样的特殊语义,并不构成一般用途的聚合!
这种包装器将在TransactionSynchronizationManager中自动被拆箱来进行关键字比较。
只有完全透明的代理,例如 对于重定向或服务查找,应该实现此接口。 使用新行为来装饰目标对象的代理(例如AOP代理)不适用于此处!
也就是相当于对象本身等同于所封装的内部对象。
2.2.10 org.apache.ibatis.session.SqlSessionFactory
public abstract class TransactionSynchronizationManager {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
生产SqlSession对象的工厂接口;
2.2.11 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
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();
}
}
}
SqlSessionFactory接口的默认实现;
2.2.12 org.apache.ibatis.executor.Executor
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
2.2.13 org.apache.ibatis.executor.BaseExecutor
public abstract class BaseExecutor implements Executor {
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
// ......
}
2.2.14 org.apache.ibatis.executor.ReuseExecutor
public class ReuseExecutor extends BaseExecutor {
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
// ......
}
3. 事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();