Mybaits 源碼解析 (十一)—– 設計模式精妙使用:靜態代理和動態代理結合使用:@MapperScan將Mapper介面生成代理注入到Spring
- 2019 年 11 月 12 日
- 筆記
上一篇文章我們講了SqlSessionFactoryBean,通過這個FactoryBean創建SqlSessionFactory並註冊進Spring容器,這篇文章我們就講剩下的部分,通過MapperScannerConfigurer將Mapper介面生成代理注入到Spring
掃描Mapper介面
我們上一篇文章介紹了掃描Mapper介面有兩種方式,一種是通過bean.xml註冊MapperScannerConfigurer對象,一種是通過@MapperScan(“com.chenhao.mapper”)註解的方式,如下
方式一:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.chenhao.mapper" /> </bean>
方式二:
@Configuration @MapperScan("com.chenhao.mapper") public class AppConfig {
@MapperScan
我們來看看@MapperScan這個註解
@Import(MapperScannerRegistrar.class) public @interface MapperScan {
MapperScan使用@Import
將MapperScannerRegistrar
導入。
MapperScannerRegistrar
1 public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { 2 private ResourceLoader resourceLoader; 3 @Override 4 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 5 // 獲取MapperScan 註解,如@MapperScan("com.chenhao.mapper") 6 AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); 7 // 創建路徑掃描器,下面的一大段都是將MapperScan 中的屬性設置到ClassPathMapperScanner ,做的就是一個set操作 8 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); 9 // this check is needed in Spring 3.1 10 if (resourceLoader != null) { 11 // 設置資源載入器,作用:掃描指定包下的class文件。 12 scanner.setResourceLoader(resourceLoader); 13 } 14 Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); 15 if (!Annotation.class.equals(annotationClass)) { 16 scanner.setAnnotationClass(annotationClass); 17 } 18 Class<?> markerInterface = annoAttrs.getClass("markerInterface"); 19 if (!Class.class.equals(markerInterface)) { 20 scanner.setMarkerInterface(markerInterface); 21 } 22 Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); 23 if (!BeanNameGenerator.class.equals(generatorClass)) { 24 scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); 25 } 26 Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); 27 if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { 28 scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass)); 29 } 30 scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); 31 //設置SqlSessionFactory的名稱 32 scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); 33 List<String> basePackages = new ArrayList<String>(); 34 //獲取配置的包路徑,如com.chenhao.mapper 35 for (String pkg : annoAttrs.getStringArray("value")) { 36 if (StringUtils.hasText(pkg)) { 37 basePackages.add(pkg); 38 } 39 } 40 for (String pkg : annoAttrs.getStringArray("basePackages")) { 41 if (StringUtils.hasText(pkg)) { 42 basePackages.add(pkg); 43 } 44 } 45 for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) { 46 basePackages.add(ClassUtils.getPackageName(clazz)); 47 } 48 // 註冊過濾器,作用:什麼類型的Mapper將會留下來。 49 scanner.registerFilters(); 50 // 掃描指定包 51 scanner.doScan(StringUtils.toStringArray(basePackages)); 52 } 53 }
ClassPathMapperScanner
接著我們來看看掃描過程 scanner.doScan(StringUtils.toStringArray(basePackages));
1 @Override 2 public Set<BeanDefinitionHolder> doScan(String... basePackages) { 3 //調用父類進行掃描,並將basePackages下的class都封裝成BeanDefinitionHolder,並註冊進Spring容器的BeanDefinition 4 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); 5 6 if (beanDefinitions.isEmpty()) { 7 logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); 8 } else { 9 //繼續對beanDefinitions做處理,額外設置一些屬性 10 processBeanDefinitions(beanDefinitions); 11 } 12 13 return beanDefinitions; 14 } 15 16 protected Set<BeanDefinitionHolder> doScan(String... basePackages) { 17 Assert.notEmpty(basePackages, "At least one base package must be specified"); 18 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); 19 //遍歷basePackages進行掃描 20 for (String basePackage : basePackages) { 21 //找出匹配的類 22 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); 23 for (BeanDefinition candidate : candidates) { 24 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); 25 candidate.setScope(scopeMetadata.getScopeName()); 26 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); 27 if (candidate instanceof AbstractBeanDefinition) { 28 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); 29 } 30 if (candidate instanceof AnnotatedBeanDefinition) { 31 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); 32 } 33 if (checkCandidate(beanName, candidate)) { 34 //封裝成BeanDefinitionHolder 對象 35 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); 36 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); 37 beanDefinitions.add(definitionHolder); 38 //將BeanDefinition對象注入spring的BeanDefinitionMap中,後續getBean時,就是從BeanDefinitionMap獲取對應的BeanDefinition對象,取出其屬性進行實例化Bean 39 registerBeanDefinition(definitionHolder, this.registry); 40 } 41 } 42 } 43 return beanDefinitions; 44 }
我們重點看下第4行和第10行程式碼,第4行是調用父類的doScan方法,獲取basePackages下的所有Class,並將其生成BeanDefinition,注入spring的BeanDefinitionMap中,也就是Class的描述類,在調用getBean方法時,獲取BeanDefinition進行實例化。此時,所有的Mapper介面已經被生成了BeanDefinition。接著我們看下第10行,對生成的BeanDefinition做一些額外的處理。
processBeanDefinitions
上面BeanDefinition已經注入進Spring容器了,接著我們看對BeanDefinition進行哪些額外的處理
1 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { 2 GenericBeanDefinition definition; 3 for (BeanDefinitionHolder holder : beanDefinitions) { 4 definition = (GenericBeanDefinition) holder.getBeanDefinition(); 5 6 if (logger.isDebugEnabled()) { 7 logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 8 + "' and '" + definition.getBeanClassName() + "' mapperInterface"); 9 } 10 11 // 設置definition構造器的輸入參數為definition.getBeanClassName(),這裡就是com.chenhao.mapper.UserMapper 12 // 那麼在getBean實例化時,通過反射調用構造器實例化時要將這個參數傳進去 13 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()) 14 // 修改definition對應的Class 15 // 看過Spring源碼的都知道,getBean()返回的就是BeanDefinitionHolder中beanClass屬性對應的實例 16 // 所以我們後面ac.getBean(UserMapper.class)的返回值也就是MapperFactoryBean的實例 17 // 但是最終被注入到Spring容器的對象的並不是MapperFactoryBean的實例,根據名字看,我們就知道MapperFactoryBean實現了FactoryBean介面 18 // 那麼最終注入Spring容器的必定是從MapperFactoryBean的實例的getObject()方法中返回 19 definition.setBeanClass(this.mapperFactoryBean.getClass()); 20 21 definition.getPropertyValues().add("addToConfig", this.addToConfig); 22 23 boolean explicitFactoryUsed = false; 24 if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { 25 definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); 26 explicitFactoryUsed = true; 27 } else if (this.sqlSessionFactory != null) { 28 //往definition設置屬性值sqlSessionFactory,那麼在MapperFactoryBean實例化後,進行屬性賦值時populateBean(beanName, mbd, instanceWrapper);,會通過反射調用sqlSessionFactory的set方法進行賦值 29 //也就是在MapperFactoryBean創建實例後,要調用setSqlSessionFactory方法將sqlSessionFactory注入進MapperFactoryBean實例 30 definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); 31 explicitFactoryUsed = true; 32 } 33 34 if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { 35 if (explicitFactoryUsed) { 36 logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); 37 } 38 definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); 39 explicitFactoryUsed = true; 40 } else if (this.sqlSessionTemplate != null) { 41 if (explicitFactoryUsed) { 42 logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); 43 } 44 definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); 45 explicitFactoryUsed = true; 46 } 47 48 if (!explicitFactoryUsed) { 49 if (logger.isDebugEnabled()) { 50 logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); 51 } 52 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); 53 } 54 } 55 }
看第19行,將definition的beanClass屬性設置為MapperFactoryBean.class,我們知道,在getBean的時候,會通過反射創建Bean的實例,也就是創建beanClass的實例,如下Spring的getBean的部分程式碼:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { // 沒有覆蓋 // 直接使用反射實例化即可 if (!bd.hasMethodOverrides()) { // 重新檢測獲取下構造函數 // 該構造函數是經過前面 N 多複雜過程確認的構造函數 Constructor<?> constructorToUse; synchronized (bd.constructorArgumentLock) { // 獲取已經解析的構造函數 constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod; // 如果為 null,從 class 中解析獲取,並設置 if (constructorToUse == null) { final Class<?> clazz = bd.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { if (System.getSecurityManager() != null) { constructorToUse = AccessController.doPrivileged( (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor); } else { //利用反射獲取構造器 constructorToUse = clazz.getDeclaredConstructor(); } bd.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Throwable ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } } // 通過BeanUtils直接使用構造器對象實例化bean return BeanUtils.instantiateClass(constructorToUse); } else { // 生成CGLIB創建的子類對象 return instantiateWithMethodInjection(bd, beanName, owner); } }
看到沒,是通過bd.getBeanClass();從BeanDefinition中獲取beanClass屬性,然後通過反射實例化Bean,如上,所有的Mapper介面掃描封裝成的BeanDefinition的beanClass都設置成了MapperFactoryBean,我們知道在Spring初始化的最後,會獲取所有的BeanDefinition,並通過getBean創建所有的實例注入進Spring容器,那麼意思就是說,在getBean時,創建的的所有Mapper對象是MapperFactoryBean這個對象了。
我們看下第13行處,設置了BeanDefinition構造器參數,那麼當getBean中通過構造器創建實例時,需要將設置的構造器參數definition.getBeanClassName(),這裡就是com.chenhao.mapper.UserMapper傳進去。
還有一個點要注意,在第30行處,往BeanDefinition的PropertyValues設置了sqlSessionFactory,那麼在創建完MapperFactoryBean的實例後,會對MapperFactoryBean進行屬性賦值,也就是Spring創建Bean的這句程式碼,populateBean(beanName, mbd, instanceWrapper);,這裡會通過反射調用MapperFactoryBean的setSqlSessionFactory方法將sqlSessionFactory注入進MapperFactoryBean實例,所以我們接下來重點看的就是MapperFactoryBean這個對象了。
MapperFactoryBean
接下來我們看最重要的一個類MapperFactoryBean
//繼承SqlSessionDaoSupport、實現FactoryBean,那麼最終注入Spring容器的對象要從getObject()中取 public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; private boolean addToConfig = true; public MapperFactoryBean() { } //構造器,我們上一節中在BeanDefinition中已經設置了構造器輸入參數 //所以在通過反射調用構造器實例化時,會獲取在BeanDefinition設置的構造器輸入參數 //也就是對應得每個Mapper介面Class public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } protected void checkDaoConfig() { super.checkDaoConfig(); Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = this.getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { configuration.addMapper(this.mapperInterface); } catch (Exception var6) { this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6); throw new IllegalArgumentException(var6); } finally { ErrorContext.instance().reset(); } } } //最終注入Spring容器的就是這裡的返回對象 public T getObject() throws Exception { //獲取父類setSqlSessionFactory方法中創建的SqlSessionTemplate //通過SqlSessionTemplate獲取mapperInterface的代理類 //我們例子中就是通過SqlSessionTemplate獲取com.chenhao.mapper.UserMapper的代理類 //獲取到Mapper介面的代理類後,就把這個Mapper的代理類對象注入Spring容器 return this.getSqlSession().getMapper(this.mapperInterface); } public Class<T> getObjectType() { return this.mapperInterface; } public boolean isSingleton() { return true; } public void setMapperInterface(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; } public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; } public boolean isAddToConfig() { return this.addToConfig; } } public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public SqlSessionDaoSupport() { } //還記得上一節中我們往BeanDefinition中設置的sqlSessionFactory這個屬性嗎? //在實例化MapperFactoryBean後,進行屬性賦值時,就會通過反射調用setSqlSessionFactory public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { //創建一個SqlSessionTemplate並賦值給sqlSession this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; } public SqlSession getSqlSession() { return this.sqlSession; } protected void checkDaoConfig() { Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }
我們看到MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean,那麼getBean獲取的對象是從其getObject()中獲取,並且MapperFactoryBean是一個單例,那麼其中的屬性SqlSessionTemplate對象也是一個單例,全局唯一,供所有的Mapper代理類使用。
這裡我大概講一下getBean時,這個類的過程:
1、MapperFactoryBean通過反射調用構造器實例化出一個對象,並且通過上一節中definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName())設置的構造器參數,在構造器實例化時,傳入Mapper介面的Class,並設置為MapperFactoryBean的mapperInterface屬性。
2、進行屬性賦值,通過上一節中definition.getPropertyValues().add(“sqlSessionFactory”, this.sqlSessionFactory);設置的屬性值,在populateBean屬性賦值過程中通過反射調用setSqlSessionFactory方法,並創建SqlSessionTemplate對象設置到sqlSession屬性中。
3、由於MapperFactoryBean實現了FactoryBean,最終註冊進Spring容器的對象是從getObject()方法中取,接著獲取SqlSessionTemplate這個SqlSession調用getMapper(this.mapperInterface);生成Mapper介面的代理對象,將Mapper介面的代理對象註冊進Spring容器
至此,所有com.chenhao.mapper中的Mapper介面都生成了代理類,並注入到Spring容器了。接著我們就可以在Service中直接從Spring的BeanFactory中獲取了,如下
SqlSessionTemplate
還記得我們前面分析Mybatis源碼時,獲取的SqlSession實例是什麼嗎?我們簡單回顧一下
/** * ExecutorType 指定Executor的類型,分為三種:SIMPLE, REUSE, BATCH,默認使用的是SIMPLE * TransactionIsolationLevel 指定事務隔離級別,使用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); // 創建Executor,即執行器 // 它是真正用來Java和資料庫交互操作的類,後面會展開說。 final Executor executor = configuration.newExecutor(tx, execType); // 創建DefaultSqlSession對象返回,其實現了SqlSession介面 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
大家應該還有印象,就是上面的DefaultSqlSession,那上一節的SqlSessionTemplate是什麼鬼???我們來看看
// 實現SqlSession介面,單例、執行緒安全,使用spring的事務管理器的sqlSession, // 具體的SqlSession的功能,則是通過內部包含的sqlSessionProxy來來實現,這也是靜態代理的一種實現。 // 同時內部的sqlSessionProxy實現InvocationHandler介面,則是動態代理的一種實現,而執行緒安全也是在這裡實現的。 // 注意mybatis默認的sqlSession不是執行緒安全的,需要每個執行緒有一個單例的對象實例。 // SqlSession的主要作用是提供SQL操作的API,執行指定的SQL語句,mapper需要依賴SqlSession來執行其方法對應的SQL。 public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; // 一個代理類,由於SqlSessionTemplate為單例的,被所有mapper,所有執行緒共享, // 所以sqlSessionProxy要保證這些mapper中方法調用的執行緒安全特性: // sqlSessionProxy的實現方式主要為實現了InvocationHandler介面實現了動態代理, // 由動態代理的知識可知,InvocationHandler的invoke方法會攔截所有mapper的所有方法調用, // 故這裡的實現方式是在invoke方法內部創建一個sqlSession局部變數,從而實現了每個mapper的每個方法調用都使用 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) { Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); Assert.notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor()); } public SqlSessionFactory getSqlSessionFactory() { return this.sqlSessionFactory; } public ExecutorType getExecutorType() { return this.executorType; } public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { return this.exceptionTranslator; } public <T> T selectOne(String statement) { //由真實的對象sqlSessionProxy執行查詢 return this.sqlSessionProxy.selectOne(statement); } public <T> T selectOne(String statement, Object parameter) { //由真實的對象sqlSessionProxy執行查詢 return this.sqlSessionProxy.selectOne(statement, parameter); } public <K, V> Map<K, V> selectMap(String statement, String mapKey) { //由真實的對象sqlSessionProxy執行查詢 return this.sqlSessionProxy.selectMap(statement, mapKey); } public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) { //由真實的對象sqlSessionProxy執行查詢 return this.sqlSessionProxy.selectMap(statement, parameter, mapKey); } public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { //由真實的對象sqlSessionProxy執行查詢 return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); } public <T> Cursor<T> selectCursor(String statement) { return this.sqlSessionProxy.selectCursor(statement); } public <T> Cursor<T> selectCursor(String statement, Object parameter) { return this.sqlSessionProxy.selectCursor(statement, parameter); } public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds); } public <E> List<E> selectList(String statement) { return this.sqlSessionProxy.selectList(statement); } public <E> List<E> selectList(String statement, Object parameter) { return this.sqlSessionProxy.selectList(statement, parameter); } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.selectList(statement, parameter, rowBounds); } public void select(String statement, ResultHandler handler) { this.sqlSessionProxy.select(statement, handler); } public void select(String statement, Object parameter, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, handler); } public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); } public int insert(String statement) { return this.sqlSessionProxy.insert(statement); } public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); } public int update(String statement) { return this.sqlSessionProxy.update(statement); } public int update(String statement, Object parameter) { return this.sqlSessionProxy.update(statement, parameter); } public int delete(String statement) { return this.sqlSessionProxy.delete(statement); } public int delete(String statement, Object parameter) { return this.sqlSessionProxy.delete(statement, parameter); } public <T> T getMapper(Class<T> type) { return this.getConfiguration().getMapper(type, this); } public void commit() { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } public void commit(boolean force) { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } public void rollback() { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } public void rollback(boolean force) { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } public void close() { throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); } public void clearCache() { this.sqlSessionProxy.clearCache(); } public Configuration getConfiguration() { return this.sqlSessionFactory.getConfiguration(); } public Connection getConnection() { return this.sqlSessionProxy.getConnection(); } public List<BatchResult> flushStatements() { return this.sqlSessionProxy.flushStatements(); } public void destroy() throws Exception { } private class SqlSessionInterceptor implements InvocationHandler { private SqlSessionInterceptor() { } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped; try { Object result = method.invoke(sqlSession, args); if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { sqlSession.commit(true); } unwrapped = result; } catch (Throwable var11) { unwrapped = ExceptionUtil.unwrapThrowable(var11); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped); if (translated != null) { unwrapped = translated; } } throw (Throwable)unwrapped; } finally { if (sqlSession != null) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } return unwrapped; } } }
我們看到SqlSessionTemplate實現了SqlSession介面,那麼Mapper代理類中執行所有的資料庫操作,都是通過SqlSessionTemplate來執行,如上我們看到所有的資料庫操作都由對象sqlSessionProxy執行查詢
靜態代理的使用
SqlSessionTemplate在內部訪問資料庫時,其實是委派給sqlSessionProxy來執行資料庫操作的,SqlSessionTemplate不是自身重新實現了一套mybatis資料庫訪問的邏輯。
SqlSessionTemplate通過靜態代理機制來提供SqlSession介面的行為,即實現SqlSession介面來獲取SqlSession的所有方法;SqlSessionTemplate的定義如下:標準的靜態代理實現模式,即實現SqlSession介面並在內部包含一個SqlSession介面實現類引用sqlSessionProxy。那我們就要看看sqlSessionProxy這個SqlSession,我們先來看看SqlSessionTemplate的構造方法
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); Assert.notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor()); }
動態代理的使用
不是吧,又使用了動態代理??真夠曲折的,那我們接著看 new SqlSessionTemplate.SqlSessionInterceptor() 這個InvocationHandler
private class SqlSessionInterceptor implements InvocationHandler { //很奇怪,這裡並沒有真實目標對象? private SqlSessionInterceptor() { } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 獲取一個sqlSession來執行proxy的method對應的SQL, // 每次調用都獲取創建一個sqlSession執行緒局部變數,故不同執行緒相互不影響,在這裡實現了SqlSessionTemplate的執行緒安全性 SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped; try { //直接通過新創建的SqlSession反射調用method //這也就解釋了為什麼不需要目標類屬性了,這裡每次都會創建一個 Object result = method.invoke(sqlSession, args); // 如果當前操作沒有在一個Spring事務中,則手動commit一下 // 如果當前業務沒有使用@Transation,那麼每次執行了Mapper介面的方法直接commit // 還記得我們前面講的Mybatis的一級快取嗎,這裡一級快取不能起作用了,因為每執行一個Mapper的方法,sqlSession都提交了 // sqlSession提交,會清空一級快取 if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { sqlSession.commit(true); } unwrapped = result; } catch (Throwable var11) { unwrapped = ExceptionUtil.unwrapThrowable(var11); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped); if (translated != null) { unwrapped = translated; } } throw (Throwable)unwrapped; } finally { if (sqlSession != null) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } return unwrapped; } }
這裡大概講一下Mapper代理類調用方法執行邏輯:
1、SqlSessionTemplate生成Mapper代理類時,將本身傳進去做為Mapper代理類的屬性,調用Mapper代理類的方法時,最後會通過SqlSession類執行,也就是調用SqlSessionTemplate中的方法。
2、SqlSessionTemplate中操作資料庫的方法中又交給了sqlSessionProxy這個代理類去執行,那麼每次執行的方法都會回調其SqlSessionInterceptor這個InvocationHandler的invoke方法
3、在invoke方法中,為每個執行緒創建一個新的SqlSession,並通過反射調用SqlSession的method。這裡sqlSession是一個執行緒局部變數,不同執行緒相互不影響,實現了SqlSessionTemplate的執行緒安全性
4、如果當前操作並沒有在Spring事務中,那麼每次執行一個方法,都會提交,相當於資料庫的事務自動提交,Mysql的一級快取也將不可用
接下來我們還要看一個地方,invoke中是如何創建SqlSession的?
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(sessionFactory, "No SqlSessionFactory specified"); Assert.notNull(executorType, "No ExecutorType specified"); //通過TransactionSynchronizationManager內部的ThreadLocal中獲取 SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if(session != null) { return session; } else { if(LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } //這裡我們知道實際上是創建了一個DefaultSqlSession session = sessionFactory.openSession(executorType); //將創建的SqlSession對象放入TransactionSynchronizationManager內部的ThreadLocal中 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } }
通過sessionFactory.openSession(executorType)實際創建的SqlSession還是DefaultSqlSession。如果讀過我前面Spring源碼的朋友,肯定知道TransactionSynchronizationManager這個類,其內部維護了一個ThreadLocal的Map,這裡同一執行緒創建了SqlSession後放入ThreadLocal中,同一執行緒中其他Mapper介面調用方法時,將會直接從ThreadLocal中獲取。