1. 程式人生 > >Mybatis中的Mapper接口和XML文件裏的SQL是如何建立關系的?

Mybatis中的Mapper接口和XML文件裏的SQL是如何建立關系的?

根據 找到 bean 動態sql val 規範 apc 其中 接口

一、解析XML

首先,Mybatis在初始化SqlSessionFactoryBean的時候,找到mapperLocations路徑去解析裏面所有的XML文件,這裏我們重點關註兩部分。

1、創建SqlSource

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

2、創建MappedStatement

XML文件中的每一個SQL標簽就對應一個MappedStatement對象,這裏面有兩個屬性很重要。

  • id

全限定類名+方法名組成的ID。

  • sqlSource

當前SQL標簽對應的SqlSource對象。

創建完MappedStatement對象,將它緩存到Configuration#mappedStatements中。
Configuration對象,我們知道它就是Mybatis中的大管家,基本所有的配置信息都維護在這裏。把所有的XML都解析完成之後,Configuration就包含了所有的SQL信息。

到目前為止,XML就解析完成了。看到上面的圖示,聰明如你,也許就大概知道了。當我們執行Mybatis方法的時候,就通過全限定類名+方法名找到MappedStatement對象,然後解析裏面的SQL內容,執行即可。

二、Dao接口代理

我們的Dao接口並沒有實現類,那麽,我們在調用它的時候,它是怎樣最終執行到我們的SQL語句的呢?

首先,我們在Spring配置文件中,一般會這樣配置:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.viewscenes.netsupervisor.dao" />
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

  

或者你的項目是基於SpringBoot的,那麽肯定也見過這種:MapperScan("com.xxx.dao")

它們的作用是一樣的。將包路徑下的所有類註冊到Spring Bean中,並且將它們的beanClass設置為MapperFactoryBean

有意思的是,MapperFactoryBean實現了FactoryBean接口,俗稱工廠Bean。那麽,當我們通過@Autowired註入這個Dao接口的時候,返回的對象就是MapperFactoryBean這個工廠Bean中的getObject()方法對象。

那麽,這個方法幹了些什麽呢?
簡單來說,它就是通過JDK動態代理,返回了一個Dao接口的代理對象,這個代理對象的處理器是MapperProxy對象。所有,我們通過@Autowired註入Dao接口的時候,註入的就是這個代理對象,我們調用到Dao接口的方法時,則會調用到MapperProxy對象的invoke方法。

對這塊內容不太能理解的朋友,可以先看看Spring中的FactoryBean 和 JDK動態代理相關知識。

曾經有個朋友問過這樣一個問題:

對於有實現的dao接口,mapper還會用代理麽?

答案是肯定的,只要你配置了MapperScan,它就會去掃描,然後生成代理。但是,如果你的dao接口有實現類,並且這個實現類也是一個Spring Bean,那就要看你在Autowired的時候,去註入哪一個了。

具體什麽意思呢?我們來到一個例子。
如果我們給userDao搞一個實現類,並且把它註冊到Spring。

@Component
public class UserDaoImpl implements UserDao{
	public List<User> getUserList(Map<String,Object> map){
		return new ArrayList<User>();
	}
}

  然後我們在Service方法中,註入這個userDao。猜猜會發生什麽?

@Service
public class UserServiceImpl implements UserService{

	@Autowired
	UserMapper userDao1;

	public List<User> getUserList(Map<String,Object> map) {
		return userDao1.getUserList(map);
	}
}

  也許你已經猜到了,是的,它會啟動報錯。因為在註入的時候,找到了兩個UserMapper的實例對象。日誌是這樣的:

No qualifying bean of type [com.viewscenes.netsupervisor.dao.UserDao] is defined: expected single matching bean but found 2: userDaoImpl,userDao

  當然了,也許我們的命名不太規範。其實我們通過名字註入就可以了,像這樣: @Autowired UserMapper userDao; 或者 @Autowired UserMapper userDaoImpl; 再或者給你其中一個Bean加上@Primary註解。

三、執行

如上所述,當我們調用Dao接口方法的時候,實際調用到代理對象的invoke方法。 在這裏,實際上調用的就是SqlSession裏面的東西了。

public class DefaultSqlSession implements SqlSession {

	public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
		try {
			MappedStatement ms = configuration.getMappedStatement(statement);
			return executor.query(ms, 
				wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
		}
	}
}

  看到以上代碼,說明我們想的不錯。它就是通過statement全限定類名+方法名拿到MappedStatement 對象,然後通過執行器Executor去執行具體SQL並返回。

四、總結

到這裏,再回到開頭我們提到的問題,也許你能更好的回答。同時筆者覺得,這道題目,如果你覆蓋到以下幾個關鍵詞,面試官可能會覺得很滿意。

  • SqlSource以及動態標簽SqlNode
  • MappedStatement對象
  • Spring 工廠Bean 以及動態代理
  • SqlSession以及執行器

那麽,針對第二個問題:如果有兩個XML文件和這個Dao建立關系,豈不是沖突了?
答案也是顯而易見,不管有幾個XML和Dao建立關系,只要保證namespace+id唯一即可。

Mybatis中的Mapper接口和XML文件裏的SQL是如何建立關系的?