Springboot集成mybatis,解读源码SQL 映射语句的路径配置

目录

一、背景

二、排查过程

2.1 Springboot的自动配置

2.2 Spring获取资源

2.3 AntPathMatcher,spring默认的路径匹配器

三、解决

 

 

四、小结

 

 


一、背景

 最近在新项目中在用springboot结合mybatis,一开始采取全注解模式,即,sql语句也写到接口注解上。后来,考虑到复杂sql语句在注解上编写的可读性和可维护性,决定将sql语句转移到mybatis常用的xml映射器上。

原mapper接口写法如下:

@Mapper
public interface AuditBillVMapper {

	@Select("select t.* from audit_bill_v t where t.task_user_no=#{userNo}")
	List getByTaskUserNo(String userNo);
	
}

加入sql映射器xml文件后写法如下:

AuditBillVMapper.class

@Mapper
public interface AuditBillVMapper {

	List getByTaskUserNo(String userNo);
	
}

AuditBillVMapper.xml



 


  
  

然而,在xml到mapper接口的映射上遇到了问题:sql语句映射器的xml文件跟mapper接口class怎么文件对应呢?mybatis官方文档有关于mybatis找到这些xml映射器的xml配置,例如(省略其他配置):




  
    
  

但是问题来了:

1.作为一个基于springboot的项目,这些xml配置文件无法忍受;

2.每次添加sql映射xml文件时,都需要在这个mybatis-config.xml添加xml文件资源路径,不易于维护。

有关mybatis的介绍和入门这里就不阐述了,官方有较为详细的中文文档。链接:mybatis入门

二、排查过程

2.1 Springboot的自动配置

一个基于springboot框架的项目,首先想到的当然是自动配置。显然springboot对于mybatis也是有自动配置的,然后我找到了自动配置类:

Springboot集成mybatis,解读源码SQL 映射语句的路径配置_第1张图片

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的(不了解可跳转:mybatis入门)。因而我在MybatisAutoConfiguration.class中找到这个方法sqlSessionFactory:

 @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    //mapperLocations,显然这就是配置mapper路径的地方
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
  }

这个方法里面便是springboot对mybatis的一些默认自动配置,我们现在里面找到factory.setMapperLocations,从字面上看这应是设置mapper路径的地方,因而跟进SqlSessionFactoryBean.setMapperLocations看到源码上的注释如下:

SqlSessionFactoryBean.class

 /**
   * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory}
   * configuration at runtime.
   *
   * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file.
   * This property being based on Spring's resource abstraction also allows for specifying
   * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml".
   */
  //官方注释说的大致如此:
  //设置即将写进SqlSessionFactory的MyBatis的mapper文件的路径。
  //这将替代mybatis配置文件中指定的""实体
  //此属性基于spring的抽象资源模式(Resource),也允许像这样指定资源模式:“classpath*:sqlmap/*-mapper.xml”
  public void setMapperLocations(Resource[] mapperLocations) {
    this.mapperLocations = mapperLocations;
  }

到了这里,我们基本可以肯定就是SqlSessionFactoryBean.mapperLocations就是我们需要配置的地方,所以我们回到自动配置方法里:

MybatisAutoConfiguration.class

private final MybatisProperties properties;
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

为了了解到this.properties.resolveMapperLocations()是什么,我们跟进到MybatisProperties.class这个类里。部分代码如下:

/**
 * Configuration properties for MyBatis.
 *
 * @author Eddú Meléndez
 * @author Kazuki Shimizu
 */
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
  public static final String MYBATIS_PREFIX = "mybatis";
  /**
   * Location of MyBatis xml config file.
   */
  private String configLocation;
  /**
   * Locations of MyBatis mapper files.
   */
  private String[] mapperLocations;
  /**
   * Packages to search type aliases. (Package delimiters are ",; \t\n")
   */
  private String typeAliasesPackage;
  /**
   * Packages to search for type handlers. (Package delimiters are ",; \t\n")
   */
  private String typeHandlersPackage;
  /**
   * Indicates whether perform presence check of the MyBatis xml config file.
   */
  private boolean checkConfigLocation = false;
}

显然,这个类的属性就是springboot的默认配置文件application.properties对应mybatis配置的属性了。例如需要配置mybatis的typeAliasesPackage属性,可以在application.properties中添加mybatis.typeAliasesPackage=com.demo.domain

接着,回到我们需要了解的resolveMapperLocations()方法:

  public Resource[] resolveMapperLocations() {
    ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    List resources = new ArrayList();
    //this.mapperLocations,这个就是配置mapper地址路径的属性
    if (this.mapperLocations != null) {
      for (String mapperLocation : this.mapperLocations) {
        try {
          //这就是我们要找resources,所以它是根据mapperLocation的地址找到xml文件并返回Resources对象
          Resource[] mappers = resourceResolver.getResources(mapperLocation);
          resources.addAll(Arrays.asList(mappers));
        } catch (IOException e) {
          // ignore
        }
      }
    }
    return resources.toArray(new Resource[resources.size()]);
  }

这时熟悉spring和springboot及其URL地址配置的朋友应该想到怎么配置了。mapperLocations为String数组,所以我们可以在application.properties添加多个mybatis.mapperLocations=路径/aaa.xml,mybatis.mapperLocations=路径/bbb.xml等等,或者根据在SqlSessionFactoryBean.setMapperLocations上看到的注释,我们也可以尝试这样配置:mybatis.mapperLocations=classpath*:sqlmap/*-mapper.xml

想知道如何在application.properties怎样配置mybatis的映射器xml路径的朋友在这里可以结束了,可以跳转到解决。


但是!如果我们想在深入了解spring是怎样把路径和资源加载进去及资源路径的匹配原则,那么,我们可以提出两个问题:

  1. 资源路径应该怎样填写?我们第一时间想到应类似是:目录1/子目录1/子子目录2/a.xml,但是理应有获取多个资源,并且自动检索所有下级目录文件的方法;因而我们要找到路径匹配原则。
  2. spring是如何获取到这些资源并返回Resource对象的?

下面便对这两个问题继续跟进。

2.2 Spring获取资源

我们接着看源码,现在来到PathMatchingResourcePatternResolver类的getResources方法:

@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// a class path resource (multiple resources for same name possible)
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// a class path resource pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// all class path resources with the given name
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// Generally only look for a pattern after a prefix here,
			// and on Tomcat only after the "*/" separator for its "war:" protocol.
			int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// a file pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// a single resource with the given name
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

上述代码有三个关键点:

1.根据地址前缀是否为CLASSPATH_ALL_URL_PREFIX(classparh*:)区分了获取资源方式(如,在我本测试项目中,以classpath*: 为前缀的路径地址,查找路径为:本项目所在文件夹路径\target\classes\匹配路径后缀 和 本项目所在文件夹路径\target\test-classes\匹配路径后缀);

2.根据方法getPathMatcher().isPattern(“路径地址”)来判断去除前缀(如classpath*:)后的路径地址是否为表达式模式(如/aa/*.txt,为表达式,/aa/a.txt则非表达式模式)。

3.如何查找所希望匹配的资源,可以跟踪findPathMatchingResources(“去除前缀后的路径地址”)方法。

 

下面以路径:classpath*: com/demo/aa/*.txt 简述查找资源步骤:

1.路径classpath*: com/demo/aa/*.txt 以classpath*: 开头,所以locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)返回true;

2.getPathMatcher().isPattern(“com/demo/aa/*.txt ”),判断路径地址是否为表达式模式,此处返回true;

3.进入findPathMatchingResources(“com/demo/aa/*.txt”) ,先找到当前路径中非匹配路径的最上级目录(如:/aa/**/*.txt,则找到/aa目录),然后从非匹配路径的最上级目录获取到其目录下的所有文件(如找到com/demo/aa/123.txt,com/demo/aa/abc,com/demo/aa/111.jpg),再根据getPathMatcher().match()来将找到的文件路径与com/demo/aa/*.txt比较,符合表达式则返回该资源,因而上述例子中返回com/demo/aa/123.txt


上述步骤中多次出现getPathMatcher()中的方法,查看源码发现:

        private PathMatcher pathMatcher = new AntPathMatcher();
	/**
	 * Return the PathMatcher that this resource pattern resolver uses.
	 */
	public PathMatcher getPathMatcher() {
		return this.pathMatcher;
	}

可以看到该resolver中使用的默认路径匹配器AntPathMatcher,这类就是路径表达式跟文件路径匹配的语法实现类了,即上述第3步骤时 123.txt 与 *.txt 匹配的语法。有关路径匹配器的内容交给下一节AntPathMatcher,srping默认的路径匹配器

Spring获取资源时序图:

Springboot集成mybatis,解读源码SQL 映射语句的路径配置_第2张图片


2.3 AntPathMatcher,spring默认的路径匹配器

由于AntPathMatcher实现了PathMatcher接口,我们先来看看PathMatcher:

/**
 * Strategy interface for {@code String}-based path matching.
 * 

Used by {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}, * {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping}, * and {@link org.springframework.web.servlet.mvc.WebContentInterceptor}. *

The default implementation is {@link AntPathMatcher}, supporting the * Ant-style pattern syntax. */ public interface PathMatcher { //省略其他代码 }

接口上的注释很容易理解:

这是一个基于String的策略接口。用于PathMatchingResourcePatternResolver,AbstractUrlHandlerMapping,和WebContentInterceptor。该接口的默认实现类为支持Ant-style模式的语法的AntPathMatcher类。

至此,我们可以了解到有关spring的路径匹配中,可以说都是基于Ant风格的匹配原则。有关AntPathMatcher的源码这里就不解读了,下面给出具体匹配原则和例子:

匹配原则:

 

符号 匹配原则 举例和说明
匹配一个字符 /app/p?ttern        匹配(Matches) /app/pattern 和 /app/pXttern,但是不包括/app/pttern
*

匹配0个或多个字符

/app/*.x               匹配(Matches)所有在app路径下的.x文件
** 匹配0个或多个目录

/app/**/dir/file.     匹配(Matches) /app/dir/file.jsp, /app/foo/dir/file.html,/app/foo/bar/dir/file.pdf

/**/*.jsp                匹配(Matches)任何的.jsp 文件

参考了:spring工具类AntPathMatcher

三、解决

 

 

四、小结

 

......

 

由于最近没有时间,目前先写到这里,解决方案和总结很快不上(其实解决方案应该已经一目了然了)
 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Springboot集成mybatis,解读源码SQL 映射语句的路径配置)