1. Mybatis執行機制1.1 Mybatis功能架構設計
Mybatis的功能架構分為三層:
(1)API介面層:提供給外部使用的介面API,開發人員透過這些本地API來操縱資料庫。介面層一接收到呼叫請求就會呼叫資料處理層來完成具體的資料處理。
(2)資料處理層:負責具體的SQL查詢、SQL解析、SQL執行和執行結果對映處理等。它主要的目的是根據呼叫的請求完成一次資料庫操作。
(3)基礎支撐層:負責最基礎的功能支撐,包括連線管理、事務管理、配置載入和快取處理,這些都是共用的東西,將他們抽取出來作為最基礎的元件。為上層的資料處理層提供最基礎的支撐。
1.2 Mybatis執行方式Mybatis支援兩種方式執行SQL:Mapper介面和SqlSession,使用如下
public class MapperMain { public static void main(String[] args) throws Exception { File file = new File("/data/learncode/hobbit/src/main/resources/conf/mybatis/mybatis_config.xml"); InputStream inputStream = new FileInputStream(file); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int id = 13; // 方式1:Mapper介面傳送SQL UserMapper mapper = sqlSession.getMapper(UserMapper.class); System.out.println("Mapper介面傳送:" + mapper.getById(id)); // 方式2:SqlSession傳送SQL UserEntity userEntity = sqlSession.selectOne("com.hobbit.mapper.UserMapper.getById", id); System.out.println("SqlSession傳送:" + userEntity); }}複製程式碼
兩種方式執行效果一致,如圖
一般的話,比較推薦Mapper介面方法,因為手工寫namespace和statementId極大增加了犯錯誤的機率,而且也降低了開發的效率。Mapper由MapperProxyFactory動態代理生成,封裝了SqlSession。
2. Spring整合Mybatis要解決的問題2.1 Mapper代理物件重點關注下的Mapper動態代理物件,因為Spring整合Mybatis的核心目標是:把某個Mapper的代理物件作為一個bean放入Spring容器中,使得能夠像使用一個普通bean一樣去使用這個代理物件,比如能被@Autowire自動注入。常用如下透過Ioc容器把UserMapper注入了UserService:
@Servicepublic class UserService { @Autowired private UserMapper userMapper; public UserEntity queryUser(int id){ UserEntity userEntity = userMapper.getById(id); return userEntity; } public void printServiceName(){ System.out.println("I'm UserService"); }}複製程式碼
透過執行時Debugger,確實與直接使用Mapper一樣的代理物件。userMapper的型別為:org.apache.ibatis.binding.MapperProxy@4acf72b6。
問題:如何能夠把Mybatis的代理物件作為一個bean放入Spring容器中?
2.2 Bean的生產過程Spring啟動過程中,bean的生命週期如下
掃描指定的包路徑下的class檔案或解析xml檔案生成對應的BeanDefinitionBeanFactoryPostProcessor註冊或修改BeanDefinition定義根據BeanDefinition反射例項化BeanBeanPostProcessor修改Bean定義Bean的業務呼叫Bean的銷燬對於兩個Service:UserService/OrderInfoService定義如下
UserService
@Servicepublic class UserService { public void printServiceName(){ System.out.println("I'm UserService"); }}複製程式碼
OrderService, 無@service註解
public class OrderInfoService { public void printServiceName(){ System.out.println("I'm OrderInfoService"); } }複製程式碼
執行如下程式碼:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);System.out.println(ctx.getBean("userService"));複製程式碼
結果如下:
com.hobbit.service.UserService@4167d97b複製程式碼
增加一個FactoryBean後置處理器,修改userService的BeanDefinition定義。
@Componentpublic class RenameBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService"); beanDefinition.setBeanClassName(OrderInfoService.class.getName()); }}複製程式碼
重新執行結果如下,生成了OrderInfoService物件。
com.hobbit.service.OrderInfoService@57ad2aa7複製程式碼
所以在Spring中bean物件跟class或xml定義的bean無直接關係,跟最終的BeanDefinition有直接關係。
要想生成一個bean,首先要有一個BeanDefinition。那Mapper對應的BeanDefinition是?
3. MapperFactoryBean3.1 Mapper BeanDefinition定義Spring透過BeanDefinition的beanClassName生成對應的bean,那mapper的對應的beanClassName是什麼?本可以有兩個答案:
1. 代理物件對應的代理類
2. 代理物件對應的介面
因為代理類是動態生成的,spring啟動時無法得知,無法使用。那麼代理物件對應的介面?
思路如下:
BeanDefinition bd = new BeanDefinitoin();// 注意這裡,設定的是UserMapperbd.setBeanClassName(UserMapper.class.getName());SpringContainer.addBd(bd);複製程式碼
實際上給BeanDefinition對應的型別設定為一個介面是行不通的,因為Spring沒有辦法根據這個BeanDefinition去new出對應型別的例項,介面是沒法直接new出例項的。
所以想透過設定BeanDefinition的class型別,然後由Spring自動地幫助我們去生成對應的bean,但是這條路是行不通的。可以透過其它方式MapperFactoryBean來實現。
MapperFactoryBean繼承關係
透過繼承SqlSessionDaoSupport擁有了sqlSession,透過FactoryBean具備定製Bean的能力。對應的原始碼如下:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { ... /** * {@inheritDoc} */ @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } /** * {@inheritDoc} */ @Override public Class<T> getObjectType() { return this.mapperInterface; } /** * {@inheritDoc} */ @Override public boolean isSingleton() { return true; } ...}複製程式碼
getObjectType返回的是Mapper介面,透過 AbstractBeanDefinition.AUTOWIRE_BY_TYPE時,可自動注入使用。
getObject返回了動態代理物件,跟之前的使用一致。至此完成了Mapper對應BeanDefinition定義的問題,那這些BeanDefinition是如何註冊到Ioc容器呢?
3.2 Mapper BeanDefinition載入Spring可透過多種方式載入BeanDefinition,從XmlBeanDefinitionReader到ClassPathBeanDefinitionScanner在到ConfigurationClassBeanDefinitionReader分別對應xml、@component、@configuration類定義的載入。Mapper BeanDefinition可透過2種形式載入MapperScannerConfigurer和@MapperScan註解,內部都是透過ClassPathMapperScanner實現。ClassPathMapperScanner繼承了ClassPathBeanDefinitionScanner,類圖如下
ClassPathMapperScanner在完成類載入的基礎上,重新定義BeanDefinition,原始碼如下
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { ... private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); 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)) { if (explicitFactoryUsed) { logger.warn("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("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { if (logger.isDebugEnabled()) { logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } } ...}複製程式碼
透過definition.setBeanClass(this.mapperFactoryBean.getClass()) 完成了beanClass的轉換,同時設定了definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)保證了透過型別自動注入。
兩種方式如下
使用MapperScannerConfigurer載入
@Data@Import(HdsClassesDataSourceConfig.class)@Configuration@MapperScan(basePackages = "com.peppa.classes.server.**.dao", sqlSessionFactoryRef = "hdsSqlSessionFactory")@ConfigurationProperties(prefix = "hds")public class HdsDataSourceConfig { private String appName; private String group; private String token; private String configCenter; private long refreshInterval; static final String MAPPER_LOCATION = "classpath*:mapper/**/**.xml"; @Primary @Bean(name = "hdsDataSource", initMethod = "init", destroyMethod = "close") public HdsDataSource dsDataSource() { final HdsDataSource hdsDataSource = new HdsDataSource(); hdsDataSource.setAppName(appName); hdsDataSource.setGroup(group); hdsDataSource.setToken(token); hdsDataSource.setConfigCenter(configCenter); hdsDataSource.setRefreshInterval(refreshInterval); return hdsDataSource; } @Primary @Bean(name = "hdsTransactionManager") public DataSourceTransactionManager transactionManager(final HdsDataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Primary @Bean(name = "hdsSqlSessionFactory") public SqlSessionFactory hdsSqlSessionFactory(final HdsDataSource dataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); sessionFactory.setMapperLocations(resourcePatternResolver.getResources(MAPPER_LOCATION)); return sessionFactory.getObject(); }}複製程式碼
到此為止,Spring整合Mybatis的核心原理就結束了,再次總結一下:
定義MapperFactoryBean,用來封裝Mapper對應的BeanDefinition透過ClassPathMapperScanner重新定義BeanClass及AutowireMode,實現BeanDefinition載入及MapperInterface與MapperFactory整合透過MapperScannerConfigurer或@MapperScan,分別擴充套件BeanDefinitionRegistryPostProcessor及ImportBeanDefinitionRegistrar 用來在啟動Spring時執行呼叫ClassPathMapperScanner完成Mapper BeanDefinition的註冊。原文連結:https://juejin.cn/post/6936842731205427236