看到阿里這道面試題的時候,我就知道是時候看下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** 生成的sqlsource2. 建立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 進入方法
sqlSessionProxy也是個代理物件,總之它實際會呼叫到SqlSessionInterceptor.invoke()。獲取MappedStatement物件
重點程式碼幾乎已經走完 下面是走到底執行底層jdbc總的來說,就是 通過statement全限定型別+方法名拿到MappedStatement 物件,然後通過執行器Executor去執行具體SQL並返回
參考: [1]: juejin.im/post/5c84b4… [2]: juejin.im/post/5c84b4…