Spring mybatis源码篇章-sql mapper配置文件绑定mapper class类

素颜马尾好姑娘i 2021-06-24 15:57 424阅读 0赞

http://www.cnblogs.com/question-sky/p/6654101.html

背景知识

  1. MappedStatement是mybatis操作sql语句的持久层对象,其id由注解模式的${mapperInterface类全名}.${methodName}或者XML模式的${namespace}.${CRUD标签的id}确定,且是唯一的
  2. Mybatis对每个CRUD语句都会生成唯一的MappedStatement对象保存至Configuration的mappedStatementsMap集合中
  3. Mybatis提供注解模式和XML模式生成MappedStatement,在两者同时存在的情况下,注解模式的MappedStatement会覆盖同id的XML模式的MappedStatement
  4. 针对非注解模式即XML模式的生成MappedStatement,还必须拥有对应的mapperInterface接口供Service层调用,即mapperInterface接口是需要注册到Configuration的MapperRegistry类中,方便Service层找寻调用(这也是本文的重点讲述
  5. 注解模式生成MappedStatement的途径有两个,一个是在其同目录下存在与类名一致的sql mapper文件;另一个是其方法名实现了CRUD的注解,其中注解与sql mapper存在同id,遵循第3点

本章主题

前文介绍到XMLMapperBuilder是如何通过扫描SQL Mapper配置文件中的标签后组装成MappedStament对象,本章则针对背景知识中的第四点来展开如何绑定mapper sql配置文件到interface

Spring Mybatis 接口注入老配置

  1. <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  2. <property name="mapperInterface" value="com.test.sqlmapper.UserMapper"/>
  3. <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  4. </bean>

上述的配置是针对单个的mapperInterface注入到应用程序中,试想如果有很多的接口则会导致Spring 主配置文件臃肿,所以上述的办法已过时

Spring Mybatis 接口注入新配置

  1. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="org.mybatis.spring.sample.mapper" /> <!-- optional unless there are multiple session factories defined --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>

采用MapperScannerConfigurer扫描类来实现对basePackage指定的路径进行接口的全部注入,节省了之前老配置很多的代码空间。

  1. 直接查看MapperScannerConfigurer类的源码了解其中的来龙去脉,内部属性如下:

    1. public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    2. private String basePackage;
    3. private boolean addToConfig = true;
    4. private SqlSessionFactory sqlSessionFactory;
    5. private SqlSessionTemplate sqlSessionTemplate;
    6. private String sqlSessionFactoryBeanName;
    7. private String sqlSessionTemplateBeanName;
    8. private Class<? extends Annotation> annotationClass;
    9. private Class<?> markerInterface;
    10. private ApplicationContext applicationContext;
    11. private String beanName;
    12. private boolean processPropertyPlaceHolders;
    13. private BeanNameGenerator nameGenerator;
    14. ....
    15. }

    其源码上的注释其实写的很清楚了,注释篇幅过长,就不在这里展示了,稍微提下这个类的相关使用:

    • basePackage 基本属性,接口类扫描的包路径,支持,;分隔
    • sqlSessionFactoryBeanName 当有多个SqlSessionFactory环境时,官方通过其来指定加载特定的sqlSessionFactory,value即为bean的id值。与sqlSessionFactory属性的区别在于可以在使用mybatis的时候才会去调用sqlSessionFactory实例,建议使用此属性
    • sqlSessionFactoty 默认是不用填的,其会去寻找id为sqlSessionFactory的sqlSessionFactory实例,sqlSessionTemplate的操作与其是一致的
    • annotationClass 注解类,其会去寻找拥有此注解的接口类,并忽略basePackage的属性,默认为null
    • markerInterface 父类接口类,其会去寻找继承此接口类的子接口类并不包括其父类接口,并忽略basePackage的属性,与annotationClass并存,默认为null
  2. 具体调用接口方法MapperScannerConfigurer#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)

    1. //支持${basePackage}形式
    2. if (this.processPropertyPlaceHolders) {
    3. processPropertyPlaceHolders();
    4. }
    5. //容易知道其是根据classpath路径来寻找资源的
    6. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    7. scanner.setAddToConfig(this.addToConfig);
    8. scanner.setAnnotationClass(this.annotationClass);
    9. scanner.setMarkerInterface(this.markerInterface);
    10. scanner.setSqlSessionFactory(this.sqlSessionFactory);
    11. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    12. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    13. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    14. scanner.setResourceLoader(this.applicationContext);
    15. scanner.setBeanNameGenerator(this.nameGenerator);
    16. //使用过滤器,主要是annotationClass和markerInterface过滤器
    17. scanner.registerFilters();
    18. //扫描指定的basePackage
    19. scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

    查看ClassPathMapperScannerdoScan(String.. packages)方法:

    1. //由父类去找到符合条件的interface类,并转化为bean类
    2. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    3. if (beanDefinitions.isEmpty()) {
    4. logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    5. } else {
    6. //处理找到的interface bean类
    7. processBeanDefinitions(beanDefinitions);
    8. }
    9. return beanDefinitions;

    转而看ClassPathMapperScanner#processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)方法

    1. GenericBeanDefinition definition;
    2. for (BeanDefinitionHolder holder : beanDefinitions) {
    3. definition = (GenericBeanDefinition) holder.getBeanDefinition();
    4. if (logger.isDebugEnabled()) {
    5. logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
    6. + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    7. }
    8. // the mapper interface is the original class of the bean
    9. // but, the actual class of the bean is MapperFactoryBean
    10. definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
    11. //最终将definition包装成MapperFactoryBean,beanClass设置为其内部属性MapperInterface
    12. definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
    13. definition.setBeanClass(MapperFactoryBean.class);
    14. definition.getPropertyValues().add("addToConfig", this.addToConfig);
    15. boolean explicitFactoryUsed = false;
    16. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
    17. //根据sqlsessionFactoryBeanName寻找运行状态的SqlsessionFactory的虚引用,但并没有去真实加载
    18. definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
    19. explicitFactoryUsed = true;
    20. } else if (this.sqlSessionFactory != null) {
    21. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
    22. explicitFactoryUsed = true;
    23. }
    24. if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
    25. if (explicitFactoryUsed) {
    26. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
    27. }
    28. definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
    29. explicitFactoryUsed = true;
    30. } else if (this.sqlSessionTemplate != null) {
    31. if (explicitFactoryUsed) {
    32. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
    33. }
    34. definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
    35. explicitFactoryUsed = true;
    36. }
    37. //当没有指定SqlSession对象,则需要@Autowired注解去注入
    38. if (!explicitFactoryUsed) {
    39. if (logger.isDebugEnabled()) {
    40. logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
    41. }
    42. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    43. }
    44. }

    接着看MapperFactoryBean的实现代码,主要关注其的checkDaoConfig()方法:

    1. super.checkDaoConfig();
    2. notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    3. Configuration configuration = getSqlSession().getConfiguration();
    4. //我们看到了熟悉的configuration.addMapper,也就是会走到MapperRegistry#addMapper,详情可见
    5. //Spring mybatis源码篇章-MybatisDAO文件解析(一)
    6. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    7. try {
    8. //这里顺便提一下,此方法的最主要目的是保存至MapperRegistry#knowMappers集合中,方便service层找寻调用
    9. configuration.addMapper(this.mapperInterface);
    10. } catch (Exception e) {
    11. logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
    12. throw new IllegalArgumentException(e);
    13. } finally {
    14. ErrorContext.instance().reset();
    15. }
    16. }

总结

  1. MapperScannerConfigurer类主要实现将basePackage包下的所有接口类注册到Configuration#MapperRegister#knowMappers<Class<?>,MapperProxyFactory<T>(Class<?>)>集合中
  2. MapperScannerConfigurer类默认情况下在形成MappedStatement的过程中会优先去找寻与接口同目录下的XML文件来加载生成,如果想应用XML配置文件且可以任意放置,则可以结合sqlSessionFactoryBeanmapperLocations属性来完成自由化绑定的过程,同样本文就是建立在此属性已指定的基础上的
  3. MappedStatement对象的生成与MapperInterfaces接口类是一一对应的

    • MapperInterfaces接口类可通过注解例如@Select方式生成注解,即脱离XML配置方式
    • MapperInterfaces接口类如果在SqlsessionFactory不使用mapperLocations属性时且不使用注解方式,则必须在其同目录下有同名字的XML mapper文件,否则无法访问数据库;反之使用mapperLocations属性且不使用注解方式,则XML mapper文件只需放在classpath路径下即可

发表评论

表情:
评论列表 (有 0 条评论,424人围观)

还没有评论,来说两句吧...

相关阅读