mybatis-spring啟動到使用

風中有朵雲做的雨發表於2019-04-11

看到阿里這道面試題的時候,我就知道是時候看下mybatis原始碼了

Mybatis的啟動

重要配置

<!-- 會話工廠bean sqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 資料來源 -->
        <property name="dataSource" ref="datasource"></property>
        <!-- 別名 -->
        <property name="typeAliasesPackage" value="com.jesse.bookstore.entities"></property>
        <!-- sql對映檔案路徑 -->
        <property name="mapperLocations" value="classpath*:com/zhangguo/bookstore/mapper/*Mapper.xml"></property>
    </bean>
    
    <!-- 自動掃描物件關係對映 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定會話工廠,如果當前上下文中只定義了一個則該屬性可省去 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <!-- 指定要自動掃描介面的基礎包,實現介面 -->
        <property name="basePackage" value="com.jesse.bookstore.mapper"></property>
    </bean>
複製程式碼

解析XML

首先,Mybatis在初始化SqlSessionFactoryBean的時候,找到mapperLocations路徑去解析裡面所有的XML檔案

1. 根據mapper中的每句SQL生成對應的SqlSource

Mybatis會把每個SQL標籤封裝成SqlSource物件。然後根據SQL語句的不同,又分為動態SQL和靜態SQL。其中,靜態SQL包含一段String型別的sql語句;而動態SQL則是由一個個SqlNode組成。

在這裡插入圖片描述
** 如下面demo 就生成dynamicSqlSource**
打打所大所大所大多所
生成的sqlsource
在這裡插入圖片描述

2. 建立MappedStatement XML檔案中的每一個SQL標籤就對應一個MappedStatement物件,這裡面有兩個屬性很重要。

在這裡插入圖片描述

3. 快取到Configuration 所有xml解析完後,configuration物件具有所有sql資訊

在這裡插入圖片描述
configuration是mybatis非常重要的一個屬性

Dao與xml如何生效

講原理之前我們得知道mybatis是怎麼用的

public interface UserMapper {	
	List<User> getUserList();
}

@Service
public class UserServiceImpl implements UserService{
	@Autowired
	UserMapper userDao;
	@Override
	public List<User> getUserList() {
		return userDao.getUserList();
	}
}
複製程式碼

userDao沒有任何實現,為什麼可以執行呢?

掃描

首先配置掃描器

在這裡插入圖片描述
配置了掃描器 又是怎麼生效的呢

檢視原始碼注意到 有這麼一個類

在這裡插入圖片描述
它實現了BeanDefinitionRegistryPostProcessor。 在spring中,它可以 動態的註冊Bean資訊,方法 postProcessBeanDefinitionRegistry()

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	//建立ClassPath掃描器,設定屬性,然後呼叫掃描方法
	ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
	scanner.setAnnotationClass(this.annotationClass);
	scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
	//如果配置了annotationClass,就將其新增到includeFilters
	scanner.registerFilters();
	scanner.scan(this.basePackage);
}

複製程式碼

複製程式碼ClassPathMapperScanner繼承自Spring中的類ClassPathBeanDefinitionScanner,所以scan方法會呼叫到父類的scan方法,而在父類的scan方法中又呼叫到子類的doScan方法。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
		//呼叫Spring的scan方法。就是將基本包下的類註冊為BeanDefinition
		Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
		processBeanDefinitions(beanDefinitions);
		return beanDefinitions;
	}
}
複製程式碼

super.doScan(basePackages)是Spring中的方法。我主要看它返回的是BeanDefinition的集合。 3、配置BeanDefinition 上面已經掃描到了所有的Mapper介面,並將其註冊為BeanDefinition物件。接下來呼叫processBeanDefinitions()要配置這些BeanDefinition物件。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

	private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();
	
	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
		GenericBeanDefinition definition;
		for (BeanDefinitionHolder holder : beanDefinitions) {
			definition = (GenericBeanDefinition) holder.getBeanDefinition();
			
			//將mapper介面的名稱新增到構造引數
			definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
			//設定BeanDefinition的class
			definition.setBeanClass(this.mapperFactoryBean.getClass());
			//新增屬性addToConfig
			definition.getPropertyValues().add("addToConfig", this.addToConfig);
			//新增屬性sqlSessionFactory
			definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
			......
	}
}
複製程式碼

複製程式碼處理的過程很簡單,就是往BeanDefinition物件中設定了一些屬性。我們重點關注兩個。

設定beanClass

設定BeanDefinition物件的BeanClass為MapperFactoryBean<?>。這意味著什麼呢?以UserMapper為例,意味著當前的mapper介面在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那麼在IOC初始化的時候,例項化的物件就是MapperFactoryBean物件。

設定sqlSessionFactory屬性

為BeanDefinition物件新增屬性sqlSessionFactory,這就意味著,在為BeanDefinition物件設定PropertyValue的時候,會呼叫到setSqlSessionFactory()。

建立SqlSession的代理

檢視MapperFactoryBean

在這裡插入圖片描述
在這裡插入圖片描述
上面步驟瞭解到 我們之前在BeanDefinition物件新增屬性sqlSessionFactory,也意味著setSqlSessionFactory()會被執行 進到裡面可以看到sqlSession實際上就是SqlSessionTemplate
在這裡插入圖片描述
最終是給sqlSessionProxy例項化了一個jdk代理物件 在setSqlSessionFactory這個方法裡,sqlSession獲取到的是SqlSessionTemplate例項。而在SqlSessionTemplate物件中,主要包含sqlSessionFactory和sqlSessionProxy,而sqlSessionProxy實際上是SqlSession介面的代理物件。

建立Mapper介面的代理

現在每一個mapper都是一個MapperFactoryBean MapperFactoryBean是一個工廠Bean

注入mapper

現在我們通過spring注入一個Mapper @Autowired UserMapper userMapper 裝配時會執行以下程式碼

在這裡插入圖片描述
在這裡插入圖片描述
有個問題是knownMappers是從哪兒來的呢?它為什麼可以根據type介面就能獲取到MapperProxyFactory例項呢? 檢視DaoSupport發現它實現了InitializingBean 所以會在類初始化時 呼叫afterPropertiesSet,最終會呼叫到addMapper的方法
在這裡插入圖片描述
在這裡插入圖片描述

getMapper 執行到new Instance()

在這裡插入圖片描述
也就是說 最終getObject獲取到的是一個MapperProxy 此時注入的就是一個MapperProxy

執行mapper的方法

當執行userMapper.XXX()時,會進入

在這裡插入圖片描述
重要的方法下面的mapperMethod.execute(sqlSession, args)
在這裡插入圖片描述

這個方法比較簡單,就是根據節點的型別,進行相應的處理。比如節點是insert 那就走到insert的邏輯

如果節點型別是select,方法返回值是list,所以程式碼執行了這個方法

在這裡插入圖片描述
重點方法在 sqlSession.selectList(command.getName(), param, rowBounds);

上面講到sqlSession是sqlSessionTemplate 進入方法

mybatis-spring啟動到使用
sqlSessionProxy也是個代理物件,總之它實際會呼叫到SqlSessionInterceptor.invoke()。
在這裡插入圖片描述

獲取MappedStatement物件

在這裡插入圖片描述
重點程式碼幾乎已經走完 下面是走到底執行底層jdbc
在這裡插入圖片描述

總的來說,就是 通過statement全限定型別+方法名拿到MappedStatement 物件,然後通過執行器Executor去執行具體SQL並返回

參考: [1]: juejin.im/post/5c84b4… [2]: juejin.im/post/5c84b4…

相關文章