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 ...
随机推荐
- 洛谷P1469找筷子
题目描述 经过一段时间的紧张筹备,电脑小组的“RP餐厅”终于开业了,这天,经理LXC接到了一个定餐大单,可把大家乐坏了!员工们齐心协力按要求准备好了套餐正准备派送时,突然碰到一个棘手的问题,筷子!CX ...
- 应用express mockjs模拟前端json数据接口
一.首先需要在项目安装express 1.cnpm install express --save-dev 2.cnpm install mockjs --save-dev 二.在项目根目录下新建pr ...
- excel 表格lookup 的操作
这个实现语句如下,(写在2行然后向下拖动)(2007或以上版本): = IF(,ISBLANK($H2),$G2="), "", IF("), IF($H2&g ...
- vue + django 的权限控制
用vue做前端页面, Django 提供api, 写了一个后台系统,结合方式是vue打包后的dist目录直接作为Django的静态目录, 这样的好处是不用配置Nginx具体的做法不在这里写了,记一下遇 ...
- hdu 4510(模拟)
小Q系列故事——为什么时光不能倒流 Time Limit: 300/100 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)T ...
- html-屏蔽按键盘空格键是滚动条向下滚动
document.onkeydown = function(ev){ var e = ev || event; if(e.keyCode == 32){ return false; } }
- FMDB支持的事务类型
FMDB支持的事务类型 在数据库中,事务可以保证数据操作的完整性.当存在大量并发操作,容易出现死锁问题.在SQLite中,为了解决该问题,提供三种事务模式,分别为DEFFERED.IMMEDIAT ...
- sort、dirname、添加环境变量、修改主机名、别名IP、静态路由
1.split-按照指定行数或大小分割文件 -l:指定行数 -a:指定文件后缀长度 -d:使用数字作为后缀 -b:指定大小 # 以10行为单位对文件进行分割 split -l 10 /etc/init ...
- 第三章 poj 1064——关于带精度的二分法
/* 题意:给定n个实数l[i],给定一个k 问:求最大的ans,使得sum l[i]/ans i=1 to n >=k,且ans最大*/ #include <iostream> # ...
- javascript --- 临时构造器F()
这篇内容也是之前讲到过的,纯属无聊,莫要见怪.~(- ̄▽ ̄)-~(- ̄▽ ̄)-~(- ̄▽ ̄)-~(- ̄▽ ̄)-~(- ̄▽ ̄)- 正如上文所说,如果所有的prototype属性都指向了一个相同的对象,父 ...