|
#1
|
|||
|
|||
|
Hi,
I have spent all day reading forums, blogs and documentation on this topic without any luck. What I want to do is: - I have a PostgreSQL database and an SQL Server database - I have two separate persistence.xml files (different filenames), and two entityManagerFactory beans defined: Code:
<context:load-time-weaver/>
<context:annotation-config/>
<tx:annotation-driven/>
<tx:jta-transaction-manager />
<bean id="webCicoEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="WebCicoPU"/>
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.PostgreSQLPlatform" p:showSql="false" />
</property>
</bean>
<bean id="centralDataEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="CentralDataPU"/>
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence-CentralDataPU.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.SQLServerPlatform" p:showSql="false" />
</property>
</bean>
Code:
@PersistenceContext(name = "WebCicoPU")
private transient EntityManager entityManager;
Code:
@PersistenceContext(name = "CentralDataPU")
private transient EntityManager entityManager;
Spring gives the following exception: Caused by: org.springframework.beans.factory.NoSuchBeanDefini tionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2 How do I tell which entity manager factory to use per DAO object? This thread did not help: http://forum.springframework.org/sho...d+single+found Thanks, Ryan |
|
#2
|
|||
|
|||
|
I've seen some threads talk about using a DefaultPersistenceUnitManager and sharing them between all LocalContainerEntityManagerFactoryBean instances. I still get the same problem where @PersistenceContext(unitName="foobar") doesn't know which instance of javax.persistence.EntityManagerFactory to inject. Is there a way for me to inject an EntityManager loaded from the appropriate factory using Spring's XML instead of the @PersistenceContext annotation?
Code:
<bean id="persistenceUnitManager" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>classpath:META-INF/persistence-CentralDataPU.xml</value>
<value>classpath:META-INF/persistence.xml</value>
</list>
</property>
</bean>
<bean id="webCicoEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="persistenceUnitManager"/>
<property name="persistenceUnitName" value="WebCicoPU"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.PostgreSQLPlatform" p:showSql="false" />
</property>
</bean>
<bean id="centralDataEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="persistenceUnitManager"/>
<property name="persistenceUnitName" value="CentralDataPU"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.SQLServerPlatform" p:showSql="false" />
</property>
</bean>
Thanks, Ryan |
|
#3
|
|||
|
|||
|
I have found a hack to make this work. I call it a hack because Spring 2.5 does not properly implement support for JPA and therefore I have to do extra work to support multiple persistence units. I've used multiple persistence units with EJB 3.0 before and it was completely effortless. It just worked. With Spring I had to spend over a day to get to this point.
Anyway, onto the solution: 1) Spring doesn't support multiple persistence unit inside the same persistence.xml, so you have to create a separate XML file for each persistence unit. 2) You need to create a separate LocalContainerEntityManagerFactoryBean for each persistence unit. Use these properties to configure them: <property name="persistenceUnitName" value="MyGreatPU"/> <property name="persistenceXmlLocation" value="classpath:META-INF/persistence-MyGreatPU.xml"/> 3) You cannot use @PersistenceUnit(unitName = "foo") or @PersistenceContext(unitName = "foo") in your DAOs because Spring doesn't know which LocalContainerEntityManagerFactoryBean to inject. It doesn't matter that you've already specified the persistence unit name in Spring's XML configuration AND in the annotation. Instead, you need to create a setter method in your DAO for an EntityManagerFactory, and in your Spring XML configure your DAO bean to have the appropriate LocalContainerEntityManagerFactoryBean injected. In this setter method I use the factory to create an EntityManager and store that in a private class level field. 4) In every method of your DAO that is an entry point from the point of view of transactions, you need to call entityManager.joinTransaction(); Below is what my applicationContext.xml looks like. I hope I was able to help other people trying to solve this problem. If anyone knows of a better way to do this then please let me know! Thanks, Ryan Code:
<context:load-time-weaver/>
<context:annotation-config/>
<tx:annotation-driven/>
<tx:jta-transaction-manager />
<bean id="webCicoEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="WebCicoPU"/>
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.PostgreSQLPlatform" p:showSql="false" />
</property>
</bean>
<bean id="centralDataEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="CentralDataPU"/>
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence-CentralDataPU.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.SQLServerPlatform" p:showSql="false" />
</property>
</bean>
<bean id="WebCicoSettingsDAO" class="com.ijws.webcico.dao.settings.WebCicoSettingsDAOImpl" scope="singleton">
<property name="entityManagerFactory" ref="webCicoEntityManagerFactory"/>
</bean>
<bean id="CentralReservationsDAO" class="com.ijws.webcico.dao.centraldata.CentralReservationsDAOImpl" scope="singleton">
<property name="entityManagerFactory" ref="centralDataEntityManagerFactory"/>
</bean>
|
|
#4
|
|||
|
|||
|
Next I moved onto my unit test environment and have more problems. My unit test environment does not use JTA or JNDI data sources. It has two datasources defined in the applicationContext.xml, and the entityManagerFactory beans point to the appropriate one. I've also created separate JpaTransactionManager's for each of the entiyManagerFactories. As you can see from my Spring XML below, the entityManagerFactories *clearly* state which data source to use:
Code:
<context:annotation-config/>
<context:property-placeholder location="META-INF/jdbc.properties"/>
<bean id="webCicoDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url.test}"/>
<property name="username" value="${jdbc.user.test}"/>
<property name="password" value="${jdbc.password.test}"/>
</bean>
<bean id="centralDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"/>
<property name="url" value="jdbc:jtds:sqlserver://vault/CMS;instance=sql2005"/>
<property name="username" value="blah"/>
<property name="password" value="blahblah"/>
</bean>
<bean id="webCicoEntityManagerFactory" class= "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="webCicoDataSource"/>
<property name="persistenceXmlLocation" value="META-INF/persistence.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.PostgreSQLPlatform" p:showSql="true"/>
</property>
</bean>
<bean id="centralDataEntityManagerFactory" class= "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="centralDataSource"/>
<property name="persistenceXmlLocation" value="META-INF/persistence-CentralDataPU.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
p:databasePlatform="oracle.toplink.essentials.platform.database.PostgreSQLPlatform" p:showSql="true"/>
</property>
</bean>
<bean id = "webCicoTransactionManager" class= "org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="webCicoEntityManagerFactory"/>
</bean>
<bean id = "centralDataTransactionManager" class= "org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="centralDataEntityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="webCicoTransactionManager"/>
1) When Spring initializes it says: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [webCicoDataSource, centralDataSource] 2) Am I supposed to write this twice, once for each transaction manager? <tx:annotation-driven transaction-manager="webCicoTransactionManager"/> I don't think so. According to this bug ticket, Spring doesn't support @Transactional with multiple transaction managers: http://jira.springframework.org/browse/SPR-3955 Any help would be appreciated |
|
#5
|
|||
|
|||
|
It looks like I'm going to have to separate the settings for each datasource/entitymanager/transaction manager into separate XML configuration files, and only load the one I need for the particular unit test.
This is bad news when it comes to higher level integration testing where a service class uses both DAOs and therefore needs both databases. Another option is to use XML to apply transactions to the classes that use @Transactional annotation. Will I have to remove the @Transactional annotation to make it work? If yes then I'll have to also use XML configuration in the non test environment too. Just a side note, I've only been using Spring for a month or so after having read a book on Spring 2.0 cover-to-cover. I want to use it, I want to like it. Every single thing I've tried to do with Spring so far has given me blue balls and black eyes. I have wasted literally weeks because of it's over complications of everything. I swear EJB 3.0 is easier. I just wish it would work in a web container. When EJB 3.1 comes out I'm going to clean up this Spring mess and migrate back. Hopefully EJB 3.1's unit testing support works the same as it does in an application server without much effort. |
|
#6
|
|||
|
|||
|
I'll describe the latest news. Earlier I mentioned that the web app running in GlassFish works when I don't use @PersitenceContext or @PersistenceUnit. Instead I use spring XML to inject the right entityManagerFactory bean. The setter method calls .createEntityManager and stores it in a class level field, then any method that uses it calls .joinTransaction().
In my unit test environment I have created two separate applicationContext.xml files. One for beans that use the first database, and one for beans that use the second database. Spring no longer complains about not knowing which DataSource instance to use because there is only one per file. My tests are kind of able to use the database but it keeps locking up somewhere on me and I have to kill the process. If I comment out the changes to my DAO that creates its own entityManager from a factory and joins the transaction, and replace it with the old @PersistenceContext then the unit tests work fine! So in my real app I can't use @PersitenceContext, but in my unit tests I must use @PersistenceContext. F*cked if you do, F*cked if you don't. It looks like I can't do any unit tests with Spring and JPA on this project. |
|
#7
|
|||
|
|||
|
What are you doing? I've not read through all your posts, but...
Revert your latest changes, this works just fine. Go and read the javadocs for PersistenceContext, you should use PersistenceContext(unitName="whatever") not PersistenceContext(name="whatever"). /Magnus |
|
#8
|
|||
|
|||
|
I must have made a typo in the forum but I'm pretty certain I have always specified unitName="whatever" when using @PersistenceContext and @PersistenceUnit. Further up in this thread I mentioned my findings when working in the application server environment:
Quote:
Quote:
Thanks, Ryan |
|
#9
|
|||
|
|||
|
Typically, you should let the container bootstrap the persistence unit and acquire the persistence unit reference from the container’s JNDI, LocalContainerEntityManagerFactoryBean products are not available to your non-spring beans.
|
|
#10
|
|||
|
|||
|
Thanks for the reply, but I just finished saying that I was using the Spring container to bootstrap the persistence unit using @PersistenceContext(unitName="blah") until I had to add a second persistence unit. Spring's implementation of @PersistenceContext does not know which entity manager to inject, which forced me to use XML to manually inject the correct factory, manually create an entity manager using that factory, and join the active transaction.
I am loading the datasource from JNDI, and it works fine in the app server. I said in my unit tests there is no JNDI so I have to define data sources in my Spring XML file. Spring complains because it doesn't know which data source to inject into the entity manager factory even after I explicitly told it which one to use! So, nothing new here yet. |
![]() |
| Thread Tools | |
| Display Modes | |
|
|