spring 集成 mybatis 后数据源初始化失败问题分析
问题背景:
项目使用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 后数据源初始化失败问题分析的更多相关文章
- spring集成mybatis后,打印SQL语句
网上说mybatis的早前版本配置打印sql还比较简单,在3.0.6之后配置方式修改了. 现在的spring-mybatis.xml配置如下: <bean id="sqlSession ...
- Spring集成MyBatis的使用-使用Mapper映射器
Spring集成MyBatis使用 前面复习MyBatis时,发现在测试时,需要手动创建sqlSessionFactory,Spring将帮忙自动创建sqlSessionFactory,并且将自动扫描 ...
- spring集成mybatis配置多个数据源,通过aop自动切换
spring集成mybatis,配置多个数据源并自动切换. spring-mybatis.xml如下: <?xml version="1.0" encoding=" ...
- SSM框架开发web项目系列(五) Spring集成MyBatis
前言 在前面的MyBatis部分内容中,我们已经可以独立的基于MyBatis构建一个数据库访问层应用,但是在实际的项目开发中,我们的程序不会这么简单,层次也更加复杂,除了这里说到的持久层,还有业务逻辑 ...
- Spring集成Mybatis,spring4.x整合Mybatis3.x
Spring集成Mybatis,spring4.x整合Mybatis3.x ============================== 蕃薯耀 2018年3月14日 http://www.cnblo ...
- MyBatis从入门到精通(第9章):Spring集成MyBatis(下)
MyBatis从入门到精通(第9章):Spring集成MyBatis(下) springmvc执行流程原理 mybatis-spring 可以帮助我们将MyBatis代码无缝整合到Spring中.使 ...
- Spring集成MyBatis框架
Java在写数据库查询时,我接触过四种方式: 1.纯Java代码,引用对应的数据库驱动包,自己写连接与释放逻辑(可以用连接池) 这种模式实际上性能是非常不错的,但是使用起来并不是非常方便:一是要手工为 ...
- Spring集成MyBatis的使用-使用SqlSessionTemplate
Spring集成MyBatis的使用 Spring集成MyBatis,早期是使用SqlSessionTemplate,当时并没有用Mapper映射器,既然是早期,当然跟使用Mapper映射器是存在一些 ...
- MyBatis从入门到精通(第9章):Spring集成MyBatis(上)
MyBatis从入门到精通(第9章):Spring集成MyBatis(上) Spring是一个为了解决企业级Web应用开发过程中面临的复杂性,而被创建的一个非常流行的轻量级框架. mybatis-sp ...
随机推荐
- 【POJ3498】March of the Penguins(最大流,裂点)
题意:在靠近南极的某处,一些企鹅站在许多漂浮的冰块上.由于企鹅是群居动物,所以它们想要聚集到一起,在同一个冰块上.企鹅们不想把自己的身体弄湿,所以它们在冰块之间跳跃,但是它们的跳跃距离,有一个上限. ...
- 标准C程序设计七---107
Linux应用 编程深入 语言编程 标准C程序设计七---经典C11程序设计 以下内容为阅读: <标准C程序设计>(第7版) 作者 ...
- udp 多播
先来了解下UDP UDP 是UserDatagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一 ...
- postgresql 10 ltree 使用说明
官方文档 https://www.postgresql.org/docs/10/static/ltree.html ltree是俄罗斯Teodor Sigaev和Oleg Bartunov共同开发的P ...
- WEB学习-兼容问题
css选择器 儿子选择器 (IE7开始兼容,IE6不兼容.) div>p{ color:red; } div的儿子p.和div的后代p的截然不同. 能够选择: <div> <p ...
- 牛客网 牛客练习赛7 D. 珂朵莉的无向图(多源BFS)
题目链接 Problem D 比赛的时候完全想不到 直接对给定的这些点做多源$BFS$,把给定的这些点全都压到队列里,然后一个个做. 最后统计被访问的点的个数即可. #include <bit ...
- [原创][FPGA][IP-Core]altlvds_tx & altlvds_rx
1. 概述 Alter公司的QuartusII软件提供了LVDS发送和接收的IP核供我们使用,其在本质上可以理解为并行-串行数据的转换器.其在官方文档(见附件)上也这样说过.其中的应用场景有告诉AD/ ...
- Codeforces 235 C
题目大意 给定一个模板串, 再给出\(n\)个询问, 询问每一个串的循环串总共在原串中出现了多少次. 循环串: 比如说有\(str[] = \{ABCD\}\), 则其循环串有\(\{ABCD\}, ...
- 335. Self Crossing
/* * 335. Self Crossing * 2016-7-10 by Mingyang */ // Categorize the self-crossing scenarios, there ...
- Linux 在VMware中搭建CentOS6.5虚拟机
原文:http://www.cnblogs.com/PurpleDream/p/4263465.html Linux 在VMware中搭建CentOS6.5虚拟机 前言: 本文主要是我在大家 ...