Wow, today I thought I was doing something good, and merged a couple of Spring application context files into one single application context file, and got this horrible-looking error message when trying to run my suite of unit tests:
Error creating bean with name 'com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests':
Unsatisfied dependency expressed through bean property 'dataSource':
No unique bean of type [javax.sql.DataSource] is defined:
expected single matching bean but found 2:
[fooDataSource, barDataSource]; nested
This "unsatisfied dependency", "dataSource" error seemed horrible, but what really happened was that over time I had created a number of database tests, basically just validating the syntax of my JDBC SQL database calls, and when I merged my application context files into one applicationContext.xml file, I learned that Spring uses autowiring in the background of some of my database tests.
I guess autowiring can be a good thing -- I haven't used it yet -- but in this case it caused me a ton of problems. In short, that error message means that you can only have one DataSource defined in an application context file if you plan to use that context file in Srping JDBC tests. (Warning: That statement may be a little ambiguous ... but I don't have all the code with me to look up the exact class definitions at this time.)
I don't have time to get into all of this today, but the solution to my problem was:
- Keep my application context files separate, so that there is just one DataSource (BasicDataSource) defined in each context.
- Create a new "main" application context file that imports these other context files.
- Use the individual context files in my JDBC tests, but have my application use my new "main" context file.
When I was finished I had one "main" context file (mainContext.xml) that looked almost exactly like the following code:
<!-- mainContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<import resource="fooContext.xml"/>
<import resource="barContext.xml"/>
<!-- this handler depends on beans from both database context files -->
<bean id="storEventHandler" class="com.devdaily.ftpfilemover.controller.StorEventHandler">
<property name="fooDao" ref="fooDao"/>
<property name="barDao" ref="barDao"/>
</bean>
</beans>
What the heck, let's go all the way. Here is my first context file, fooContext.xml:
<!-- fooContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="ftpFileMoverDao" class="com.devdaily.ftpfilemover.dao.FTPFileMoverDao">
<property name="dataSource" ref="fooDataSource"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="fooDataSource"/>
</bean>
<bean id="fooDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="@db_driver@" />
<property name="url" value="@db_url@" />
<property name="username" value="@db_username@" />
<property name="password" value="@db_password@" />
<property name="initialSize" value="@initial_pool_size@" />
<property name="maxActive" value="@max_pool_size@" />
</bean>
</beans>
barContext.xml
And here is my second Spring application context file that references a DataSource, barContext.xml:
<!-- barContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="barDao" class="com.devdaily.ftpfilemover.dao.BarDao">
<property name="dataSource" ref="barBasicDataSource"/>
<property name="transactionTemplate" ref="barTransactionTemplate"/>
</bean>
<bean id="barTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="barTransactionManager"/>
</bean>
<bean id="barTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="barBasicDataSource"/>
</bean>
<bean id="barBasicDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="@bar_db_driver@" />
<property name="url" value="@bar_db_url@" />
<property name="username" value="@bar_db_username@" />
<property name="password" value="@bar_db_password@" />
<property name="initialSize" value="@bar_initial_pool_size@" />
<property name="maxActive" value="@bar_max_pool_size@" />
</bean>
</beans>
I don't have the rest of the source code with me at this time, but as mentioned, my JDBC SQL "unit" tests refer to either the fooContext.xml file, or the barContext.xml file, while my actual application refers to the mainContext.xml context file, which glues those others together, while also defining a bean that uses the data sources from both of the other context files.
The full gory Spring error message
If for some reason you'd like to see the full, gory error message from Spring, to make sure we're talking about the same error for instance, here it is:
Testsuite: com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests
Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 0.344 sec
------------- Standard Output ---------------
INFO | 2008-09-08 15:20:02,323 | AbstractSingleSpringContextTests.java | 187 | loadContextLocations | Loading context for locations: ffmContext.xml
INFO | 2008-09-08 15:20:02,401 | XmlBeanDefinitionReader.java | 323 | loadBeanDefinitions | Loading XML bean definitions from class path resource [ffmContext.xml]
INFO | 2008-09-08 15:20:02,495 | AbstractApplicationContext.java | 412 | prepareRefresh | Refreshing org.springframework.context.support.GenericApplicationContext@4f1d0d: display name [org.springframework.context.support.GenericApplicationContext@4f1d0d]; startup date [Mon Sep 08 15:20:02 EDT 2008]; root of context hierarchy
INFO | 2008-09-08 15:20:02,495 | AbstractApplicationContext.java | 427 | obtainFreshBeanFactory | Bean factory for application context [org.springframework.context.support.GenericApplicationContext@4f1d0d]: org.springframework.beans.factory.support.DefaultListableBeanFactory@192b996
INFO | 2008-09-08 15:20:02,526 | DefaultListableBeanFactory.java | 414 | preInstantiateSingletons | Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@192b996: defining beans [ftpEventFinder,ftpFileMoverDao,transactionTemplate,transactionManager,fooDataSource,barDao,barTransactionTemplate,barTransactionManager,barBasicDataSource,ftpFileMoverDaoTests,storEventHandler,deleteEventHandler,renameEventHandler]; root of factory hierarchy
------------- ---------------- ---------------
Testcase: testGetApplicationId took 0.312 sec
Caused an ERROR
Error creating bean with name 'com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests': Unsatisfied dependency expressed through bean property 'dataSource': : No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests': Unsatisfied dependency expressed through bean property 'dataSource': : No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1091)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:982)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:329)
at org.springframework.test.AbstractDependencyInjectionSpringContextTests.injectDependencies(AbstractDependencyInjectionSpringContextTests.java:205)
at org.springframework.test.AbstractDependencyInjectionSpringContextTests.prepareTestInstance(AbstractDependencyInjectionSpringContextTests.java:180)
at org.springframework.test.AbstractSingleSpringContextTests.setUp(AbstractSingleSpringContextTests.java:100)
at org.springframework.test.ConditionalTestCase.runBare(ConditionalTestCase.java:76)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:621)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1076)