原生的mybatis的使用方式,是通过获取SqlSession,然后使用SqlSession去获取代理对象,执行sql,进行返回数据的包装返回的,具体实现可以参考mybatis的源码,简单使用如下所示:
public class MybatisOriginalDemo { public static void main(String[] args) { Reader reader = null; try { reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession = sqlSessionFactory.openSession(); QuestionMapper mapper = sqlSession.getMapper(QuestionMapper.class); QuestionDO questionDO = mapper.selectById(10L); System.out.println(questionDO); } }
那么Spring是如何和mybatis整合的呢?今天我们来一探究竟
我们在Spring应用中使用mybatis的时候,需要配置三个关键的bean如下:
1 dataSource
<!-- 配置数据源--> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean>
2 SqlSessionFactoryBean
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="spring/mapper/*/*Mapper.xml"/> </bean>
3 MapperScannerConfigurer
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--注意这里对于不同的数据源需要使用不同的包,否则会导致数据源映射错误 --> <property name="basePackage" value="com.muxi.sample.spring.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>
从上面的配置我们可以看出,其实dataSource 和 SqlSessionFactoryBean这两个的bean 是为mybatis配置的,而Spring整合mybatis关键的bean是MapperScannerConfigurer,那么这个bean到底是啥?拥有那么超级能力呢?能够这么完美的整合到Spring中,下面我们就解开它的神秘面纱
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
我们看一下类继承结构
从上面的类图我们可以看到MapperScannerConfigurer 实际上是一个BeanDefinitionRegistryPostProcessor,这个bean可以实现向Spring中注入BeanDefinition,那下面我们看看那是怎么注入BeanDefinition
关键代码:org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry在这个方法中,从代码中我们可以看到这创建了一个mybatis自定义的一个扫描器
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { 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(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
那我们继续看这个扫描器是怎么工作的?我们继续往下看代码
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
从上面的代码可以看出,这里就是通过你配置的包路径,将该路径下的Java接口扫描为一个BeanDefinition并包装为BeanDefinitionHolder,看到这里就有点疑惑了,Spring 默认是不会对接口进行解析的,所以接口是无法注册到Spring容器中成为一个单例的bean的,这里就算扫描成了一个BeanDefinition后面也无法完成实例化的呀?
带着这样的疑惑,我们继续往下看
继续跟代码我们发现其实scanner的真正实现是ClassPathMapperScanner
那么在ClassPathMapperScanner扫描完BeanDefinition后,有一步后置处理如下:
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 关键实现就在这个方法里 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
从代码中可以看出关键实现就在processBeanDefinitions()这个方法里了,那我们继续
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 // 这里mybatis他偷偷的将beanClass的给换了 definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); 这里省略了无关的代码 ........... } }
看到没有就在这里,就是在这里mybatis来了个狸猫换太子,将beanClass偷偷的换成了mapperFactoryBean这个FactoryBean,这样就能够实现接口注入Spring中了,并能够获取到mybatis的代理对象了,是不是很惊叹‼️
我只能膜拜呀,大佬终究是大佬哈
这里省略了无关的代码
好了,到这里mybatis整合Spring 的解释清楚了,记录一下整个最终流程,还是蛮有意思的。