javascript
MyBatis(五)MyBatis整合Spring原理分析
前面梳理了下MyBatis在單獨使用時的工作流程和關鍵源碼,現在看看MyBatis在和Spring整合的時候是怎么工作的
也先從使用開始
Spring整合MyBatis
1.引入依賴,除了MyBatis的依賴,還需要引入 mybatis-spring依賴
2.在spring的配置文件applicationContext.xml里配置SqlSessionFactoryBean,從名字可以看出我們是通過這個Bean來創建SqlSessionFactory
3.在applicationContext.xml配置掃描Mapper的路徑
可以通過三種方式 1.配置MapperScannerConfigurer <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.XXX.XXX"/></bean>2.配置<scan>標簽<context:component-scan base-package="com.XXX.XXX">3.使用@MapperScan注解原理分析
創建會話工廠
先看下前面配置的SqlSessionFactoryBean,其類圖如下所示
其中的InitializingBean有一個待實現方法afterPropertiesSet,會在初始化Bean之后調用,這里就是在afterPropertiesSet實現方法里創建了SqlSessionFactory對象
//在Bean初始化之后調用 public void afterPropertiesSet() throws Exception {Assert.notNull(this.dataSource, "Property 'dataSource' is required");Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");//創建SqlSessionFactorythis.sqlSessionFactory = this.buildSqlSessionFactory();} protected SqlSessionFactory buildSqlSessionFactory() throws IOException {XMLConfigBuilder xmlConfigBuilder = null;Configuration targetConfiguration;//如果configuration已經存在,則把當前Bean的Properties屬性也加入到Configurationif (this.configuration != null) {targetConfiguration = this.configuration;if (targetConfiguration.getVariables() == null) {targetConfiguration.setVariables(this.configurationProperties);} else if (this.configurationProperties != null) {targetConfiguration.getVariables().putAll(this.configurationProperties);}//如果configuration不存在,但是存在configLocation,則使用XmlConfigBuilder解析對應的配置文件} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();} else {//如果Configuration對象不存在,configLocation路徑也沒有,則使用默認的configurationProperties給configuration賦值LOGGER.debug(() -> {return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";});targetConfiguration = new Configuration();Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);}//基于當前factoryBean里已有的屬性,對targetConfiguration對象里面的屬性進行賦值Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);String[] typeHandlersPackageArray;if (StringUtils.hasLength(this.typeAliasesPackage)) {typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);LOGGER.debug(() -> {return "Scanned package: '" + packageToScan + "' for aliases";});});}if (!ObjectUtils.isEmpty(this.typeAliases)) {Stream.of(this.typeAliases).forEach((typeAlias) -> {targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);LOGGER.debug(() -> {return "Registered type alias: '" + typeAlias + "'";});});}if (!ObjectUtils.isEmpty(this.plugins)) {Stream.of(this.plugins).forEach((plugin) -> {targetConfiguration.addInterceptor(plugin);LOGGER.debug(() -> {return "Registered plugin: '" + plugin + "'";});});}if (StringUtils.hasLength(this.typeHandlersPackage)) {typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {targetConfiguration.getTypeHandlerRegistry().register(packageToScan);LOGGER.debug(() -> {return "Scanned package: '" + packageToScan + "' for type handlers";});});}if (!ObjectUtils.isEmpty(this.typeHandlers)) {Stream.of(this.typeHandlers).forEach((typeHandler) -> {targetConfiguration.getTypeHandlerRegistry().register(typeHandler);LOGGER.debug(() -> {return "Registered type handler: '" + typeHandler + "'";});});}if (this.databaseIdProvider != null) {try {targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException var23) {throw new NestedIOException("Failed getting a databaseId", var23);}}Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);//如果XMLConfigBuilder不為空,就調用parse方法,這跟MyBatis里調用的一樣if (xmlConfigBuilder != null) {try {xmlConfigBuilder.parse();LOGGER.debug(() -> {return "Parsed configuration file: '" + this.configLocation + "'";});} catch (Exception var21) {throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);} finally {ErrorContext.instance().reset();}}//創建environment,事務工廠(默認使用SpringManagedTransactionFactory)targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));//根據配置的MapperLocation 使用XMLMapperBuilder解析mapper.xml,把接口和對應的MapperProxyFactory注冊到MapperRegistry 中。if (!ObjectUtils.isEmpty(this.mapperLocations)) {Resource[] var24 = this.mapperLocations;int var4 = var24.length;for(int var5 = 0; var5 < var4; ++var5) {Resource mapperLocation = var24[var5];if (mapperLocation != null) {try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());xmlMapperBuilder.parse();} catch (Exception var19) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);} finally {ErrorContext.instance().reset();}LOGGER.debug(() -> {return "Parsed mapper file: '" + mapperLocation + "'";});}}} else {LOGGER.debug(() -> {return "Property 'mapperLocations' was not specified or no matching resources found";});}//使用SqlSessionFactoryBuilder的build方法構建SqlSessionFactoryreturn this.sqlSessionFactoryBuilder.build(targetConfiguration);}創建SqlSession
Spring在創建SqlSession的時候不是直接使用MyBatis的DefaultSqlSession,而是自己封裝了一個SqlSessionTemplate,這是因為DefaultSqlSession不是線程安全的,所以Spring特性封裝了一個線程安全的SqlSessionTemplate(線程安全問題在web場景下不可避免)
SqlSessionTemplate里創建的SqlSession是使用的jdk動態代理,所有方法的調用實際都是通過這個proxy來調用的
private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;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;//通過JDK動態代理創建代理對象this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());}//創建代理對象的InvocationHandler private class SqlSessionInterceptor implements InvocationHandler {private SqlSessionInterceptor() {}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//獲得SqlSession對象SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);Object unwrapped;try {//調用目標對象方法,實際調用了DefaultSqlSession的方法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是如何保證線程安全的參考這篇博客?MyBatis(六)SqlSessionTemplate是如何保證線程安全的
知道Spring中是使用SqlSessionTemplate來保證線程安全的,那么我們怎么獲取這個Template并使用呢?在spring的早期版本,想要使用SqlSessionTemplate,需要讓我們的dao類去繼承SqlSessionDaoSupport,它持有一個SqlSessionTemplate 對象,并提供了getSqlSession的方法
public abstract class SqlSessionDaoSupport extends DaoSupport {private SqlSessionTemplate sqlSessionTemplate;public SqlSessionDaoSupport() { }//在繼承SqlSessionDaoSupport的時候需要調用setSqlSessionFactory把SqlSessionFactory注入(也可以通過xml注入)public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);}}protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}public final SqlSessionFactory getSqlSessionFactory() {return this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null;}//對外提供的獲取SqlSessionTemplate的方法(SqlSessionTemplate也實現了SqlSession接口)public SqlSession getSqlSession() {return this.sqlSessionTemplate;}... }但是這樣一來,我們的DAO層的接口,如果要操作數據庫,就都需要實現SqlSessionDaoSupport去拿到一個SqlSessionTemplate
目前的spring早已經優化了使用SqlSessionTemplate的方式,可以直接使用@Autowired 在Service層自動注入的 Mapper 接口
接口的掃描注冊
這些Mapper就是在applicationContext.xml配置MapperScannerConfigurer時注冊上的
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.XXX.XXX"/></bean>MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor(該接口就是用于在BeanDefinition注冊后做一些后置處理,比放說修改BeanDefinition等)接口的postProcessBeanDefinitionRegistry方法
//MapperScannerConfigurer public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {this.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();//掃描包路徑,注冊Mapperscanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));}-----scanner.scan最后會調用ClassPathMapperScanner的doScan方法public Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> {return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";});} else {//對BeanDefinitions進行處理的方法this.processBeanDefinitions(beanDefinitions);}return beanDefinitions;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {Iterator var3 = beanDefinitions.iterator();while(var3.hasNext()) {BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> {return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";});//the mapper interface is the original class of the bean//but the actual class of the bean is MapperFactoryBean //構造方法參數添加為當前接口的類名 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);definition.setBeanClass(this.mapperFactoryBean.getClass());definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;//向BeanDefinition的屬性加入SqlSessionFactory和sqlSessionTemplateif (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)) {if (explicitFactoryUsed) {LOGGER.warn(() -> {return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";});}definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {LOGGER.warn(() -> {return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";});}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {LOGGER.debug(() -> {return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";});//表示當前bean會自動填充setter方法的屬性,容器中配置了SqlSessionFactory的bean,所以會調用setSqlSessionFactory()方法來注入屬性。definition.setAutowireMode(2);}}}從上面可以看到,我們掃描到的Mapper會把其BeanDefinition的BeanClass設置成MapperFactoryBean,所以在實例化的時候實際生成的也是MapperFactoryBean對象,而我們的MapperFactoryBean就繼承了SqlSessionDaoSupport,而前面我們又向BeanDefinition里添加了SqlSessionTemplate和SqlSessionFactory的屬性,所以生成的MapperFactoryBean實際就是一個繼承了SqlSessionDaoSupport并且注入了SqlSessionTemplate和SqlSessionFactory的對象
接口注入的使用
前面說了,目前spring在使用Mapper的時候,直接在加了Service 注解的類里面使用@Autowired注入Mapper接口就好了
@Service public class EmployeeService {@AutowiredEmployeeMapper employeeMapper;//按id查詢public Employee getEmp(Integer id) {Employee employee = employeeMapper.selectByPrimaryKey(id);return employee;} }spring在啟動的時候會去實例化EmployeeService,而EmployeeService里又需要注入EmployeeMapper對象,
Spring會根據Mapper的名字從BeanFactory 中獲取它的BeanDefination,再從BeanDefination 中 獲 取 BeanClass ,此時其BeanClass已經被替換成MapperFactoryBean了
接下來就只需要創建MapperFactoryBean實例對象就可以了,因為MapperFactoryBean實現了FactoryBean接口,所以會通過getObject()來獲取對應的實例對象
//實際調用的是SqlSession(這里的返回的對象是SqlSessionTemplate)的getMapper方法public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);}----SqlSessionTemplate 這里調用了Configuration的getMapperpublic <T> T getMapper(Class<T> type) {return this.getConfiguration().getMapper(type, this);}----Configuration 最終是通過configuration的mapperRegistry獲得對應的Mapper,實際拿到的是 通過工廠類MapperProxyFactory獲得的用MapperProxy增強的代理對象public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return this.mapperRegistry.getMapper(type, sqlSession);}總結一下:
| 關鍵類 | 功能 |
| SqlSessionTemplate | Spring 中 線程安全的SqlSession實現類,通過代理的方式調用 DefaultSqlSession 的方法 |
| SqlSessionInterceptor(內部類) | 定義在在 SqlSessionTemplate ,用來增強代理 DefaultSqlSession |
| SqlSessionDaoSupport | 用于獲取 SqlSessionTemplate,需要繼承它并且注入SqlSessionFactory |
| MapperFactoryBean | 注冊到 IOC 容器中的Mapper接口替換類,繼承了 SqlSessionDaoSupport 可以用來獲取SqlSessionTemplate,注入接口的時候,會調用用它的 getObject()方法 最終調用Configuration的getMapper方法 |
| SqlSessionHolder | 管理SqlSession,幫助實現spring中SqlSession的線程安全 |
?
?
總結
以上是生活随笔為你收集整理的MyBatis(五)MyBatis整合Spring原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MyBatis(四)MyBatis插件原
- 下一篇: MyBatis(一)MyBatis介绍和