阿里面试题:Mybatis中的Dao接口和XML文件里的SQL是如何创建关系的?

前言

昨天,笔者在一篇面经中忽然看到阿里的这样一道面试题:面试

Mybatis中的Dao接口和XML文件里的SQL是如何创建关系的? 若是有两个XML文件和这个DAO创建关系,岂不是冲突了?spring

若是你看过笔者关于Mybatis源码分析的往期博文,相信你确定能够给出一个不错的答案。sql

但鉴于系列文章篇幅较大,并且重点是源码部分的解读,因此笔者想再针对这个问题,再梳理下整个流程。缓存

本文配合下列文章,食用更佳。bash

XML的解析和注解的支持mybatis

DAO接口是如何调用到的app

SQL语句的执行过程源码分析

1、解析XML

首先,Mybatis在初始化SqlSessionFactoryBean的时候,找到mapperLocations路径去解析里面全部的XML文件,这里咱们重点关注两部分。post

一、建立SqlSource

Mybatis会把每一个SQL标签封装成SqlSource对象。而后根据SQL语句的不一样,又分为动态SQL和静态SQL。其中,静态SQL包含一段String类型的sql语句;而动态SQL则是由一个个SqlNode组成。 ui

假如咱们有这样一个SQL:

<select id="getUserById" resultType="user">
	select * from user 
	<where>
		<if test="uid!=null">
			and uid=#{uid}
		</if>
	</where>
</select>	
复制代码

它对应的SqlSource对象看起来应该是这样的:

二、建立MappedStatement

XML文件中的每个SQL标签就对应一个MappedStatement对象,这里面有两个属性很重要。

  • id

全限定类名+方法名组成的ID。

  • sqlSource

当前SQL标签对应的SqlSource对象。

建立完MappedStatement对象,将它缓存到Configuration#mappedStatements中。

Configuration对象,咱们知道它就是Mybatis中的大管家,基本全部的配置信息都维护在这里。把全部的XML都解析完成以后,Configuration就包含了全部的SQL信息。

到目前为止,XML就解析完成了。看到上面的图示,聪明如你,也许就大概知道了。当咱们执行Mybatis方法的时候,就经过全限定类名+方法名找到MappedStatement对象,而后解析里面的SQL内容,执行便可。

2、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注解。

具体原理,请参看笔者文章:完全搞明白Spring中的自动装配和Autowired

说着说着可能扯远了,咱们继续回到Mybatis。那么,目前为止,咱们经过Dao接口也有了代理实现,因此就能够执行到它里面的方法了。

3、执行

如上所述,当咱们调用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并返回。

4、总结

到这里,再回到开头咱们提到的问题,也许你能更好的回答。同时笔者以为,这道题目,若是你覆盖到如下几个关键词,面试官可能会以为很满意。

  • SqlSource以及动态标签SqlNode
  • MappedStatement对象
  • Spring 工厂Bean 以及动态代理
  • SqlSession以及执行器

那么,针对第二个问题:若是有两个XML文件和这个Dao创建关系,岂不是冲突了?

答案也是显而易见,无论有几个XML和Dao创建关系,只要保证namespace+id惟一便可。

相关文章
相关标签/搜索