mybatis-spring啓動到使用

看到阿里這道面試題的時候,我就知道是時候看下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文件spring

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

Mybatis會把每一個SQL標籤封裝成SqlSource對象。而後根據SQL語句的不一樣,又分爲動態SQL和靜態SQL。其中,靜態SQL包含一段String類型的sql語句;而動態SQL則是由一個個SqlNode組成。 緩存

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

2. 建立MappedStatement XML文件中的每個SQL標籤就對應一個MappedStatement對象,這裏面有兩個屬性很重要。 bash

在這裏插入圖片描述

3. 緩存到Configuration 全部xml解析完後,configuration對象具備全部sql信息 mybatis

在這裏插入圖片描述
configuration是mybatis很是重要的一個屬性

Dao與xml如何生效

講原理以前咱們得知道mybatis是怎麼用的app

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

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

userDao沒有任何實現,爲何能夠執行呢?ide

掃描

首先配置掃描器 post

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

查看源碼注意到 有這麼一個類 ui

在這裏插入圖片描述
它實現了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的集合。 三、配置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 進入方法

sqlSessionProxy也是個代理對象,總之它實際會調用到SqlSessionInterceptor.invoke()。
在這裏插入圖片描述

獲取MappedStatement對象

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

總的來講,就是 經過statement全限定類型+方法名拿到MappedStatement 對象,而後經過執行器Executor去執行具體SQL並返回

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

相關文章
相關標籤/搜索