Mybatis的核心——SqlSession解读
在spring中,dao層大多都是用Mybatis,那么
1,Mybatis執行sql最重要的是什么?
在以前對Mybatis的源碼解讀中,我們知道,Mybatis利用了動態代理來做,最后實現的類是MapperProxy,在最后執行具體的方法時,實際上執行的是:
@Overridepublic 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);}最重要的一步:
mapperMethod.execute(sqlSession, args);這里的sqlSession 其實是在Spring的配置時設置的 sqlSessionTemplate,隨便對其中的一個進行跟進:可以在sqlSessionTemplate類中發現很好這樣的方法,用來執行具體的sql,如:
@Overridepublic <T> T selectOne(String statement, Object parameter) {return this.sqlSessionProxy.<T> selectOne(statement, parameter);}這一步就是最后執行的方法,那么問題來了 sqlSessionProxy 到底是啥呢? 這又得回到最開始。
2,使用mybatis連接mysql時一般都是需要注入SqlSessionFactory,SqlSessionTemplate,PlatformTransactionManager。
其中SqlSessionTemplate是生成sqlSession的模版,來看他的注入過程(注解形式注入):
@Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory); }在這個初始化過程中:
/** * 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());}
最后一步比較重要,用java動態代理生成了一個sqlSessionFactory。代理的類是:
/*** 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 #22closeSqlSession(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分頁插件等,不在此次討論的范圍內。
3,第二步的 sqlSession 一樣不一樣到底有什么影響?
在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*/注釋很明白了,來一一看看怎么起的這些作用。
3.1,執行命令。
在第一個小標題中 執行sql最重要的方法就是 this.sqlSessionProxy. selectOne(statement, parameter); 這個方法,而在第二個小標題中我們看到是通過代理來執行的,最后實際上沒有事務則提交sql。這就是執行sql的基本動作了。獲取sqlsession,提交執行Sql。
3.2,獲取mapper。
在我們日常的代碼中可能不會這么寫,但是實際上,如果必要我們是可以這么做的,如:
XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);一般情況下,如果要這么做,首先需要注入 sqlSessionFactory,然后利用
sqlSessionFactory.openSession()。即可獲取session。
3.3,事務管理
上面我一直提到一點,sqlSession 那個代理類里有個操作,判斷這個是不是事務管理的sqlSession,如果是,則不提交,不是才提交,這個就是事務管理了,那么有個問題,在哪里提交這個事務呢????
4,事務從哪里攔截,就從哪里提交
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() {@Overridepublic 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>() {@Overridepublic 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();}} }5,小結,SqlSession 還在別的地方有用到嗎?
其實,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的一級緩存是用不到的。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Mybatis的核心——SqlSession解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cglib动态代理和jdk动态代理的区别
- 下一篇: JAVA知识总结目录