看到阿里这道面试题的时候,我就知道是时候看下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> 复制代码
首先,Mybatis在初始化SqlSessionFactoryBean的时候,找到mapperLocations路径去解析里面所有的XML文件
Mybatis会把每个SQL标签封装成SqlSource对象。然后根据SQL语句的不同,又分为动态SQL和静态SQL。其中,静态SQL包含一段String类型的sql语句;而动态SQL则是由一个个SqlNode组成。
** 如下面demo 就生成dynamicSqlSource**
生成的sqlsource
2. 创建MappedStatementXML文件中的每一个SQL标签就对应一个MappedStatement对象,这里面有两个属性很重要。
3. 缓存到Configuration所有xml解析完后,configuration对象具有所有sql信息
configuration是mybatis非常重要的一个属性
讲原理之前我们得知道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对象中设置了一些属性。我们重点关注两个。
设置BeanDefinition对象的BeanClass为MapperFactoryBean<?>。这意味着什么呢?以UserMapper为例,意味着当前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那么在IOC初始化的时候,实例化的对象就是MapperFactoryBean对象。
为BeanDefinition对象添加属性sqlSessionFactory,这就意味着,在为BeanDefinition对象设置PropertyValue的时候,会调用到setSqlSessionFactory()。
查看MapperFactoryBean
上面步骤了解到 我们之前在BeanDefinition对象添加属性sqlSessionFactory,也意味着setSqlSessionFactory()会被执行 进到里面可以看到sqlSession实际上就是SqlSessionTemplate
最终是给sqlSessionProxy实例化了一个jdk代理对象 在setSqlSessionFactory这个方法里,sqlSession获取到的是SqlSessionTemplate实例。而在SqlSessionTemplate对象中,主要包含sqlSessionFactory和sqlSessionProxy,而sqlSessionProxy实际上是SqlSession接口的代理对象。
现在每一个mapper都是一个MapperFactoryBean MapperFactoryBean是一个工厂Bean
现在我们通过spring注入一个Mapper @Autowired UserMapper userMapper 装配时会执行以下代码
有个问题是knownMappers是从哪儿来的呢?它为什么可以根据type接口就能获取到MapperProxyFactory实例呢? 查看DaoSupport发现它实现了InitializingBean 所以会在类初始化时 调用afterPropertiesSet,最终会调用到addMapper的方法
getMapper 执行到new Instance()
也就是说 最终getObject获取到的是一个MapperProxy 此时注入的就是一个MapperProxy当执行userMapper.XXX()时,会进入
重要的方法下面的mapperMethod.execute(sqlSession, args)
这个方法比较简单,就是根据节点的类型,进行相应的处理。比如节点是insert 那就走到insert的逻辑
如果节点类型是select,方法返回值是list,所以代码执行了这个方法
重点方法在 sqlSession.selectList(command.getName(), param, rowBounds);
上面讲到sqlSession是sqlSessionTemplate 进入方法
sqlSessionProxy也是个代理对象,总之它实际会调用到SqlSessionInterceptor.invoke()。
重点代码几乎已经走完 下面是走到底执行底层jdbc