昨天,笔者在一篇面经中忽然看到阿里的这样一道面试题:面试
Mybatis中的Dao接口和XML文件里的SQL是如何创建关系的? 若是有两个XML文件和这个DAO创建关系,岂不是冲突了?spring
若是你看过笔者关于Mybatis源码分析的往期博文,相信你确定能够给出一个不错的答案。sql
但鉴于系列文章篇幅较大,并且重点是源码部分的解读,因此笔者想再针对这个问题,再梳理下整个流程。缓存
本文配合下列文章,食用更佳。bash
XML的解析和注解的支持mybatis
DAO接口是如何调用到的app
SQL语句的执行过程源码分析
首先,Mybatis在初始化SqlSessionFactoryBean
的时候,找到mapperLocations
路径去解析里面全部的XML文件,这里咱们重点关注两部分。post
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对象看起来应该是这样的:
XML文件中的每个SQL标签就对应一个MappedStatement对象,这里面有两个属性很重要。
全限定类名+方法名组成的ID。
当前SQL标签对应的SqlSource对象。
建立完MappedStatement
对象,将它缓存到Configuration#mappedStatements
中。
Configuration对象,咱们知道它就是Mybatis中的大管家,基本全部的配置信息都维护在这里。把全部的XML都解析完成以后,Configuration就包含了全部的SQL信息。
到目前为止,XML就解析完成了。看到上面的图示,聪明如你,也许就大概知道了。当咱们执行Mybatis方法的时候,就经过全限定类名+方法名
找到MappedStatement
对象,而后解析里面的SQL内容,执行便可。
咱们的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接口也有了代理实现,因此就能够执行到它里面的方法了。
如上所述,当咱们调用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并返回。
到这里,再回到开头咱们提到的问题,也许你能更好的回答。同时笔者以为,这道题目,若是你覆盖到如下几个关键词,面试官可能会以为很满意。
那么,针对第二个问题:若是有两个XML文件和这个Dao创建关系,岂不是冲突了?
答案也是显而易见,无论有几个XML和Dao创建关系,只要保证namespace+id
惟一便可。