在開始正文以前,首先解釋Dao接口和XML文件裏的SQL是如何一一對應的?java
一句話講完就是:mybatis 會先解析這些xml 文件,經過 xml 文件裏面的命名空間 (namespace)跟dao 創建關係;而後 xml 中的每段 sql 會有一個id 跟 dao 中的接口進行關聯。spring
那麼問題來了: "若是 我有兩個這個xml 文件 都跟這個dao 創建關係了,那不是就是衝突了?"sql
帶着這個疑問咱們就要開始下面的正題了!數據庫
首先咱們要知道每一個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲中心的,SqlSessionFactory 的實例能夠經過 SqlSessionFactoryBuilder 得到。緩存
但SqlSessionFactory
是一個接口,它裏面其實就兩個方法:openSession
、getConfiguration
微信
其中,openSession
方法是爲了獲取一個SqlSession對象,完成必要數據庫增刪改查功能。可是,SqlSessionFactory屬性太少了,因此須要getConfiguration
的配合;來配置mapper映射文件、SQL參數、返回值類型、緩存等屬性。session
/**
* Creates an {@link SqlSession} out of a connection or a DataSource
*
* @author Clinton Begin
*/
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}複製代碼
能夠看到getConfiguration是屬於Configuration類的一個方法。你能夠把它當成一個配置管家。MyBatis全部的配置信息都維持在Configuration對象之中,基本每一個對象都會持有它的引用。mybatis
但平常開發中咱們都是將Mybatis與Spring一塊兒使用的,因此把實例化交給Spring處理。app
所以咱們能夠看下org.mybatis.spring.SqlSessionFactoryBean
,它實現了InitializingBean接口。這說明,在這個類被實例化以後會調用到afterPropertiesSet()。它只有一個方法ide
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}複製代碼
而這個afterPropertiesSet
方法只有一個動做,就是buildSqlSessionFactory
。它能夠分爲兩部分來看:
大體有以下兩種方式:
使用Mybatis提供的API進行操做,經過獲取SqlSession對象,而後根據Statement Id 和參數來操做數據庫。
String statement = "com.mmzsblog.business.dao.MemberMapper.getMemberList";
List<Member> result = sqlsession.selectList(statement);複製代碼
定義Mapper接口,並在裏面定義一系列業務數據操做方法。在Service層經過注入mapper屬性,調用其方法就能夠執行數據庫操做。就像下面這樣
public interface MemberMapper {
List<Member> getMemberList();
}
@Service
public class MemberServiceImpl implements MemberService{
@Resource
private MemberMapper memberMapper;
@Override
public List<Member> getMemberList() {
return memberMapper.getMemberList();
}
}複製代碼
那麼,MemberMapper 只是個接口,並無任何實現類。咱們在調用它的時候,它是怎樣最終執行到咱們的SQL語句的呢?
經過註解的方式配置:
@MapperScan({"com.mmzsblog.business.dao"})複製代碼
或者xml的方式配置:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.mmzsblog.business.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>複製代碼
咱們來到org.mybatis.spring.mapper.MapperScannerConfigurer
這個類,能夠看到它實現了幾個接口。
其中的重點是BeanDefinitionRegistryPostProcessor
。它能夠動態的註冊Bean信息,方法爲postProcessBeanDefinitionRegistry()
。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
// 建立ClassPath掃描器,設置屬性,而後調用掃描方法
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
// 建立ClassPath掃描器,設置屬性,而後調用掃描方法
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}複製代碼
ClassPathMapperScanner
繼承自Spring中的類ClassPathBeanDefinitionScanner
,因此它的scan方法會調用到父類ClassPathBeanDefinitionScanner
的scan方法,
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
……
public int scan(String... basePackages) {
//
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
this.doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
……
} 複製代碼
而在父類的scan方法中又調用到子類ClassPathMapperScanner
重寫的doScan方法。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
……
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
……
} 複製代碼
此處super.doScan(basePackages)
是Spring中的方法,就不貼代碼多敘述了,想詳細瞭解的話,能夠本身翻一下源碼哦。
而且通過上面這些步驟,此時已經掃描到了全部的Mapper接口,並將其註冊爲BeanDefinition對象。而註冊的時候就是用到了上面doScan
方法中的processBeanDefinitions
方法。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
……
// 設置beanClass
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean();
……
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
Iterator var3 = beanDefinitions.iterator();
while(var3.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// 將mapper接口的名稱添加到構造參數
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
// 設置BeanDefinition的class
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 添加屬性addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 添加屬性sqlSessionFactory
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) {
this.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) {
this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(2);
}
}
}
……
} 複製代碼
處理的過程相對比較簡單,只是往BeanDefinition對象中設置了一些屬性。例如:
設置BeanDefinition對象的BeanClass爲MapperFactoryBean
。這就至關於使用MemberMapper註冊時:當前的mapper接口在Spring容器中,beanName是memberMapper,beanClass是MapperFactoryBean.class。故在Spring的IOC初始化的時候,實例化的對象就是MapperFactoryBean對象。
爲BeanDefinition對象添加屬性sqlSessionFactory,是爲了BeanDefinition對象設置PropertyValue的時候,方便調用到setSqlSessionFactory()。
最終在setSqlSessionFactory這個方法裏,sqlSession獲取到的是SqlSessionTemplate實例。而在SqlSessionTemplate對象中,主要包含sqlSessionFactory和sqlSessionProxy,而sqlSessionProxy其實是SqlSession接口的代理對象。實際調用的是代理類的invoke方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
……
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
……
} 複製代碼
Mapper接口的代理建立過程大體以下:
最後咱們在Service層,經過
@Resource
private MemberMapper memberDao;複製代碼
注入屬性的時候,返回的就是代理類。執行memberDao的方法的時候,實際調用的也是代理類的invoke方法。
Mybatis在初始化SqlSessionFactoryBean的時候,找到配置須要掃描的基本包路徑去解析裏面全部的XML文件。重點就在以下兩個地方:
一、建立SqlSource
Mybatis會把每一個SQL標籤封裝成SqlSource對象。而後根據SQL語句的不一樣,又分爲動態SQL和靜態SQL。其中,靜態SQL包含一段String類型的sql語句;而動態SQL則是由一個個SqlNode組成。
二、建立MappedStatement
XML文件中的每個SQL標籤就對應一個MappedStatement對象,這裏面有兩個屬性很重要。
全限定類名+方法名組成的ID。
當前SQL標籤對應的SqlSource對象。建立完MappedStatement
對象,會將它緩存到Configuration#mappedStatements
中。
前面初始化中提到的Configuration對象,咱們知道它就是Mybatis中的配置大管家,基本全部的配置信息都維護在這裏。
例以下面這樣一段代碼:
<!-- namespace的值就是全限定類名 -->
<mapper namespace="com.java.mmzsblog.dao.MemberMapper">
……
<!-- select標籤中id的值就是方法名,它和全限定類中的方法名是對應的 -->
<select id="getMemberById" resultType="com.java.mmzsblog.entity.member">
select * from member
<where>
<if test="memberId!=null">
and member_id=#{memberId}
</if>
</where>
</select>
……
</mapper> 複製代碼
把全部的XML都解析完成以後,Configuration就包含了全部的SQL信息。而後解析完成的XML大概就是這樣了:
看到上面的圖示,聰明如你,也許就大概知道了。當咱們執行Mybatis方法的時候,就經過全限定類名+方法名
找到MappedStatement對象,而後解析裏面的SQL內容,執行便可。
歡迎關注公衆號:
我的博客網站:www.mmzsblog.cn