文章首發於blog.csdn.net/doujinlong1…java
在spring中,dao層大多都是用Mybatis,那麼mysql
在之前對Mybatis的源碼解讀中,咱們知道,Mybatis利用了動態代理來作,最後實現的類是MapperProxy,在最後執行具體的方法時,實際上執行的是:spring
@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);
}
複製代碼
最重要的一步:sql
mapperMethod.execute(sqlSession, args);
複製代碼
這裏的sqlSession 實際上是在Spring的配置時設置的 sqlSessionTemplate,隨便對其中的一個進行跟進:能夠在sqlSessionTemplate類中發現很好這樣的方法,用來執行具體的sql,如:編程
@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
複製代碼
這一步就是最後執行的方法,那麼問題來了 sqlSessionProxy 究竟是啥呢? 這又得回到最開始。緩存
其中SqlSessionTemplate是生成sqlSession的模版,來看他的注入過程(註解形式注入):session
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
複製代碼
在這個初始化過程當中:mybatis
/**
* Constructs a Spring managed {@code SqlSession} with the given
* {@code SqlSessionFactory} and {@code ExecutorType}.
* A custom {@code SQLExceptionTranslator} can be provided as an
* argument so any {@code PersistenceException} thrown by MyBatis
* can be custom translated to a {@code RuntimeException}
* The {@code SQLExceptionTranslator} can also be null and thus no
* exception translation will be done and MyBatis exceptions will be
* thrown
*
* @param sqlSessionFactory
* @param executorType
* @param exceptionTranslator
*/
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());
複製代碼
}app
最後一步比較重要,用java動態代理生成了一個sqlSessionFactory。代理的類是:ide
/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got
* from Spring's Transaction Manager
* It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
* pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
*/
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執行sql的時候就會用這個代理類。isSqlSessionTransactional 這個會判斷是否是有Transactional,沒有則直接提交。若是有則不提交,在最外層進行提交。
其中
getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);
複製代碼
這個方法用來獲取sqlSession。具體實現以下:
/**
* Gets an SqlSession from Spring Transaction Manager or creates a new one if needed.
* Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one.
* Then, it synchronizes the SqlSession with the transaction if Spring TX is active and
* <code>SpringManagedTransactionFactory</code> is configured as a transaction manager.
*
* @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions
* @param executorType The executor type of the SqlSession to create
* @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
* @throws TransientDataAccessResourceException if a transaction is active and the
* {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
* @see SpringManagedTransactionFactory
*/
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;
}
複製代碼
這個sqlSession的建立其實看他方法解釋就夠了,「從Spring事務管理器中獲取一個sqlsession,若是沒有,則建立一個新的」,這句話的意思其實就是若是有事務,則sqlSession用一個,若是沒有,就給你個新的咯。 再通俗易懂一點:**若是在事務裏,則Spring給你的sqlSession是一個,不然,每個sql給你一個新的sqlSession。**這裏生成的sqlSession其實就是DefaultSqlSession了。後續可能仍然有代理,如Mybatis分頁插件等,不在這次討論的範圍內。
在2中,咱們看到若是是事務,sqlSession 同樣,若是不是,則每次都不同,且每次都會提交。這是最重要的。
sqlSession,顧名思義,就是sql的一個會話,在這個會話中發生的事不影響別的會話,若是會話提交,則生效,不提交不生效。
來看下sqlSession 這個接口的介紹。
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
* 爲Mybatis工做最重要的java接口,經過這個接口來執行命令,獲取mapper以及管理事務
* @author Clinton Begin
*/
複製代碼
註釋很明白了,來一一看看怎麼起的這些做用。
在第一個小標題中 執行sql最重要的方法就是 this.sqlSessionProxy. selectOne(statement, parameter); 這個方法,而在第二個小標題中咱們看到是經過代理來執行的,最後實際上沒有事務則提交sql。這就是執行sql的基本動做了。獲取sqlsession,提交執行Sql。
在咱們平常的代碼中可能不會這麼寫,可是實際上,若是必要咱們是能夠這麼作的,如:
XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
複製代碼
通常狀況下,若是要這麼作,首先須要注入 sqlSessionFactory,而後利用
sqlSessionFactory.openSession()。
複製代碼
便可獲取session。
####3.3,事務管理 ####
上面我一直提到一點,sqlSession 那個代理類裏有個操做,判斷這個是否是事務管理的sqlSession,若是是,則不提交,不是才提交,這個就是事務管理了,那麼有個問題,在哪裏提交這個事務呢????
Spring中,若是一個方法被 @Transactional 註解標註,在生效的狀況下(不生效的狀況見我寫動態代理的那篇博客),則最終會被TransactionInterceptor 這個類所代理,執行的方法其實是這樣的:
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
複製代碼
繼續看invokeWithinTransaction這個方法:
/**
* General delegate for around-advice-based subclasses, delegating to several other template
* methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
* as well as regular {@link PlatformTransactionManager} implementations.
* @param method the Method being invoked
* @param targetClass the target class that we're invoking the method on
* @param invocation the callback to use for proceeding with the target invocation
* @return the return value of the method, if any
* @throws Throwable propagated from the target invocation
*/
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
//基本上咱們的事務管理器都不是一個CallbackPreferringPlatformTransactionManager,因此基本上都是會從這個地方進入,下面的else狀況暫不討論。
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//獲取具體的TransactionInfo ,若是要用編程性事務,則把這塊的代碼能夠借鑑一下。
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation(); //執行被@Transactional標註裏面的具體方法。
}
catch (Throwable ex) {
// target invocation exception
//異常狀況下,則直接完成了,由於在sqlsession執行完每一條指令都沒有提交事務,因此表現出來的就是回滾事務。
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//正常執行完成的提交事務方法 跟進能夠看到實際上執行的是:(編程性事務的提交)
// ==============txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());===========
commitTransactionAfterReturning(txInfo);
return retVal;
}
// =======================else狀況不討論================================
else {
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
return new ThrowableHolder(ex);
}
}
finally {
cleanupTransactionInfo(txInfo);
}
}
});
// Check result: It might indicate a Throwable to rethrow.
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
}
else {
return result;
}
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
複製代碼
其實,Mybatis的一級緩存就是 SqlSession 級別的,只要SqlSession 不變,則默認緩存生效,也就是說,以下的代碼,實際上只會查一次庫的:
XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
//對應的sql爲: select id from test_info;
xxxxxMapper.selectFromDb();
xxxxxMapper.selectFromDb();
xxxxxMapper.selectFromDb();
複製代碼
實際上只會被執行一次,感興趣的朋友們能夠試試。
可是,在平常使用中,咱們都是使用spring來管理Mapper,在執行selectFromDb 這個操做的時候,其實每次都會有一個新的SqlSession,因此,Mybatis的一級緩存是用不到的。