MyBatis(五)MyBatis整合Spring原理分析
前面梳理了下MyBatis在單獨(dú)使用時(shí)的工作流程和關(guān)鍵源碼,現(xiàn)在看看MyBatis在和Spring整合的時(shí)候是怎么工作的
也先從使用開始
Spring整合MyBatis
1.引入依賴,除了MyBatis的依賴,還需要引入 mybatis-spring依賴
2.在spring的配置文件applicationContext.xml里配置SqlSessionFactoryBean,從名字可以看出我們是通過這個(gè)Bean來創(chuàng)建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>標(biāo)簽<context:component-scan base-package="com.XXX.XXX">3.使用@MapperScan注解原理分析
創(chuàng)建會(huì)話工廠
先看下前面配置的SqlSessionFactoryBean,其類圖如下所示
其中的InitializingBean有一個(gè)待實(shí)現(xiàn)方法afterPropertiesSet,會(huì)在初始化Bean之后調(diào)用,這里就是在afterPropertiesSet實(shí)現(xiàn)方法里創(chuàng)建了SqlSessionFactory對(duì)象
//在Bean初始化之后調(diào)用 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");//創(chuàng)建SqlSessionFactorythis.sqlSessionFactory = this.buildSqlSessionFactory();} protected SqlSessionFactory buildSqlSessionFactory() throws IOException {XMLConfigBuilder xmlConfigBuilder = null;Configuration targetConfiguration;//如果configuration已經(jīng)存在,則把當(dāng)前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解析對(duì)應(yīng)的配置文件} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();} else {//如果Configuration對(duì)象不存在,configLocation路徑也沒有,則使用默認(rèn)的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);}//基于當(dāng)前factoryBean里已有的屬性,對(duì)targetConfiguration對(duì)象里面的屬性進(jìn)行賦值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不為空,就調(diào)用parse方法,這跟MyBatis里調(diào)用的一樣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();}}//創(chuàng)建environment,事務(wù)工廠(默認(rèn)使用SpringManagedTransactionFactory)targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));//根據(jù)配置的MapperLocation 使用XMLMapperBuilder解析mapper.xml,把接口和對(duì)應(yīng)的MapperProxyFactory注冊(cè)到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方法構(gòu)建SqlSessionFactoryreturn this.sqlSessionFactoryBuilder.build(targetConfiguration);}創(chuàng)建SqlSession
Spring在創(chuàng)建SqlSession的時(shí)候不是直接使用MyBatis的DefaultSqlSession,而是自己封裝了一個(gè)SqlSessionTemplate,這是因?yàn)镈efaultSqlSession不是線程安全的,所以Spring特性封裝了一個(gè)線程安全的SqlSessionTemplate(線程安全問題在web場(chǎng)景下不可避免)
SqlSessionTemplate里創(chuàng)建的SqlSession是使用的jdk動(dòng)態(tài)代理,所有方法的調(diào)用實(shí)際都是通過這個(gè)proxy來調(diào)用的
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動(dòng)態(tài)代理創(chuàng)建代理對(duì)象this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());}//創(chuàng)建代理對(duì)象的InvocationHandler private class SqlSessionInterceptor implements InvocationHandler {private SqlSessionInterceptor() {}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//獲得SqlSession對(duì)象SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);Object unwrapped;try {//調(diào)用目標(biāo)對(duì)象方法,實(shí)際調(diào)用了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;}}關(guān)于SqlSessionTemplate是如何保證線程安全的參考這篇博客?MyBatis(六)SqlSessionTemplate是如何保證線程安全的
知道Spring中是使用SqlSessionTemplate來保證線程安全的,那么我們?cè)趺传@取這個(gè)Template并使用呢?在spring的早期版本,想要使用SqlSessionTemplate,需要讓我們的dao類去繼承SqlSessionDaoSupport,它持有一個(gè)SqlSessionTemplate 對(duì)象,并提供了getSqlSession的方法
public abstract class SqlSessionDaoSupport extends DaoSupport {private SqlSessionTemplate sqlSessionTemplate;public SqlSessionDaoSupport() { }//在繼承SqlSessionDaoSupport的時(shí)候需要調(diào)用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;}//對(duì)外提供的獲取SqlSessionTemplate的方法(SqlSessionTemplate也實(shí)現(xiàn)了SqlSession接口)public SqlSession getSqlSession() {return this.sqlSessionTemplate;}... }但是這樣一來,我們的DAO層的接口,如果要操作數(shù)據(jù)庫,就都需要實(shí)現(xiàn)SqlSessionDaoSupport去拿到一個(gè)SqlSessionTemplate
目前的spring早已經(jīng)優(yōu)化了使用SqlSessionTemplate的方式,可以直接使用@Autowired 在Service層自動(dòng)注入的 Mapper 接口
接口的掃描注冊(cè)
這些Mapper就是在applicationContext.xml配置MapperScannerConfigurer時(shí)注冊(cè)上的
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.XXX.XXX"/></bean>MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor(該接口就是用于在BeanDefinition注冊(cè)后做一些后置處理,比放說修改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();//掃描包路徑,注冊(cè)Mapperscanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));}-----scanner.scan最后會(huì)調(diào)用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 {//對(duì)BeanDefinitions進(jìn)行處理的方法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 //構(gòu)造方法參數(shù)添加為當(dāng)前接口的類名 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() + "'.";});//表示當(dāng)前bean會(huì)自動(dòng)填充setter方法的屬性,容器中配置了SqlSessionFactory的bean,所以會(huì)調(diào)用setSqlSessionFactory()方法來注入屬性。definition.setAutowireMode(2);}}}從上面可以看到,我們掃描到的Mapper會(huì)把其BeanDefinition的BeanClass設(shè)置成MapperFactoryBean,所以在實(shí)例化的時(shí)候?qū)嶋H生成的也是MapperFactoryBean對(duì)象,而我們的MapperFactoryBean就繼承了SqlSessionDaoSupport,而前面我們又向BeanDefinition里添加了SqlSessionTemplate和SqlSessionFactory的屬性,所以生成的MapperFactoryBean實(shí)際就是一個(gè)繼承了SqlSessionDaoSupport并且注入了SqlSessionTemplate和SqlSessionFactory的對(duì)象
接口注入的使用
前面說了,目前spring在使用Mapper的時(shí)候,直接在加了Service 注解的類里面使用@Autowired注入Mapper接口就好了
@Service public class EmployeeService {@AutowiredEmployeeMapper employeeMapper;//按id查詢public Employee getEmp(Integer id) {Employee employee = employeeMapper.selectByPrimaryKey(id);return employee;} }spring在啟動(dòng)的時(shí)候會(huì)去實(shí)例化EmployeeService,而EmployeeService里又需要注入EmployeeMapper對(duì)象,
Spring會(huì)根據(jù)Mapper的名字從BeanFactory 中獲取它的BeanDefination,再從BeanDefination 中 獲 取 BeanClass ,此時(shí)其BeanClass已經(jīng)被替換成MapperFactoryBean了
接下來就只需要?jiǎng)?chuàng)建MapperFactoryBean實(shí)例對(duì)象就可以了,因?yàn)镸apperFactoryBean實(shí)現(xiàn)了FactoryBean接口,所以會(huì)通過getObject()來獲取對(duì)應(yīng)的實(shí)例對(duì)象
//實(shí)際調(diào)用的是SqlSession(這里的返回的對(duì)象是SqlSessionTemplate)的getMapper方法public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);}----SqlSessionTemplate 這里調(diào)用了Configuration的getMapperpublic <T> T getMapper(Class<T> type) {return this.getConfiguration().getMapper(type, this);}----Configuration 最終是通過configuration的mapperRegistry獲得對(duì)應(yīng)的Mapper,實(shí)際拿到的是 通過工廠類MapperProxyFactory獲得的用MapperProxy增強(qiáng)的代理對(duì)象public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return this.mapperRegistry.getMapper(type, sqlSession);}總結(jié)一下:
| 關(guān)鍵類 | 功能 |
| SqlSessionTemplate | Spring 中 線程安全的SqlSession實(shí)現(xiàn)類,通過代理的方式調(diào)用 DefaultSqlSession 的方法 |
| SqlSessionInterceptor(內(nèi)部類) | 定義在在 SqlSessionTemplate ,用來增強(qiáng)代理 DefaultSqlSession |
| SqlSessionDaoSupport | 用于獲取 SqlSessionTemplate,需要繼承它并且注入SqlSessionFactory |
| MapperFactoryBean | 注冊(cè)到 IOC 容器中的Mapper接口替換類,繼承了 SqlSessionDaoSupport 可以用來獲取SqlSessionTemplate,注入接口的時(shí)候,會(huì)調(diào)用用它的 getObject()方法 最終調(diào)用Configuration的getMapper方法 |
| SqlSessionHolder | 管理SqlSession,幫助實(shí)現(xiàn)spring中SqlSession的線程安全 |
?
?
總結(jié)
以上是生活随笔為你收集整理的MyBatis(五)MyBatis整合Spring原理分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MyBatis(四)MyBatis插件原
- 下一篇: JS组件系列——表格组件神器:boots