Springboot Mybatis 打包jar扫描bean与mapper问题研究与解决

SpringBootLean 是对springboot学习与研究项目,是根据实际项目的形式对进行配置与处理,欢迎star与fork。
[oschina 地址]
http://git.oschina.net/cmlbeliever/SpringBootLearning
[github 地址]
https://github.com/cmlbeliever/SpringBootLearning

最近在项目中集成以全注解的方式Mybatis,配置了自动bean包与mapper所在包

db.mybatis.mapperLocations=classpath*:com/cml/springboot/sample/db/resource/*
db.mybatis.typeAliasesPackage=com.cml.springboot.sample.bean
db.mybatis.typeHandlerPackage=com.cml.springboot.framework.mybatis.typehandler

这些配置直接在ide上运行都是ok的,但是经过打包成jar包后,就一直报错提示找打不到对应的bean或者对应的mapper。主要错误如下:

 Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'logbean'.  Cause: java.lang.ClassNotFoundException: Cannot find class: logbean
 
***************************
APPLICATION FAILED TO START
***************************

Description:

Field logMapper in com.cml.springboot.sample.service.impl.LogServiceImpl required a bean of type 'com.cml.springboot.sample.db.LogMapper' that could not be found.


Action:

Consider defining a bean of type 'com.cml.springboot.sample.db.LogMapper' in your configuration.

针对以上问题,在新开的分支deploy_jar_bugfind上进行研究,找到了一个临时解决办法,就是配置mybat-configuration.xml将对应的bean都配置到xml上。

<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
    <typeAliases>
        <typeAlias type="com.cml.springboot.sample.bean.LogBean" alias="logbean"/>
        <typeAlias type="com.cml.springboot.sample.bean.User" alias="user"/>
    </typeAliases>
    <typeHandlers>
        <typeHandler javaType="org.joda.time.DateTime"
            handler="com.cml.springboot.framework.mybatis.typehandler.JodaDateTimeTypeHandler" />
        <typeHandler javaType="org.joda.time.LocalTime"
            handler="com.cml.springboot.framework.mybatis.typehandler.JodaLocalTimeTypeHandler" />
    </typeHandlers>
</configuration>

但是这个仅仅适合小项目并且bean少的情况,假如在打项目上,可能有几十上百的bean,都要一一配置岂不是累死人了,而且容易出bug。

于是继续研究,首先自定义DefaultSqlSessionFactoryBean取代默认的SqlSessionFactoryBean,并且在buildSqlSessionFactory()方法上对bean扫描的配置进行一步步log拆分,主要代码如下:

if (hasLength(this.typeAliasesPackage)) {
            String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeAliasPackageArray) {

                logger.debug("##################################################################################");
                List<String> children = VFS.getInstance().list(packageToScan.replace(".", "/"));
                logger.debug("findclass:" + children);
                logger.debug("##################################################################################");

                logger.debug("DefaultSqlSessionFactoryBean.buildSqlSessionFactory.registerAliases:" + packageToScan);

                ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
                resolverUtil.find(new ResolverUtil.IsA(Object.class), packageToScan);
                Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();

                logger.debug(
                        "DefaultSqlSessionFactoryBean.buildSqlSessionFactory.typeAliasesPackage.scanClass:" + typeSet);

                for (Class<?> type : typeSet) {
                    if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                        String alias = type.getSimpleName();
                        String key = alias.toLowerCase(Locale.ENGLISH);
                        logger.debug(
                                "DefaultSqlSessionFactoryBean.buildSqlSessionFactory.typeAliasesPackage.scan:" + key);

                    }
                }

                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                        typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }

发现在ide上运行的时候是可以扫描到指定的bean,但是在jar上就扫描不到了。主要问题找到了,就是对代码的扫描问题,原本想自定义configuration的,但是发现好多类都是依赖configuration这个类,于是便放弃这个想法,转而研究扫描不到bean的问题。

继续研究源码,得知bean的扫描是通过ResolverUtil这个类进行的,并且ResolverUtil扫描是通过VFS进行扫描的,主要代码:

public ResolverUtil<T> find(Test test, String packageName) {
    String path = getPackagePath(packageName);

    try {
      List<String> children = VFS.getInstance().list(path);
      for (String child : children) {
        if (child.endsWith(".class"))
          addIfMatching(test, child);
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }

    return this;
  }

结合log上的信息,工程上默认使用的是Mybatis的DefaultVFS进行扫描。查看这个类代码后也没发现什么问题,转而求救于百度与谷歌,经过多方搜索,找到了Mybatis官网issue (地址:https://github.com/mybatis/mybatis-3/issues/325)这里有对这些问题进行说明。根据issue的描述与各大神的回答,定位到了问题是DefaultVFS在获取jar上的class问题。

在mybat/spring-boot-starter工程上找到了SpringBootVFS,这个类重写了class扫描功能,通过spring进行扫描。

于是将SpringBootVFS拷贝到工程上,并且添加到VFS实现上去,代码如下:

@Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DataSource datasource, MybatisConfigurationProperties properties)
            throws Exception {

        log.info("*************************sqlSessionFactory:begin***********************" + properties);

        VFS.addImplClass(SpringBootVFS.class);

        DefaultSqlSessionFactoryBean sessionFactory = new DefaultSqlSessionFactoryBean();
        sessionFactory.setDataSource(datasource);
        sessionFactory.setTypeAliasesPackage(properties.typeAliasesPackage);
        sessionFactory.setTypeHandlersPackage(properties.typeHandlerPackage);

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources(properties.mapperLocations));

//      sessionFactory
//              .setConfigLocation(new PathMatchingResourcePatternResolver().getResource(properties.configLocation));

        SqlSessionFactory resultSessionFactory = sessionFactory.getObject();

        log.info("===typealias==>" + resultSessionFactory.getConfiguration().getTypeAliasRegistry().getTypeAliases());

        log.info("*************************sqlSessionFactory:successs:" + resultSessionFactory
                + "***********************" + properties);

        return resultSessionFactory;

    }

这样mybaits打包jar后扫描问题完美解决。


综上,解决SpringBoot Mybatis打包jar后问题处理完毕,已提交到git项目master分支和deploy_jar_bugfind
http://git.oschina.net/cmlbeliever/SpringBootLearning

解决办法:

  1. 通过mybatis-configuration.xml进行配置
  2. 通过SpringBootVFS进行自动扫描配置(推荐)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,799评论 6 342
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,498评论 0 4
  • 时刻保持自己的独立与清醒,不要总是低头跟着别人前行,当别人到站的时候你才会发现自己忽然之间就失去了方向——你与别人...
    高手如林阅读 830评论 0 3
  • 无论世界多么嫌弃你,你都不能嫌弃你自己。 写下这段话的我,此时应该是经过阵阵焦虑,艰难思索后的结果。 ...
    独声阅读 177评论 0 0