在Mybatis-spring中由于默认Autowired导致不能配置多个数据源的问题分析及解决

来源:互联网 发布:怎么在爱淘宝上开店 编辑:程序博客网 时间:2024/05/05 14:51

在使用Mybatis中,通常使用接口来表示一个Sql Mapper的接口以及相对应的xml实现,而在spring的配置文件中,通常会使用MapperScannerConfigurer来达到批量扫描以及简化spring bean接口配置的目的,以直接让mybatis的各个接口直接成为spring的bean组件。那么,一个通常的spring配置文件如下所示:

 <bean id="datasource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"/>

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="datasource"/>
 <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory"/>
 </bean>
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    p:dataSource-ref="datasource"/>
 <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer"
    p:basePackage="mapper"/>
上面的配置分别对应于一个基本的mybatis最需要的信息。值得需要注意的是,在配置MapperScannerConfigurer时,这里并没有指定sqlSessionFactoryName以及sqlTemplateName。在没有指定的情况下,spring就会指定默认的查找规则进行查询,如分别查找到默认的sqlSessionFactory实现和sqlSessionTemplate实现,并注入到MapperFactoryBean中。

这种配置方式,在一个数据源时,没有问题,但是在如果存在多个数据源时,上面的配置就存在问题了。在多个数据源时,如果配置不正确,或者配置的步骤不正确,将直接产生莫名奇妙的问题。而这个问题的产生,不在于开发人员,即不在于程序员本身,而在于spring,或来自于mybatis-spring,在其内部画蛇添足的注解,将导致整个多数据源配置完全不能工作。


在mybatis-spring内部,即在配置MapperScannerConfigurer时,手册告诉我们,可以不配置sqlSessionFactoryName和sqlTemplateName,因为不配置的情况下spring会自动进行注入。但是,手册没有告诉我们它是怎么来注入的,如果你仔细查看源代码,你会发现,它使用了让人悲剧的@Autowired悲剧来进行注解,那么这个注解是怎么工作的呢。以下是它的工作方式:

1.先根据beanName进行注解,那么这个beanName是指什么呢,即参数的名称。
2.如果未找到,则尝试根据类型寻找所有这个类型的bean信息。如果查找到多于1个,则会报NotUnique异常;但如果没有查找,则会根据Autowired中的required属性进行处理,如果required为false,则不进行注入,否则报异常信息。
问题就在于它的工作方式,当出现多个同类型的bean时,它并不是总是报异常。因此,当第一个步骤满足时,它总会优先拿到满足条件的bean,而并不会因为有同类型的多个bean而报异常。那么,我们来看用于实现mybaits-spring中bean的代理工厂,即MapperFactoryBean的那2个属性定义:

  @Autowired(required = false)
  public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  @Autowired(required = false)
  public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }
上面的定义在MapperFactoryBean的父类SqlSessionDaoSupport中。仔细看这两个参数名,sqlSessionFactory和sqlSessionTemplate,是不是很熟悉。对,它就是我们在applicationContext.xml中定义的bean的默认id值。那么,当出现多个数据源时,它是怎么工作的呢。我们以另一个配置为例。如下所示:

 <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer"
    p:annotationClass="com.m_ylf.study.java.mybatis.Mybatis"
    p:basePackage="mapper2"
    p:sqlSessionFactoryBeanName="sessionFactory2"
   />
根据mybatis-spring的配置建议,我们不能即配置sqlSessionFactoryBeanName和sqlSessionTemplate,只能配置其中一个即可。好吧,我们配置了sqlSessionFactoryBeanName。如果你认为,它能够像你期望地那样工作,你肯定会失望了。因为最终的结果会是:

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test.tc' doesn't exist
如果,你运行在oracle上,则是类似某用户下不存在指定的表的错误,为什么会产生这个错误。而且,这个错误发生在运行期,而不是在spring的加载过程中。你绝对想不到,因为经过Autowired的处理,MapperFactoryBean即会运行setSqlSessionFactory方法,也会运行setSqlSessionTemplate方法。而更让人郁闷的是,你设置的sqlSessionFactoryBeanName根本没有用。这来自于内部,自以为是的externalSqlSession变量。当此变量为true时,setSqlSessionFactory方法会直接返回。因为,setSqlSessionTemplate会比属性注入的applyPropertyValues更先运行,这一切是不是很让人郁闷。

那么,你会想,那么我们使用sqlSessionTemplateName这个变量吧,好吧。这个变量是正常工作的,这也来自于内部的externalSqlSession变量。你不要以为setSqlSessionFactory不会运行,而是因为这个变量让setSqlSessionFactory不能改变值而已。

那么,再对应于使用sqlSessionFactoryName所产生的问题,问题就在于在注入第二个数据源的信息时,并没有使用对应第二个数据源的信息,而是根据默认的查询策略直接找到了默认的第一个数据源信息。本来应该文号第二个数据源的sql信息,改去访问第一个数据源,肯定找不到任何信息,因为想要访问的数据表都不存在。

在这个情况中,不要认为,我们没有把默认的第一个数据源的信息命名为sqlSessionFactoryName1和sqlSessionTemplate1。在大多数的项目中,不会从一开始就知道会有多个数据源的,在默认只有一个数据源的情况下,不会有人将bean id命名为那样的奇怪名称。

最后,我们再来看下,让mybatis-spring报警告的同时配置sqlSessionFactoryName和sqlSessionTemplateName的情况。在这个情况下,mybaits-MapperScannerConfigurer会很好心的给你报一个警告,如下:

logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");然后,它不知道,这个并不是警告,而是根本spring容器就不能启动成功,即这样的配置会让整个spring容器启动失败。为什么,这要规结于内部奇怪的代码,如下所示:

//第一部分,探测到sqlSessionFactoryName
if (StringUtils.hasLength(MapperScannerConfigurer.this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(MapperScannerConfigurer.this.sqlSessionFactoryBeanName));
}

//第二部分,探测到sqlSessionTemplateName
if (StringUtils.hasLength(MapperScannerConfigurer.this.sqlSessionTemplateBeanName)) {
            }
            definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(MapperScannerConfigurer.this.sqlSessionTemplateBeanName));
            definition.getPropertyValues().add("sqlSessionFactory", null);
}问题,在于后面的definition.getPropertyValues().add("sqlSessionFactory", null);代码,这句话是说,行,我们会调用setSqlSessionFactory(null)方法。看起来没有错误,因为我们在调用了setSqlSessionTemplate之后,再调用setSqlSessionFactory(null)不会出错,因为后面的调用无效嘛。但是,问题关键在于:setSqlSessionFactory(null)会比setSqlSessionTemplate更先调用,在先调用的情况下,会产生NullPointerException异常。这个异常在整个spring的错误堆栈中找不到,你根本查不到怎么会产生这个异常,而且spring报的异常如下所示:


Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tcMapper'
defined in file xxxxxxxx: Error setting property values;
 nested exception is org.springframework.beans.PropertyBatchUpdateException;
 nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'sqlSessionFactory' threw exception;
 nested exception is java.lang.NullPointerException错误原因在于在调用setSqlSessionFactory方法时,会将sqlSessionFactory传递给sqlSessionTemplate以用于创建新的sqlSessionTemplate,而在sqlSessionTemplate的构造方法中,会调用sqlSessionFactory.getConfiguration().getDefaultExecutorType()方法,这就是NPE的来源。

综全所述,如果想要成功运行,一是修改bean命名,二是修改MapperScannerConfigurer配置中的属性一定为sqlSessionTemplateBeanName而不是sqlSessionFactoryBeanName。

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 耳机和手机不兼容怎么办 软件和手机不兼容怎么办 小米6开关键失灵怎么办 同步助手下载不了微信旧版本怎么办 闲鱼退货卖家拒收怎么办 闲鱼把联系人删了怎么办 闲鱼付款了卖家不发货怎么办 红米4c卡怎么办 如果买鞋子买到假的怎么办 猎趣永久封号钱怎么办 支付宝换绑定手机后怎么办 为什么回收站的删除键不见了怎么办 微信在异地登录怎么办 支付宝帐号被冻结怎么办 进不了路由器设置页面怎么办 支付宝支付密码忘记了怎么办 淘宝忘记登录密码了怎么办 手机换卡了微信怎么办 淘宝退款成功后收到货怎么办 没收到货退款商家不处理怎么办 没收到货申请退款卖家不处理怎么办 京东已收到货却不处理退款怎么办 货退了卖家不退款怎么办 淘宝退了货卖家不退款怎么办 手机淘宝不显示图片怎么办 京东申请退款卖家不处理怎么办 企业网银冻结了怎么办 农行k宝坏了怎么办 手机检测不到u盾怎么办 农行有k宝怎么办信用卡 k米怎么点不了怎么办 c1驾照被扣3分怎么办 淘宝账号被黑了怎么办 淘宝卖家号虚假交易违规怎么办 扣扣申诉成功后怎么办 微信二维码收款异常怎么办 国际包裹被退回去了怎么办 京东账号手机号换了怎么办 换手机号了淘宝账号怎么办 qq登录id密码忘记怎么办 iphone商店密码忘记了怎么办