问题背景:

项目使用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. Larevel5.1 打印SQL语句

    Larevel5.1 打印SQL语句 为了方便调试,开发时需要打印sql. 方法一(全局打开): SQL打印默认是关闭的, 需要在/vendor/illuminate/database/Connect ...

  2. django中表变更后migrate无效的问题

    问题描述: 已有的model,修改之后,想重新建模,于是将migrations文件夹中除__init__.py之外其他文件都删掉,再次执行以下步骤python manage.py makemigrat ...

  3. React-Native解决ListView 在Android手机上无吸顶效果

    stickySectionHeadersEnabled={true} stickyHeaderIndices={[0]}

  4. 2017 [六省联考] T1 期末考试

    4868: [Shoi2017]期末考试 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 842  Solved: 385[Submit][Status ...

  5. Maven更新POM中的JDK版本(比如更新为JDK1.8)

    默认POM如果不指定JDK版本为1.5,而有些项目需要使用泛型这些,就必须使用1.8版本的JDK,所以需要手动修改POM. 而所涉及到的还是插件maven-compiler-plugin,官方参考:h ...

  6. Parallel Database for OLTP and OLAP

    Parallel Database for OLTP and OLAP Just asurvey article on materials on parallel database products ...

  7. DBCC

    http://www.cnblogs.com/lyhabc/archive/2013/01/19/2867174.html http://www.cnblogs.com/lyhabc/articles ...

  8. python设置utf-8为默认编码

    当使用Python编程时,编码问题一直很让人头疼,程序中经常会碰到如下错误提示: UnicodeDecodeError: 'ascii' codec can't decode byte 0x?? in ...

  9. [反汇编练习] 160个CrackMe之032

    [反汇编练习] 160个CrackMe之032. 本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注 ...

  10. python 使用cx-freeze打包程序

    python环境 3.6.5      win7   linux环境同理 先尝试了PyInstaller   ,打包时一直提示 no module named gtk   而gtk 又依赖pygobj ...