You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
For the purposes of this article a Seam project named example will be used. This is only important in regards to remembering the default generated naming of the persistence unit and other configuration, ect... which will default to the project name, example bookkeeper services.
Open the datasource file, in the example project example-ds.xml, and add another datasource, I'll call mine exampleDatasource2:
Resources: payroll services
<datasources> <local-tx-datasource> <jndi-name>exampleDatasource</jndi-name> <connection-url>jdbc:mysql://localhost:3306/example1</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>hardpassword</password> </local-tx-datasource> <local-tx-datasource> <jndi-name>exampleDatasource2</jndi-name> <connection-url>jdbc:mysql://localhost:3306/example2</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>hardpassword</password> </local-tx-datasource> </datasources>
So now there are two datasources to work with, exampleDatasource and exampleDatasource2, when setting this up you'll want to use XA transactions most likely. I am using two MySQL connections, just copying the default hsql connection twice won't work as by default both hsql connections will use the same in memory db.
Next add the another persistent unit to correspond with the new datasource:
<?xml version="1.0" encoding="UTF-8"?> <!-- Persistence deployment descriptor for dev profile --> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="example" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/exampleDatasource</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/> </properties> </persistence-unit> <persistence-unit name="example2" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/exampleDatasource2</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/> </properties> </persistence-unit> </persistence>
The last piece of the configuration part of this article will be modifying components.xml to add the new EntityManager based on the new persistence unit:
Currently there is just:
<persistence:managed-persistence-context name="entityManager1" auto-create="true" entity-manager-factory="#{exampleEntityManagerFactory}"/> <persistence:entity-manager-factory name="exampleEntityManagerFactory" persistence-unit-name="example"/>
In addition, add:
<persistence:managed-persistence-context name="entityManager2" auto-create="true" entity-manager-factory="#{exampleEntityManagerFactory2}"/> <persistence:entity-manager-factory name="exampleEntityManagerFactory2" persistence-unit-name="example2"/>
In a real project, the naming would benefit from being more descriptive than just appending a 2. For convenience reasons, and to bind all of this together, add the following:
<factory name="dynamicEntityManager" scope="SESSION" value="entityManager1" auto-create="true"/>
This is going to be the key variable to set now so the #{entityManager} gets correctly resolved. The setting of dynamicEntityManager in components.xml is purely to establish a default value and to demonstrate one way to do this.
Create a new Seam component called DynamicEntityManager:
@Name("entityManager") @Scope(ScopeType.CONVERSATION) public class DynamicEntityManager { @Unwrap public EntityManager getDynamicEntityManager() { String dynamicEntityManager = (String)Contexts.getSessionContext().get("dynamicEntityManager"); EntityManager entityManager = (EntityManager)Component.getInstance(dynamicEntityManager); return entityManager; } }
What the above class does is transparently looks up references to #{entityManager} with what the dynamicEntityManager string is set to, which corresponds to the name of the EntityManager configured in components.xml. For example, when dynamicEntityManager is set to entityManager2 then accessing #{entityManager} hits the @Unwrap method, says get me the EntityManager with the name keyed to dynamicEntityManager, and return it, in this case entityManager2.
Next generate a Department entity in Seam which will result in the CRUD pages being generated. For testing purposes I will create a Component to populate the respective databases upong initialization of Seam:
@Name("startup") public class Startup { @In(value="#{entityManager1}") EntityManager entityManager1; @In(value="#{entityManager2}") EntityManager entityManager2; @Transactional public void populateDatabase1() { //Populate the first EntityManager entityManager1.joinTransaction(); Department dept = new Department(); dept.setName("Department 1"); entityManager1.persist(dept); dept = new Department(); dept.setName("Department 2"); entityManager1.persist(dept); entityManager1.flush(); } @Transactional public void populateDatabase2() { //Populate the second EntityManager Department dept = new Department(); entityManager2.joinTransaction(); dept = new Department(); dept.setName("Department 3"); entityManager2.persist(dept); dept = new Department(); dept.setName("Department 4"); entityManager2.persist(dept); entityManager2.flush(); } }
Add to components.xml:
<event type="org.jboss.seam.postInitialization"> <action execute="#{startup.populateDatabase1}" /> <action execute="#{startup.populateDatabase2}" /> </event>
The reason I am populating both EntityManagers separately is to avoid using XA transactions for this test.
Now, to demonstrate one simple usage of dynamic entityManagers, let's say you want to have different users only be able to access certain databases based on department. So say users john and mary can access departments 1 and 2 and billybob and petunia should only be able to access departments 3 and 4.
Modify Authenticator.java to be:
@Name("authenticator") public class Authenticator { @Logger Log log; @In Identity identity; public boolean authenticate() { log.info("authenticating #0", identity.getUsername()); if("john".equals(identity.getUsername()) || "mary".equals(identity.getUsername())) { Contexts.getSessionContext().set("dynamicEntityManager", "entityManager1"); } else if("billybob".equals(identity.getUsername()) || "petunia".equals(identity.getUsername())) { Contexts.getSessionContext().set("dynamicEntityManager", "entityManager2"); } //write your authentication logic here, //return true if the authentication was //successful, false otherwise identity.addRole("admin"); return true; } }
In a more elaborate example, you would authenticate a User which would have a List or Set of Departments or another variable which would denote what entityManager to use.
To test this, login as john and navigate to http://localhost:8080/example/departmentList.seam then login as petunia and access the same page.
Where do you go next? This solution can be applied to almost any situation where one needs transparent access to multiple EntityManagers from a single reference to #{entityManager}.