摘要:創建出的是對象,持有這個對象。根據接口名和方法名從對象的中檢查并獲取方法對應的語句解析成的對象,保存它的和命令類型。實現類攔截映射接口的自定義方法,讓去處理方法對應的解析成的。
前言
Mybatis是目前主流的Java ORM框架之一。
mybatis-spring包則是為了讓Mybatis更好得整合進Spring的衍生產品。
本文就從Mybatis和mybatis-spring源碼著手,以目前較為流行的用法,探究Mybatis的工作原理以及mybatis-spring是如何做到“迎合”Spring的。
首先在pom.xml文件中引入Mybatis包和mybatis-spring包(如果是SpringBoot,引入mybatis-spring-boot-starter即可):
org.mybatis mybatis 3.5.1 org.mybatis mybatis-spring 2.0.1
然后在Spring的配置xml文件中聲明以下bean:
classpath*:xxx/*.xml
下面我們研究每個配置的作用,進而了解mybatis-spring的工作方式。
SqlSessionFactoryBean一個FactoryBean,負責創建SqlSessionFactory,而SqlSessionFactory是創建SqlSession的工廠類。
它在初始化時會解析基本配置和XML映射文件,然后全部封裝到一個Configuration對象中。創建出的SqlSessionFactory是DefaultSqlSessionFactory對象,持有這個Configuration對象。
一般來說一個應用只需要創建一個SqlSessionFactory。
這里重點關注下XML映射文件的解析,確切的說應該是解析的結果如何處理(畢竟解析的過程太復雜)。
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); } }
常用的幾個節點:
parameterMap節點解析成ParameterMap對象保存在Configuration的parameterMaps屬性中;
resultMap節點解析成ResultMap對象保存在Configuration的resultMaps屬性中;
select|insert|update|delete節點解析成MappedStatement保存在Configuration的mappedStatements屬性中。
光解析完還不夠,還得和映射接口關聯起來。XML文件的mapper節點會有namespace屬性,它的值就是映射接口的全類名。根據全類名獲取到Class對象,然后Configuration對象中的MapperRegistry屬性負責注冊該類,就是將類對象和由它初始化的MapperProxyFactory對象組成鍵值對放入knownMappers屬性。后面創建映射接口的實現類對象時會用到。
總結下SqlSessionFactoryBean的作用,就是創建一個SqlSessionFactory類型的單例,持有所有的配置信息和解析結果。
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor,負責掃描指定包下的映射接口并向容器中注冊對應的bean。
注冊過程中有一些細節需要提一下,注冊的bean的beanClass并不是映射接口本身,而統一是MapperFactoryBean。同時MapperScannerConfigurer創建時傳入的sqlSessionFactoryBeanName所代表的SqlSessionFactory會設置到這些bean中去。
MapperFactoryBean一個FactoryBean,負責創建對應映射接口的實現類對象,這個實現類負責完成映射接口的方法和XML定義的SQL語句的映射關系。
Mybatis通過SqlSession接口執行SQL語句,所以MapperFactoryBean會在初始化時通過持有的SqlSessionFactory對象創建一個SqlSessionTemplate(它實現了SqlSession)對象。這個SqlSessionTemplate是mybatis-spring的核心,它給常規的SqlSession賦予了更多的功能,特別是迎合Spring的功能,后面會詳細描述。
我們來看一下MapperFactoryBean是如何創建映射接口的實現類對象的。
既然是FactoryBean,就是通過getObject創建需要的bean對象。跟蹤方法調用,發現最終委托給了Configuration對象中MapperRegistry屬性。上面簡述XML解析過程時已知,MapperRegistry對象的knownMappers屬性保存了映射接口的類對象和一個MapperProxyFactory對象組成的鍵值對。
MapperProxyFactory就是一個代理工廠類,它創建實現類對象的方式就是創建以映射接口為實現接口、MapperProxy為InvocationHandler的JDK動態代理。代理的邏輯都在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 ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
可以看到,我們想要實現的方法(即排除Object方法和接口的默認方法),都委托給了對應的MapperMethod去實現。方法第一次調用時,新建MapperMethod,然后放入緩存。MapperMethod包含了兩個內部類屬性:
SqlCommand:負責關聯SQL命令。根據接口名和方法名從Configuration對象的mappedStatements中檢查并獲取方法對應的SQL語句解析成的MappedStatement對象,保存它的id和SQL命令類型。
MethodSignature:負責解析和保存方法簽名信息。解析方法的參數和返回類型,保存解析后的信息。
獲取MapperMethod后就是調用它的execute方法:
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; }
方法根據SQL命令類型的不同進行不同的操作,一樣的地方是都會先把方法參數轉化為SQL參數形式,然后執行傳進execute方法的SqlSession對象(即MapperFactoryBean對象持有的SqlSessionTemplate對象)的對應的方法。
總結下MapperScannerConfigurer和MapperFactoryBean的作用:MapperScannerConfigurer負責把配置路徑下的映射接口注冊為Spring容器的MapperFactoryBean類型的bean。這個工廠bean通過代理方式創建對應映射接口的實現類對象。實現類攔截映射接口的自定義方法,讓SqlSessionTemplate去處理方法對應的SQL解析成的MappedStatement。
SqlSessionTemplate實現了SqlSession,但和SqlSession默認實現類DefaultSqlSession不同的是,它是線程安全的,這意味著一個SqlSessionTemplate實例可以在多個Dao之間共享;它和Spring的事務管理緊密關聯,可以實現多線程下各個事務之間的相互隔離;另外,它會把Mybatis返回的異常轉化為Spring的DataAccessException。下面我們來探究它是如何做到這幾點的。
SqlSessionTemplate在初始化時會通過JDK動態代理的方式創建一個實現SqlSession、以SqlSessionInterceptor為InvocationHandler的代理對象,SqlSessionTemplate的大多數方法調用都轉發給這個代理。攔截的邏輯在SqlSessionInterceptor#invoke中:
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,SqlSessionUtils#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; }
這里包含了與Spring事務關聯的邏輯。先嘗試從事務同步管理類中獲取傳入的SqlSessionFactory對象在當前線程綁定的SqlSessionHolder對象,如果存在就直接返回SqlSessionHolder對象持有的SqlSession對象,否則就用SqlSessionFactory創建一個新的SqlSession,調用DefaultSqlSessionFactory#openSessionFromDataSource,level默認是null,autoCommit默認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(); } }
可以看到最終創建了一個DefaultSqlSession對象,這里需要注意的一點是,這里創建了Transaction和Executor,在繼續往底層探索時會再提及到。
創建完之后,會根據當前線程是否存在Spring事務而選擇是否封裝成SqlSessionHolder放入事務同步管理類,這樣以來,同線程同事務下對映射接口的調用,實際工作的都是同一個SqlSession。
我們回到SqlSessionInterceptor,獲取到實際工作的DefaultSqlSession會去執行當前攔截的方法(具體我們稍后探究),如果拋出Mybatis的PersistenceException異常,初始化時設置的PersistenceExceptionTranslator對象(默認是MyBatisExceptionTranslator對象)會對異常進行轉化為DataAccessException。
總結下SqlSessionTemplate的作用,它通過動態代理對方法進行攔截,然后根據當前Spring事務狀態獲取或創建SqlSession來進行實際的工作。
DefaultSqlSession我們現在知道SqlSessionTemplate最終還是依賴一個DefaultSqlSession對象去處理映射接口方法對應的MappedStatement。下面我們以selectList方法為例探究具體的處理過程:
publicList 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(); } }
首先從configuration中獲取到MappedStatement對象,然后讓Executor對象調用query方法。
ExecutorExecutor是Mybatis的執行器,負責SQL語句的生成和查詢緩存的維護。
前面在創建DefaultSqlSession的時候,會先讓configuration創建一個Executor,根據配置的ExecutorType選擇具體的Executor實現,默認是SimpleExecutor,然后如果配置緩存開啟(默認開啟),則還要封裝成CachingExecutor。
CachingExecutor的query方法會先從MappedStatement對象動態生成sql語句,和參數一起封裝在BoundSql對象中;再根據sql、參數和返回映射等信息創建一個緩存鍵;然后檢查XML里有沒有配置二級緩存,有的話就用緩存鍵去查找,否則就執行它代理的Executor對象的query方法,先用緩存鍵去一級緩存也叫本地緩存中去查找,如果沒有的話就執行doQuery方法。不同Executor實現的doQuery有所不同,但核心都是創建一個StatementHandler,然后通過它對底層JDBC Statement進行操作,最后對查詢的結果集進行轉化。
限于篇幅,就不繼續探究StatementHandler及更底層的操作了,就再看下Mybatis是怎么管理數據庫連接的。
Transaction先回顧下這個Transaction對象是怎么來的:前面創建實際工作的DefaultSqlSession時會讓TransactionFactory對象創建一個Transactio對象作為Executor對象的屬性。而這個TransactionFactory對象,如何沒有指定的話,默認是SpringManagedTransactionFactory對象。它接受一個DataSource創建SpringManagedTransaction,可以看到這里把事務隔離級別和是否自動提交兩個參數都忽略了,那是因為mybatis-spring把事務都交給Spring去管理了。
Executor在執行doQuery方法,創建JDBC Statement對象時需要先獲取到數據庫連接:
protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
繼續看到SpringManagedTransaction,它的Connection是通過DataSourceUtils調用getConnection方法獲取的,核心邏輯在doGetConnection方法中:
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; }
可以看到,Spring的事務管理器不僅保存了事務環境下當前線程的SqlSession,還以dataSource為鍵保存了Connection。如果從事務管理器沒有獲取到,就需要通過從SpringManagedTransaction傳遞過來的dataSource獲取Connection對象,獲取到之后判斷當前是否在事務環境,是的話就把Connection對象封裝成ConnectionHolder保存在事務管理器中,這樣的話就能保證一個事務中的數據庫連接是同一個。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74587.html
摘要:第一是手動在的配置文件中使用部分來指定類路徑。第二是使用工廠的屬性。注解和樣式的配置都是支持的。在事務處理期間一個單獨的對象將會被創建和使用。創建的代理控制開放和關閉翻譯任意的異常到的異常中。每個映射器將會在指定的包路徑中遞歸地被搜索到。 mybatis-spring 若要整合spring和mybatis就需要一個插件即mybatis-spring-x.x.x.jar。具體的安裝如下所...
摘要:這個文件包含對對數據訪問進行封裝的所有類。為等提供的一致的聲明式和編程式事務管理。 SSM 環境搭建 目錄創建 pom.xml SSM 逐層配置 一、目錄 1.1 src/main/java 目錄下的包(以下包要放在項目包下,如:com.imooc.項目名) entity: 存放實體類 web: 存放controller,相當于Struts中的action service: 業務...
摘要:不是線程安全的,所以在使用的時候一定要保證他是局部變量。他對應的類圖如下有幾種常見的實現是默認的非線程安全的實現是中對的線程安全實現,在內部是使用的的形式來保證線程安全的是的核心。是線程安全的,可以被多個或映射器所共享使用。 MyBatis核心類 SqlSessionFactory 每一個MyBatis應用都是以一個SqlSessionFactory的實例為核心構建的。SqlSessi...
摘要:配置用到的框架和版本配置數據庫核心依賴依賴依賴擴展依賴層依賴相關依賴相關依賴依賴依賴的包如果你的項目中 ssm demo github : https://github.com/rongyaya10... 配置pom用到的框架和版本:spring 5.0.5.RELEASEmybatis 3.4.0mysql 5.1.35log4j 1.2.17mybatis-spring 1.3.2...
摘要:結果描述主要的問題是掃描的時候異常了,通過增加配置,確保被找到即可。 問題:ibatis.type.TypeException: Could not resolve type aliasspring-boot jar包啟動異常,idea啟動沒有任何問題 showImg(https://segmentfault.com/img/bV7ycX?w=643&h=409); pom信息: ...
閱讀 2781·2021-11-19 11:30
閱讀 3066·2021-11-15 11:39
閱讀 1787·2021-08-03 14:03
閱讀 1996·2019-08-30 14:18
閱讀 2052·2019-08-30 11:16
閱讀 2163·2019-08-29 17:23
閱讀 2607·2019-08-28 18:06
閱讀 2540·2019-08-26 12:22