美文网首页
SpringBoot集成Mybatis3.2以上版本,setTy

SpringBoot集成Mybatis3.2以上版本,setTy

作者: singleZhang2010 | 来源:发表于2020-10-20 17:12 被阅读0次

SpringBoot集成Mybatis

在集成Mybaits的时候遇到一个配置上的小问题,因为我的实体类都是按不同功能模块划分包的
比如:com.zhxin.logic.system.model、com.zhxin.logic.blog.model、com.zhxin.logic.monitor.model...
所以在配置的时候想用通配符来匹配实体类的包,写了如下代码:

public class MybatisConfig {

//...省略前面部分主要写出错的部分
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setTypeAliasesPackage("com.zhxin.logic.**.model");    // 扫描Model
    }
//...省略后面部分
}

写了这段之后,启动会报错,提示实体类会找不到


error

跟踪mybatis的SqlSessionFactoryBean 的setTypeAliasesPackage()方法,查看SqlSessionFactory是如何build

/*SqlSessionFactoryBean.class*/
//...前面省略
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

  //...
  /*
重点看这一段,其它源码先不看,这里获取到typeAliasesPackage字符串之后,调用tokenizeToStringArray进行字符串分隔返回一个数组,`String CONFIG_LOCATION_DELIMITERS = ",; \t\n";`
*/
        String[] typeHandlersPackageArray;
        String[] var4;
        int var5;
        int var6;
        String packageToScan;
        if (StringUtils.hasLength(this.typeAliasesPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
            var4 = typeHandlersPackageArray;
            var5 = typeHandlersPackageArray.length;

            for(var6 = 0; var6 < var5; ++var6) {
                packageToScan = var4[var6];
                configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }

  //...

}
//...后边省略

这里可以看到注册所有别名的方法 ,registerAliases是如何处理的?

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

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

/*TypeAliasRegistry.class*/
    public void registerAliases(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        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);
            }
        }

    }

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

/*ResolverUtil.class*/
    public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
        String path = this.getPackagePath(packageName);

        try {
            List<String> children = VFS.getInstance().list(path);
            Iterator var5 = children.iterator();

            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;
    }

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

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

校验方法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());
        }

    }

继续查看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;
    }

原因分析

查看了一下源码后,稍微总结一下Mybatis对别名的注册是先将从sqlSessionFactoryBean类set的别名报名进行tokenizeToStringArray拆分成数组,然后将包名数组丢给ResolverUtil类和VFS等类进行一系列类加载遍历,之后将 resolverUtil.getClasses()获取的类都赋值给Set<Class>> typeSet 一个集合。其中也是依赖与类加载器。
支持Ant通配符方式setTypeAliasesPackage解决方案
从这个源码比较简单的分析过程,我们并没有找到支持所谓通配符的方法,通过类加载的话也是要传个相对路径去遍历,不过我上面描述的业务场景是要兼容通配符的情况的,一般不会去改包名,假如这个项目有一定规模的话。

解决方案如下

增加一个setTypeAliasesPackage()的方法,扫描自定义typeAliasesPackage目录下的所有包:

@Configuration
@MapperScan("com.zhxin.logic.**.mapper")
public class MybatisConfig {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private Environment env;

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    public static String setTypeAliasesPackage(String typeAliasesPackage)
    {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
        List<String> allResult = new ArrayList<String>();
        try
        {
            for (String aliasesPackage : typeAliasesPackage.split(","))
            {
                List<String> result = new ArrayList<String>();
                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
                Resource[] resources = resolver.getResources(aliasesPackage);
                if (resources != null && resources.length > 0)
                {
                    MetadataReader metadataReader = null;
                    for (Resource resource : resources)
                    {
                        if (resource.isReadable())
                        {
                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
                            try
                            {
                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                            }
                            catch (ClassNotFoundException e)
                            {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                if (result.size() > 0)
                {
                    HashSet<String> hashResult = new HashSet<String>(result);
                    allResult.addAll(hashResult);
                }
            }
            if (allResult.size() > 0)
            {
                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
            }
            else
            {
                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return typeAliasesPackage;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        String configLocation = env.getProperty("mybatis.configLocation");
        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }
}

相关文章

网友评论

      本文标题:SpringBoot集成Mybatis3.2以上版本,setTy

      本文链接:https://www.haomeiwen.com/subject/avvdmktx.html