MyBatis中一个sqlSessionFactory代表一个数据源,那么配置多个数据源只需要注入多个sqlSessionFactory即可。
首先需要说明的是,用mybatis-spring-1.1.0貌似无法配置多个数据源(这里说的不对,应该是无法在配置数据源中使用${..}占位符),这里大概折腾了我一整天的时间。后来才想到可能是版本问题,于是换了最新的1.2.2版,问题就迎刃而解了。
下面记录一下分析这个问题的过程:
首先我在Spring的配置文件中配置了org.springframework.beans.factory.config.PropertyPlaceholderConfigurer,这个Bean中的作为一个BeanFactoryPostProcessor会在载入所有BeanDefinition后运行,然后利用指定的properties文件来替换BeanDefinition中定义的${...}占位符,Spring的配置文件如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
- <bean id="pmsDatasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
- <property name="driver" value="${pms.driver}" />
- <property name="url" value="${pms.url}" />
- <property name="username" value="${pms.username}" />
- <property name="password" value="${pms.password}" />
- </bean>
- <bean id="pmsSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="pmsDatasource" />
- </bean>
- <bean id="pmsMapperConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
- <property name="basePackage" value="com.westsoft.pms.dao" />
- <property name="sqlSessionFactoryBeanName" value="pmsSqlSessionFactory"></property>
- </bean>
- <bean id="kftDatasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
- <property name="driver" value="${kft.driver}" />
- <property name="url" value="${kft.url}" />
- <property name="username" value="${kft.username}" />
- <property name="password" value="${kft.password}" />
- </bean>
- <bean id="kftSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="kftDatasource" />
- </bean>
- <bean id="kftMapperConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
- <property name="basePackage" value="com.westsoft.kft.dao" />
- <property name="sqlSessionFactoryBeanName" value="kftSqlSessionFactory"></property>
- </bean>
- <bean id="propertyPlaceholderConfigurer"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath:/META-INF/config/kft-ds.properties</value>
- <value>classpath:/META-INF/config/pms-ds.properties</value>
- </list>
- </property>
- </bean>
- </beans>
但是启动容器后,却报错:Cannot find class: ${kft.driver}
因为记得几个月前也弄过这个,但是没成功,后来我就改成不读properties文件,而是直接写在xml中了.问了度娘,上面铺天盖地的都是说不要注入MapperScannerConfigurer的sqlSessionFactory属性,而应该使用sqlSessionFactoryBeanName。可问题是我这里已经使用sqlSessionFactoryBeanName,显然不是这个问题。可是度娘上只有这么一个答案,真是略坑啊~难道没人用1.0.0的版本?好吧,那么我做第一人,若以后有人碰到诸如此类的问题,就不用像我搞这么久了~
虽然度娘上没给出解决答案,但是分析的还是有道理的:说是因为MapperScannerConfigurer初始化的时候同时也初始化了SqlSessionFactoryBean,并且由于MapperScannerConfigurer要先于PropertyPlaceholderConfigurer初始化,那么此时对于datasource中配置的占位符无法被替换,所以也就导致出现了上面的错误。
根据上面的思路,跟踪MapperScannerConfigurer的源码看看:
- public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
- processPropertyPlaceHolders();
-
- Scanner scanner = new Scanner(beanDefinitionRegistry);
- scanner.setResourceLoader(this.applicationContext);
-
- scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
发现MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这类Bean会在BeanDefinition被全部载入后,BeanPostProcessor前初始化。并且在初始化时,会执行postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)方法。
其中后面几行我们看的明白,是自动扫描Mapper的。而第一行执行了processPropertyPlaceHolders()方法,看了代码注释,这个方法就是设计出来防止MapperScannerConfigurer先于PropertyPlaceholderConfigurer初始化而无法转换${..}的。但是为什么没起作用呢?我们来看看源码:
- 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);
- }
- }
首先这个方法不管从名字或者意义上来看,我们都不难才想到他是用来得到所有定义的PropetyResourceConfigurer类。
其次,注意到上面的条件了吗?这里需要applicationContext是GenericApplicationContext才起作用,而我们这里的applicationContext的实现类是XmlWebApplicationContext,所以这一步完全不起作用(不对,应该说还起了副作用,因为上面的报错就是和这个方法有关)可以找到这个方法最终其实是交给DefaultListableBeanFactory的getBeansOfType(...)来实现的:
- public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
- throws BeansException {
-
- String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
- Map<String, T> result = new LinkedHashMap<String, T>(beanNames.length);
- for (String beanName : beanNames) {
- try {
- result.put(beanName, getBean(beanName, type));
- }
- catch (BeanCreationException ex) {
- Throwable rootCause = ex.getMostSpecificCause();
- if (rootCause instanceof BeanCurrentlyInCreationException) {
- BeanCreationException bce = (BeanCreationException) rootCause;
- if (isCurrentlyInCreation(bce.getBeanName())) {
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Ignoring match to currently created bean '" + beanName + "': " +
- ex.getMessage());
- }
- onSuppressedException(ex);
- // Ignore: indicates a circular reference when autowiring constructors.
- // We want to find matches other than the currently created bean itself.
- continue;
- }
- }
- throw ex;
- }
- }
- return result;
- }
这里先去寻找相应type的beanName,继续跟踪getBeanNameForType(...):
- public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
- if (type == null || !allowEagerInit) {
- return this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
- }
- Map<Class<?>, String[]> cache = includeNonSingletons ?
- this.nonSingletonBeanNamesByType : this.singletonBeanNamesByType;
- String[] resolvedBeanNames = cache.get(type);
- if (resolvedBeanNames != null) {
- return resolvedBeanNames;
- }
- resolvedBeanNames = this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
- cache.put(type, resolvedBeanNames);
- return resolvedBeanNames;
- }
后两个boolean参数在这里都是true,且在cache中没有该bean,继续跟踪doGetBeanNamesForType(...)
- private String[] doGetBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
- List<String> result = new ArrayList<String>();
-
- // Check all bean definitions.
- String[] beanDefinitionNames = getBeanDefinitionNames();
- for (String beanName : beanDefinitionNames) {
- // Only consider bean as eligible if the bean name
- // is not defined as alias for some other bean.
- if (!isAlias(beanName)) {
- try {
- RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
- // Only check bean definition if it is complete.
- if (!mbd.isAbstract() && (allowEagerInit ||
- ((mbd.hasBeanClass() || !mbd.isLazyInit() || this.allowEagerClassLoading)) &&
- !requiresEagerInitForType(mbd.getFactoryBeanName()))) {
- // In case of FactoryBean, match object created by FactoryBean.
- boolean isFactoryBean = isFactoryBean(beanName, mbd);
- boolean matchFound = (allowEagerInit || !isFactoryBean || containsSingleton(beanName)) &&
- (includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type);
- if (!matchFound && isFactoryBean) {
- // In case of FactoryBean, try to match FactoryBean instance itself next.
- beanName = FACTORY_BEAN_PREFIX + beanName;
- matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
- }
- if (matchFound) {
- result.add(beanName);
- }
- }
- }
- catch (CannotLoadBeanClassException ex) {
- if (allowEagerInit) {
- throw ex;
- }
- // Probably contains a placeholder: let's ignore it for type matching purposes.
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex);
- }
- onSuppressedException(ex);
- }
- catch (BeanDefinitionStoreException ex) {
- if (allowEagerInit) {
- throw ex;
- }
- // Probably contains a placeholder: let's ignore it for type matching purposes.
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex);
- }
- onSuppressedException(ex);
- }
- }
- }
-
- // Check singletons too, to catch manually registered singletons.
- String[] singletonNames = getSingletonNames();
- for (String beanName : singletonNames) {
- // Only check if manually registered.
- if (!containsBeanDefinition(beanName)) {
- // In case of FactoryBean, match object created by FactoryBean.
- if (isFactoryBean(beanName)) {
- if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) {
- result.add(beanName);
- // Match found for this bean: do not match FactoryBean itself anymore.
- continue;
- }
- // In case of FactoryBean, try to match FactoryBean itself next.
- beanName = FACTORY_BEAN_PREFIX + beanName;
- }
- // Match raw bean instance (might be raw FactoryBean).
- if (isTypeMatch(beanName, type)) {
- result.add(beanName);
- }
- }
- }
-
- return StringUtils.toStringArray(result);
- }
可以看到要得到 指定类型的类,其实最后是通过遍历所有的beanDefinition来的。其中判断type是否一致是用isTypeMatch(...)
- public boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException {
- String beanName = transformedBeanName(name);
- Class<?> typeToMatch = (targetType != null ? targetType : Object.class);
- // Check manually registered singletons.
- Object beanInstance = getSingleton(beanName, false);
- if (beanInstance != null) {
- <pre name="code" class="java"> <span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>
else {
<span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>
// Check bean class whether we're dealing with a FactoryBean.if (FactoryBean.class.isAssignableFrom(beanClass)) {if (!BeanFactoryUtils.isFactoryDereference(name)) {// If it's a FactoryBean, we want to look at what it creates, not the factory class.Class<?> type = getTypeForFactoryBean(beanName, mbd);return (type != null && typeToMatch.isAssignableFrom(type));}else {return typeToMatch.isAssignableFrom(beanClass);}}else {return !BeanFactoryUtils.isFactoryDereference(name) &&typeToMatch.isAssignableFrom(beanClass);}}}
在我模拟的时候发现在mapper类进来的时候会造成sqlSessionFactory的初始化,这是为什么呢?
由于mapper被MyBatis代理了,mapper定义的时候是用下面这样的形式:
- <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
- <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
- <property name="sqlSessionFactory" ref="sqlSessionFactory" />
- </bean>
所以当要获取userMapper的类型时,由于判断其是FactoryBean,且不是Dereference(不是以'&'开头去取FactoryBean的类型),就去执行getTypeForFactoryBean(beanName, mbd)这个方法:
- protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
- if (!mbd.isSingleton()) {
- return null;
- }
- try {
- FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
- return getTypeForFactoryBean(factoryBean);
- }
- catch (BeanCreationException ex) {
- // Can only happen when getting a FactoryBean.
- if (logger.isDebugEnabled()) {
- logger.debug("Ignoring bean creation exception on FactoryBean type check: " + ex);
- }
- onSuppressedException(ex);
- return null;
- }
- }
继续跟进doGetBean(...)方法查看:
- protected <T> T doGetBean(
- final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
- throws BeansException {
- lt;pre name="code" class="java"> <span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>
}else { //省略部分代码.....// Create bean instance.if (mbd.isSingleton()) {// 总算找到了,这里是会去创建sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {public Object getObject() throws BeansException {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}//省略部分代码.....}
<span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>
return (T) bean;}
所以关键原因是因为要得到FactoryBean的类型(其实是得到创建的Object的类型),那么先要去初始化它再通过getObjectType方法来得到其类型。于是就造成了sqlSessionFactory提前初始化了!另外,分析过程应该是倒推的,而不是正推~
而看了下mybatis-spring-1.2.2的源码,发现MapperScannerConfigurer初始化中的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));
- }
并且可以看到,它提供了更多配置项用于自动发现Mapper。
--转自