在MyBatis-Spring的項目中,我們一般會為MyBatis配置兩個配置文件 beans-mybatis.xml 和 mybatis-config.xml。
其中 beans-mybatis.xml 中配置的是MyBatis 和 Spring結合使用時委托給 spring 管理的 bean。
mybatis-config.xml 中是MyBatis 自身的配置。
例:beans-mybatis.xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml" p:mapperLocations="classpath:mapper/**/*.xml" /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.cn.kvn.usage.dao" />
mybatis-config.xml:
<configuration> <settings> <setting name="lazyLoadingEnabled" value="false" /> <!-- logback日志指定打印sql用 --> <setting name="logPrefix" value="dao." /> </settings> <typeAliases> <!-- 別名定義 --> </typeAliases> <!-- 插件 --> <plugins> <plugin interceptor="com.cn.kvn.framework.jdbc.mybatis.interceptor.PageInterceptor"> <property name="dialectClassName" value="com.cn.kvn.framework.jdbc.mybatis.interceptor.MySQLDialect" /> </plugin> </plugins> </configuration>
mybatis-spring的入口就在bean的定義那里(beans-mybatis.xml ): SqlSessionFactoryBean 、MapperScannerConfigurer
#####1. SqlSessionFactoryBean
SqlSessionFactoryBean實現了 InitializingBean ,在 afterPropertiesSet() 中會對 mybatis的配置 p:configLocation="classpath:mybatis-config.xml" 、 p:mapperLocations="classpath:mapper/**/*.xml" 進行解析。
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory():
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; // MyBatis 的配置,存放 mybatis-config.xml 中的配置 XMLConfigBuilder xmlConfigBuilder = null; // mybatis-config.xml 的解析器,解析出來的內容放在 Configuration 中 if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (hasLength(this.typeAliasesPackage)) { // typeAlias : 類型別名,用於sqlmap中類型(parameterType、resultType)的配置 String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); // 注冊 typeAliases } } if (!isEmpty(this.typeAliases)) { // typeAlias : 類型別名,用於sqlmap中類型(parameterType、resultType)的配置 for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); // 注冊 typeAliases } } if (!isEmpty(this.plugins)) { // plugins : mybatis 的插件,即 MyBatis 的攔截器和擴展點。可以針對Executor 、 StatementHandler 、 ResultSetHandler 和 PameterHandler 進行攔截擴展 for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); // 添加 MyBatis Interceptor } } if (hasLength(this.typeHandlersPackage)) { // typeHandler : 類型轉換器,對java類型和sqlmap中的類型進行轉換 String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); // 注冊 typeHandler } } if (!isEmpty(this.typeHandlers)) { // typeHandler : 類型轉換器,對java類型和sqlmap中的類型進行轉換 for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); // 注冊 typeHandler } } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); // xmlConfigBuilder解析 mybatis-spring.xml 配置 } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); // 重置 ErrorContext } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); // 默認的 transactionFactory } Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource); configuration.setEnvironment(environment); if (this.databaseIdProvider != null) { try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); // DatabaseId 用於對多數據庫的支持 } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } 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(); // xmlMapperBuilder:解析 mapperLocation 中指定的 sqlmap 文件,即解析 sql 語句 } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); // 重置 ErrorContext } } } return this.sqlSessionFactoryBuilder.build(configuration); // 將 configuration 配置設置到 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 中 }
附:ErrorContext: 解析配置文件時,用於存儲異常信息(ThreadLocal實現的)
跟配置解析相關的類:
XMLConfigBuilder : 解析 mybatis-config.xml 中 MyBatis 自身的配置。
XmlMapperBuilder : 解析 sqlmap 中的配置,包括 <resultMap> 和 sql 語句
ResultMapResolver : 解析 sqlmap 中的 <resultMap> 節點
XMLStatementBuilder : 解析 sql 語句(<insert>、<delete>、<update>、<select>)
舉例:mybatis-config.xml 的解析是通過 XMLConfigBuilder 來完成的
mybatis-config.xml中能夠配置的屬性:(XMLConfigBuilder#parseConfiguration(XNode))
private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
其中<settings>標簽可以配置的屬性如下:(XMLConfigBuilder#settingsElement(XNode))
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
#####2. MapperScannerConfigurer
MapperScannerConfigurer 的作用是掃描 java 的 Dao 接口,來與 mapper 做映射關系
org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); // java類型過濾器,排除一些不符合要求的 Dao scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); // 進行 Dao 掃描,掃描 basePackage 包下面的類 }
org.mybatis.spring.mapper.ClassPathMapperScanner#scan(String... basePackages):
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); // 調用 ClassPathBeanDefinitionScanner#doScan()解析出bean for (BeanDefinitionHolder holder : beanDefinitions) { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); // mapperInterface 是 Dao 對應的 bean 的原始類型 definition.setBeanClass(MapperFactoryBean.class); // MapperFactoryBean 是 Dao 對應的 bean 的實際類型。也就是所有的 Dao 對應的 bean ,最后都是 MapperFactoryBean,通過 MapperFactoryBean 來做 Dao 的代理,將CRUD操作分發到具體的 sqlmap 中的 sql 去執行。 definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 讓 MapperFactoryBean 可以通過 @Autowired 注解來進行注入。即注入 xxxDao } return beanDefinitions; }
#####3. MapperFactoryBean
MapperFactoryBean 繼承了SqlSessionDaoSupport,默認使用了 org.mybatis.spring.SqlSessionTemplate 來操作CRUD方法。
org.mybatis.spring.SqlSessionTemplate 的構造方法:
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; // 默認使用 MyBatisExceptionTranslator ,對 SQL 異常進行友好轉換 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
SqlSessionTemplate 又是通過 sqlSessionProxy 來操作CRUD方法的。
sqlSessionProxy 是JDK 動態代理,通過 SqlSessionInterceptor 來攔截 org.apache.ibatis.session.SqlSession 接口里面的 CRUD 操作。
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor:
/** * 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 { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); // 從當前線程中獲取一個 SqlSession ,如果沒有,就創建一個新的 SqlSession。(org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(ExecutorType execType))。這樣 spring 和 mybatis 就結合在一起了 try { Object result = method.invoke(sqlSession, args); // 執行 SqlSession 的方法(CRUD操作)。 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); // 對 Method#invoke(Object, Object...) 的異常進行解封裝 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); // 對 SQL 異常進行友好轉換 if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
至此,我們還有一個問題沒有解決:我們在調用 xxDao 中的方法 xxMethod 時,mybatis-spring.jar 是如何找到 xxMethod 對應的配置文件 xxMapper.xml 中的 sql 語句的?
它是通過多個 JDK 動態代理,去攔截 mapperInterface(xxDao)中的方法來實現的。具體可以看下圖:
通過上圖,前面的疑問就迎刃而解了。^_^
附:MapperFactoryBean 中的 sqlSessionFactory 是如何注入的?
在 Spring 容器里面 IJobBeforehandRetryDao.java 對應的 beanName = "IJobBeforehandRetryDao"
相應的 RootBeanDefinition為:
Root bean: class [org.mybatis.spring.mapper.MapperFactoryBean]; scope=singleton; abstract=false; lazyInit=false; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [E:\gitWorkspace\dal-job\dal-job-core\target\classes\com\kvn\dal\core\dao\IJobBeforehandRetryDao.class]
IJobBeforehandRetryDao 對應的 bean 是一個 FactoryBean。Spring 對 FactoryBean 里面的屬性有特殊處理。
AbstractAutowireCapableBeanFactory#autowireByType(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs)
MapperFactoryBean#setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)會被調用。
這樣,MapperFactoryBean 中的屬性 sqlSession就會被賦值。 this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);