Mybatis系列之全局扫描ant通配符格式的包名

举报
yd_273762914 发表于 2020/12/02 22:25:43 2020/12/02
【摘要】 业务场景 业务场景:首先项目进行分布式拆分之后,按照模块再分为为api层和service层,web层。 其中订单业务的实体类放在com.muses.taoshop.item.entity,而用户相关的实体类放在com.muses.taoshop.user.entity。所以就这样,通过通配符方式去setTypeAliasesPackage ,com.muses.tao...

业务场景

业务场景:首先项目进行分布式拆分之后,按照模块再分为为api层和service层,web层。
其中订单业务的实体类放在com.muses.taoshop.item.entity,而用户相关的实体类放在com.muses.taoshop.user.entity。所以就这样,通过通配符方式去setTypeAliasesPackage ,com.muses.taoshop.*.entity

Ant通配符的3中风格:
(1) ?:匹配文件名中的一个字符 eg: com/test/entity? 匹配 com/test/entityaa
(2) * : 匹配文件名中的任意字符 eg: com/*/entity 匹配 com/test/entity
(3) ** : 匹配文件名中的多重路径 eg: com/**/entity 匹配 com/test/test1/entity

mybatis配置类写在common工程,数据库操作有些是可以共用的,不需要每个web工程都进行重复配置。
所以写了个Mybatis配置类:

package com.muses.taoshop.common.core.database.config;
public class BaseConfig { /** * 设置主数据源名称 */ public static final String DATA_SOURCE_NAME = "shop"; /** * 加载配置文件信息 */ public static final String DATA_SOURCE_PROPERTIES = "spring.datasource.shop"; /** * repository 所在包 */ public static final String REPOSITORY_PACKAGES = "com.muses.taoshop.**.repository"; /** * mapper 所在包 */ public static final String MAPPER_PACKAGES = "com.muses.taoshop.**.mapper"; /** * 实体类 所在包 */ public static final String ENTITY_PACKAGES = "com.muses.taoshop.*.entity";
...

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

贴一下配置类代码,主要关注: factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);之前的写法是这样的。ENTITY_PACKAGES是个常量:public static final String ENTITY_PACKAGES = "com.muses.taoshop.*.entity";,ps:注意了,这里用了通配符

package com.muses.taoshop.common.core.database.config;
//省略代码
@MapperScan( basePackages = MAPPER_PACKAGES, annotationClass = MybatisRepository.class, sqlSessionFactoryRef = SQL_SESSION_FACTORY
)
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
	//省略其它代码,主要看sqlSessionFactory配置 @Primary @Bean(name = SQL_SESSION_FACTORY) public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME)DataSource dataSource)throws Exception{ //SpringBoot默认使用DefaultVFS进行扫描,但是没有扫描到jar里的实体类 VFS.addImplClass(SpringBootVFS.class); SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try{ factoryBean.setMapperLocations(resolver.getResources("classpath*:/mybatis/*Mapper.xml")); //String typeAliasesPackage = packageScanner.getTypeAliasesPackages(); //设置一下TypeAliasesPackage factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES); SqlSessionFactory sqlSessionFactory = factoryBean.getObject(); return sqlSessionFactory; }catch (Exception e){ e.printStackTrace(); throw new RuntimeException(); } }

...

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

ps:原先做法:在sqlSessionFactory方法里进行TypeAliasesPackage设置,(让Mybatis能够扫描到实体类,在xml文件里就不需要写全实体类的全包名了。)factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);

但是运行之后都会报错,提示实体类不能扫描到,因为我的service工程里都是这样写的,ResultType进行用别名ItemCategory,例子:

 <!-- 获取所有的商品品类信息--> <select id="listCategory" resultType="ItemCategory"> SELECT <include refid="BaseColumnList" /> FROM item_category t </select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

源码简单分析

针对上面的业务场景,首先的分析一下,我们知道Mybatis的执行都会通过SQLSessionFactory去调用,调用前都是先用SqlSessionFactoryBean的setTypeAliasesPackage可以看一下SqlSessionFactoryBean的源码:

/** * Packages to search for type aliases. * * @since 1.0.1 * * @param typeAliasesPackage package to scan for domain objects * */
  public void setTypeAliasesPackage(String typeAliasesPackage) { this

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

我们看一下SqlSessionFactoryBean的初步Build方法:

/** * Build a {@code SqlSessionFactory} instance. * * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a * {@code SqlSessionFactory} instance based on an Reader. * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file). * * @return SqlSessionFactory * @throws IOException if loading the config file failed */
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration;
//创建一个XMLConfigBuilder读取配置文件的一些信息 XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties);//添加Properties属性 } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); }
/*
重点看这里,其它源码先不看,这里获取到typeAliasesPackage字符串之后,调用tokenizeToStringArray进行字符串分隔返回一个数组,`String CONFIG_LOCATION_DELIMITERS = ",; \t\n";`
*/ if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) {//遍历,注册到configuration对象上 configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } ... //省略其它代码
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

然后可以看到注册所有别名的方法 ,registerAliases是怎么做的?

configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);

  
 
  • 1
  • 2

要扫描注册所有的别名之前先要扫描包下面的所有类

public void registerAliases(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil(); resolverUtil.find(new IsA(superType), packageName); //通过ResolverUtil获取到的所有类都赋值给一个集合 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); /*遍历集合,然后一个个注册*/ Iterator var5 = typeSet.iterator(); while(var5.hasNext()) { Class<?> type = (Class)var5.next(); if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { this.registerAlias(type); } } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

ResolverUtil是怎么通过packageName去查找的呢,可以再继续跟一下

 public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
 	//获取包路径 String path = this.getPackagePath(packageName); try { //VFS类用单例模式实现,先获取集合 List<String> children = VFS.getInstance().list(path); Iterator var5 = children.iterator(); //遍历,.class文件的选出来 while(var5.hasNext()) { String child = (String)var5.next(); if (child.endsWith(".class")) { this.addIfMatching(test, child); } } } catch (IOException var7) { log.error("Could not read package: " + packageName, var7); } return this; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

获取packPath只是获取一下相对路径

protected String getPackagePath(String packageName) { return packageName == null ? null : packageName.replace('.', '/'); }

  
 
  • 1
  • 2
  • 3

校验方法addIfMatching:

 protected void addIfMatching(ResolverUtil.Test test, String fqn) { try { String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.'); ClassLoader loader = this.getClassLoader();//类加载器 if (log.isDebugEnabled()) { log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); } Class<?> type = loader.loadClass(externalName);//通过类加载器加载类 if (test.matches(type)) {//校验是否符合 this.matches.add(type); } } catch (Throwable var6) { log.warn("Could not examine class '" + fqn + "' due to a " + var6.getClass().getName() + " with message: " + var6.getMessage()); } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

VFS类具体是怎么setInstance的?继续跟:

//这里的关键点就是getResources,Thread.currentThread().getContextClassLoader().getResources(),其实总结一下Mybatis的类扫描还是要依赖与jdk提供的类加载器
 protected static List<URL> getResources(String path) throws IOException {
 //获取到资源路径以列表形式放在集合里 return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path)); } ... public List<String> list(String path) throws IOException { List<String> names = new ArrayList(); Iterator var3 = getResources(path).iterator();
	//遍历封装成列表 while(var3.hasNext()) { URL url = (URL)var3.next(); names.addAll(this.list(url, path)); } return names; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

ok,本博客只是稍微跟一下源码,并没有对Mybatis的源码进行比较系统更高层次的分析。
跟了一下源码知道,稍微总结一下Mybatis对别名的注册是先将从sqlSessionFactoryBean类set的别名报名进行tokenizeToStringArray拆分成数组,然后将包名数组丢给ResolverUtil类和VFS等类进行一系列类加载遍历,之后将 resolverUtil.getClasses()获取的类都赋值给Set<Class<? extends Class<?>>> typeSet 一个集合。其中也是依赖与类加载器。

支持Ant通配符方式setTypeAliasesPackage解决方案

从这个源码比较简单的分析过程,我们并没有找到支持所谓通配符的方法,通过类加载的话也是要传个相对路径去遍历,不过我上面描述的业务场景是要兼容通配符的情况的,一般不会去改包名,假如这个项目有一定规模的话。

下面给出我的解决方案:

package com.muses.taoshop.common.core.database.annotation;

import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ClassUtils;

import static com.muses.taoshop.common.core.database.config.BaseConfig.ENTITY_PACKAGES;

public class AnnotationConstants { public static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; public final static String PACKAGE_PATTERN = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(ENTITY_PACKAGES) + DEFAULT_RESOURCE_PATTERN;

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

写一个扫描类,代码参考Hibernate的AnnotationSessionFactoryBean源码

package com.muses.taoshop.common.core.database.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;
import java.util.*;

import org.springframework.core.io.Resource;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.util.StringUtils;

import static com.muses.taoshop.common.core.database.annotation.AnnotationConstants.PACKAGE_PATTERN;

/**
 * <pre>
 *  TypeAlicsesPackage的扫描类
 * </pre>
 *
 * @author nicky
 * @version 1.00.00
 * <pre>
 * 修改记录
 * 修改后版本: 修改人:  修改日期: 2018.12.01 18:23 修改内容:
 * </pre>
 */
@Component
public class TypeAliasesPackageScanner { protected final static Logger LOGGER = LoggerFactory.getLogger(TypeAliasesPackageScanner.class); private static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); /* private TypeFilter[] entityTypeFilters = new TypeFilter[]{new AnnotationTypeFilter(Entity.class, false), new AnnotationTypeFilter(Embeddable.class, false), new AnnotationTypeFilter(MappedSuperclass.class, false), new AnnotationTypeFilter(org.hibernate.annotations.Entity.class, false)};*/ public static String getTypeAliasesPackages() { Set<String> packageNames = new TreeSet<String>(); //TreeSet packageNames = new TreeSet(); String typeAliasesPackage =""; try { //加载所有的资源 Resource[] resources = resourcePatternResolver.getResources(PACKAGE_PATTERN); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver); //遍历资源 for (Resource resource : resources) { if (resource.isReadable()) { MetadataReader reader = readerFactory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); //eg:com.muses.taoshop.item.entity.ItemBrand LOGGER.info("className : {} "+className); try{ //eg:com.muses.taoshop.item.entity LOGGER.info("packageName : {} "+Class.forName(className).getPackage().getName()); packageNames.add(Class.forName(className).getPackage().getName()); }catch (ClassNotFoundException e){ LOGGER.error("classNotFoundException : {} "+e); } } } } catch (IOException e) { LOGGER.error("ioException =>: {} " + e); } //集合不为空的情况,拼装一下数据 if (!CollectionUtils.isEmpty(packageNames)) { typeAliasesPackage = StringUtils.join(packageNames.toArray() , ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); }else{ LOGGER.info("set empty,size:{} "+packageNames.size()); } return typeAliasesPackage; } }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

然后刚才的Mybatis配置类改一下,主要改 String typeAliasesPackage = packageScanner.getTypeAliasesPackages();通过扫描类去获取一下,重点代码在TypeAliasesPackageScanner 扫描类

package com.muses.taoshop.common.core.database.config;
//省略代码
@MapperScan( basePackages = MAPPER_PACKAGES, annotationClass = MybatisRepository.class, sqlSessionFactoryRef = SQL_SESSION_FACTORY
)
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
	//省略其它代码,主要看sqlSessionFactory配置 @Primary @Bean(name = SQL_SESSION_FACTORY) public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME)DataSource dataSource)throws Exception{ //SpringBoot默认使用DefaultVFS进行扫描,但是没有扫描到jar里的实体类 VFS.addImplClass(SpringBootVFS.class); SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try{ factoryBean.setMapperLocations(resolver.getResources("classpath*:/mybatis/*Mapper.xml")); String typeAliasesPackage = packageScanner.getTypeAliasesPackages(); //设置一下TypeAliasesPackage factoryBean.setTypeAliasesPackage(typeAliasesPackage); SqlSessionFactory sqlSessionFactory = factoryBean.getObject(); return sqlSessionFactory; }catch (Exception e){ e.printStackTrace(); throw new RuntimeException(); } }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

文章来源: smilenicky.blog.csdn.net,作者:smileNicky,版权归原作者所有,如需转载,请联系作者。

原文链接:smilenicky.blog.csdn.net/article/details/84723292

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。