问题背景:

项目使用spring,springmvc框架,后边需操作关系数据库,选择了mybatis + durid,集成mybatis后,项目一直启动失败。错误的原因是dataSource初始化的时候读取不到配置文件中的值,初始化失败。

日志如下:

ERROR - aba.druid.pool.DruidDataSource - {dataSource-1} init error
java.sql.SQLException: unkow jdbc driver : ${jdbc.url}
at com.alibaba.druid.util.JdbcUtils.getDriverClassName(JdbcUtils.java:436)
at com.alibaba.druid.pool.DruidDataSource.init(DruidDataSource.java:643)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)

问题原因:

初步查看日志原因还是比较明确的,数据源初始化的时候,配置文件还没有读取完毕。不过问题来了spring管理的其他的bean初始化的时候也用到了配置文件中的值,然而启动的时候都没用问题,为什么到这会出现这个问题呢?

继续查看spring配置:

    <!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="xxxxxxxxx" />
<property name="typeAliasesSuperType" value="xxxxxxx" />
<property name="mapperLocations" value="xxxxxxxxxxx" />
<property name="configLocation" value="xxxxxxxxxxxxx" />
</bean> <!-- 扫描basePackage下的接口 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="xxxxxx" />
</bean>

发现是mybatis的 MapperScannerConfigurer 引用了dataSource,查看MapperScannerConfigurer 的源码,发现 MapperScannerConfigurer 自己实现了spring的BeanDefinitionRegistryPostProcessor,此时猜想是不是因为它自定义了bean初始化的借口,然后在初始化的时候它游离在spring的初始化进程之外,导致在它初始化的时候PropertyPlaceholderConfigurer还没有初始化完毕,因此读取不到配置文件中得值。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
......................
}

继续查看 MapperScannerConfigurer的代码,发现了有意思东西:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
........................ private boolean processPropertyPlaceHolders;
........................ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
} ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
} /*
* BeanDefinitionRegistries are called early in application startup, before
* BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
* loaded and any property substitution of this class' properties will fail. To avoid this, find
* any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)
.getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform
// property placeholder substitution. Instead, create a BeanFactory that just
// contains this mapper scanner and post process the factory.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
} PropertyValues values = mapperScannerBean.getPropertyValues(); this.basePackage = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
}
}
................................. }

看到注释证实了上面的猜测,确实是因为 MapperScannerConfigurer 初始化过早的原因:

在BeanFactoryPostProcessor之前,BeanDefinitionRegistries在应用程序启动的早期被调用。 这意味着PropertyResourceConfigurers将不会被加载,并且这个类的属性的任何属性替换都将失败。 为了避免这种情况发现上下文中定义的任何PropertyResourceConfigurers,并在这个类的beanz定义上运行它们。 然后更新这些值。

解决方法:

查看源码的时候发现这三个属性比较有意思,似乎是与问题相关的:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
......................
private SqlSessionFactory sqlSessionFactory;
....................... private String sqlSessionFactoryBeanName;
......................
private boolean processPropertyPlaceHolders;
....................

@Deprecated
      public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
          this.sqlSessionFactory = sqlSessionFactory;
      }

    ....................
}

发现 MapperScannerConfigurer 注入 sqlSessionFactory 的时候有两种选择一种是在spring配置的时候直接注入sqlSessionFactory对象,另一种是sqlSessionFactoryBeanName通过名字来注入bean。
这两种的区别在于直接注入对象要求在初始化的时候对象必须已经初始化完毕,而通过名字注入则在初始化的时候并不要求对象初始化完毕。并且通过对象方式的注入已经被标记为过时了。

到这里发现似乎在配置 MapperScannerConfigurer 的时候,加上 processPropertyPlaceHolders 属性似乎就能解决问题。当时心里很高兴,立即配置后上试了下,结果很悲剧,问题依然存在。

当时心里挺奇怪的,然后就继续看代码,心里有了一个猜测是不是因为我们 SqlSessionFactory bean的名字也是 sqlSessionFactory 跟MapperScanner中属性的名字一致,导致初始化的时候依然注入了对象。

抱着试试看的心态,修改sqlSessionFactory 为mySqlSessionFactory 结果问题竟然解决了。

总结:

1.配置MapperScannerConfigurer的时候, 使用sqlSessionFactoryBeanName方式注入SqlSessionFactory

2.配置SqlSessionFactory  的时候,beanId一定不能为sqlSessionFactory ,(这里是因为项目配置原因,default-autowire="byName" ):

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName">

spring 集成 mybatis 后数据源初始化失败问题分析的更多相关文章

  1. spring集成mybatis后,打印SQL语句

    网上说mybatis的早前版本配置打印sql还比较简单,在3.0.6之后配置方式修改了. 现在的spring-mybatis.xml配置如下: <bean id="sqlSession ...

  2. Spring集成MyBatis的使用-使用Mapper映射器

    Spring集成MyBatis使用 前面复习MyBatis时,发现在测试时,需要手动创建sqlSessionFactory,Spring将帮忙自动创建sqlSessionFactory,并且将自动扫描 ...

  3. spring集成mybatis配置多个数据源,通过aop自动切换

    spring集成mybatis,配置多个数据源并自动切换. spring-mybatis.xml如下: <?xml version="1.0" encoding=" ...

  4. SSM框架开发web项目系列(五) Spring集成MyBatis

    前言 在前面的MyBatis部分内容中,我们已经可以独立的基于MyBatis构建一个数据库访问层应用,但是在实际的项目开发中,我们的程序不会这么简单,层次也更加复杂,除了这里说到的持久层,还有业务逻辑 ...

  5. Spring集成Mybatis,spring4.x整合Mybatis3.x

    Spring集成Mybatis,spring4.x整合Mybatis3.x ============================== 蕃薯耀 2018年3月14日 http://www.cnblo ...

  6. MyBatis从入门到精通(第9章):Spring集成MyBatis(下)

    MyBatis从入门到精通(第9章):Spring集成MyBatis(下) springmvc执行流程原理 mybatis-spring  可以帮助我们将MyBatis代码无缝整合到Spring中.使 ...

  7. Spring集成MyBatis框架

    Java在写数据库查询时,我接触过四种方式: 1.纯Java代码,引用对应的数据库驱动包,自己写连接与释放逻辑(可以用连接池) 这种模式实际上性能是非常不错的,但是使用起来并不是非常方便:一是要手工为 ...

  8. Spring集成MyBatis的使用-使用SqlSessionTemplate

    Spring集成MyBatis的使用 Spring集成MyBatis,早期是使用SqlSessionTemplate,当时并没有用Mapper映射器,既然是早期,当然跟使用Mapper映射器是存在一些 ...

  9. MyBatis从入门到精通(第9章):Spring集成MyBatis(上)

    MyBatis从入门到精通(第9章):Spring集成MyBatis(上) Spring是一个为了解决企业级Web应用开发过程中面临的复杂性,而被创建的一个非常流行的轻量级框架. mybatis-sp ...

随机推荐

  1. C# 打印webBrowser打开的页面

    this.webBrowser.Navigate(webBrowserUrl, tagerFrameName, postBuffer, heads); this.webBrowser.Document ...

  2. 【Visual Studio】Visual Studio 2015快捷键设置问题 alt+ F8 (转)

    具体修改方法如下: 工具-选项-环境-键盘-应用以下其他键盘映射方案,选择visual C++6, 然后编代码试试,嘿,我的alt+F8回来了(否则是ctrl + k, ctrl + f); 原文转自 ...

  3. 33深入理解C指针之---通过字符串传递数据

    一.传递字符串:在函数的参数列表中,将参数声明为char指针即可实现通过字符串传递参数 1.特征: 1).字符串以char指针的形式传递,一般是const指针传递: 2).使用字符数组声明字符串,调用 ...

  4. Java通过开启线程池实现多线程

    计算1..100  和1...200 的和,使用线程池开启两个线程 调用Executors类的newFixedThreadPool方法参数是线程池容纳的线程数量 这里是2 返回的对象是 Executo ...

  5. js-点击按钮页面滚动到顶部,底部,指定位置

    之所以笔记一下这个,因为我在项目中经常用到. $('.scroll_top').click(function(){$('html,body').animate({scrollTop: '0px'}, ...

  6. python 之 线程池实现并发

    使用线程池实现高IO并发 模块:ThreadPoolExecutor, as_completed 测试代码如下: #!/opt/python3/bin/python3 from concurrent. ...

  7. 2019南昌邀请赛 C. Angry FFF Party 大数矩阵快速幂+分类讨论

    题目链接 https://nanti.jisuanke.com/t/38222 题意: 定义函数: $$F(n)=\left\{\begin{aligned}1, \quad n=1,2 \\F(n- ...

  8. ASP.NET MVC Identity 使用自己的SQL Server数据库

    之前在网上看到的一篇后来找不到了,现在自己记录一下. 1.在web.config中添加一个数据库连接. <add name="dataContext" connectionS ...

  9. (2)html 块类

    span span是内联元素,内联元素的特点:在显示时通常不会以新行开始 div div是块级元素,块级元素会换新行 class 设置 <head> <style> .citi ...

  10. 济南day4

    啥也不会,做了不对,对了没分.T1 50 + 30 + 0想了想,有思路,写了,半个小时写完,算错复杂度,复杂度最差(n*m),想成了(n+m)被卡没了50分,gg.....T2自己写了个单向并查集, ...