疑惑
我們知道Mybatis單獨使用時,需要如下步驟:
@Autowired
public SqlSessionFactory sqlSessionFactory;
@Test
public void testMybatis() {
//步驟1 拿到數(shù)據(jù)庫連接
SqlSession sqlSession = sqlSessionFactory.openSession();
//步驟2 獲取接口的代理股缸,也就是將sqlSession與接口包裝成一個proxy對象
BikeMapper mapper = sqlSession.getMapper(BikeMapper.class);
//步驟3 通過代理調用指定方法验游,并返回結果
Bike bike = mapper.selectByPrimaryKey(1L);
System.out.println(bike);
}
注釋寫的很清楚了,就不贅述了泄隔。
但是從springboot集成Mybatis時可以非常簡單配置一些參數(shù)就可以了拒贱,而且使用時直接調用接口方法就可以完成上面的步驟。好奇的我就想知道到底spring做了哪些工作佛嬉,使得我們不用獲取sqlSession并且不用從sqlSession獲取接口代理逻澳,也就是上面的1和2步。
揭秘
springboot集成Mybatis時暖呕,只需要引入對應的starter即可
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
而使用JavaConfig配置集成Mybatis數(shù)據(jù)源時斜做,可以使用一個注解來完成對Mybatis功能接口的檢索
@Configuration
@MapperScan(basePackages = {"com.demo.test.mapper.master"}, sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataBaseConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "masterTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
factoryBean.setTypeAliasesPackage("com.demo.test.po");
return factoryBean.getObject();
}
}
通過代碼來看,通過向spring容器注入sqlSessionFactory的Bean湾揽,讓容器管理這個Mybatis中重要的類瓤逼。除此之外還有個注解@MapperScan,這個注解指定了檢索的目錄和SqlSessionFactory類名库物,這里就是答案所在霸旗。
@MapperScan
這個注解除了上面提到的兩個參數(shù)之外還包括了一個重要的參數(shù)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
String[] basePackages() default {};
String sqlSessionFactoryRef() default "";
//mapper的工廠bean
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
這里沒有列舉出所有參數(shù),而factoryBean參數(shù)的默認MapperFactoryBean.class是我們關注的重點戚揭,下面還會提到诱告。
檢索過程
@MapperScan通過spring的@Import標簽引入了實際檢索接口的類MapperScannerRegistrar。@Import是Spring自動裝配的基礎毫目,提供了向spring容器注入Bean的功能蔬啡。但是mybatis提供的MapperScannerRegistrar類不只是將檢索到的接口注入到spring容器這么簡單诲侮,接著往下看。
源碼很長箱蟆,我這里只給出關鍵位置沟绪。
- MapperScannerRegistrar.registerBeanDefinitions:在@Import被解析時會調用這個方法。里面創(chuàng)建一個檢索對象ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);并對scanner進行參數(shù)賦值空猜,最后調用scanner.doScan方法绽慈,我們跟到這里
- ClassPathMapperScanner.doScan:方法一共兩步,第一步是檢索給定目錄文件夾中類并轉成BeanDefinition辈毯,第二步是對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;
}
- ClassPathMapperScanner.processBeanDefinitions:給出縮略代碼
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//Bean的Class指定為工廠類
definition.setBeanClass(this.mapperFactoryBean.getClass());
//自動裝配sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
這里就真正的學到了,還有這種操作:
- 使用代碼的方式將一個普通的spring的beanDefinition通過setBeanClass的方式變成一個工廠Bean谆沃。
- 還添加了屬性sqlSessionFactory钝凶,并在實例化時通過自動注入到bean中。
既然將bean轉成的工廠bean唁影,那么就要看mapperFactoryBean耕陷,也就是上面提到的@MapperScan中MapperFactoryBean.class這個類。
- MapperFactoryBean
上一步提到了將Bean的class指定為MapperFactoryBean据沈,并且添加了參數(shù)sqlSessionFactory哟沫,在實例化時需要自動注入sqlSessionFactory。那么 MapperFactoryBean中就會有setSqlSessionFactory方法锌介。我們在MapperFactoryBean的父類SqlSessionDaoSupport中發(fā)現(xiàn)了:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
new SqlSessionTemplate方法最終調用代碼(中間忽略了一些調用過程):
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
這里就可以發(fā)現(xiàn)嗜诀,在工廠類實例化時,通過注入SqlSessionFactory時孔祸,通過SqlSessionFactory創(chuàng)建了sqlSession隆敢。這就揭開疑惑中第一步的獲取SqlSession的過程。
既然是工廠Bean融击,那在spring實例化時就一定會調用getObject方法來創(chuàng)建真正的Bean筑公。我們來揭開最終的謎底雳窟。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()是上一步創(chuàng)建的SqlSession尊浪。而getMapper就是我們苦苦尋找的疑惑中第二步的獲取Mapper代理過程。
總結
從疑惑到揭秘封救,這里過程有點長拇涤,但是還算清晰的解釋了Spring繼承MyBatis之后便捷之處:在項目啟動時,檢索Mybatis業(yè)務接口并注入到Spring時誉结,就已經將接口在Bean初始化時轉換成最終的接口代理類了鹅士。
有問題的地方,可以在留言中指出惩坑,希望對看到的人有幫助掉盅。