Dynamic-JPA Overview Print

Making the persistence behavior of an application that uses JPA dynamic in OSGi Environments involves a lot of application design decisions and supportive classes that has nothing to do with application business logic. Dynamic-JPA provides an infrastructure which allows developers to use JPA dynamically in OSGi Environments through very simple means (mostly it will be 3-10 lines of code).

The concept of Dynamic-JPA was developed ground-up from OSGi-based applications that proved to be most efficient in terms of using JPA dynamically in OSGi Environments. Actually, large part of the source code was extracted from these projects.

If you didn't read Dynamic-JPA project announcement, it might be useful for you to read it first since it contains some points uncovered in the overview.

Why you might prefer to work with JPA dynamically?

These are few cases where you will find that dynamic use of JPA to be very useful.

  • Updating database connection info (database url, user credentials, database parameters, etc.).
  • Changing databases, e.g., from HSQLDB to MySQL.
  • Changing JPA implementations, e.g., from Hibernate to Toplink.
  • Changing JPA implementation configurations, e.g., Log Level, Cache type, Cache size, Maximum number of connections allowed.
  • Adding new entity classes to your Persistence Unit.

It's obvious that updating most of what mentioned above is more appropriate at runtime when such an opportunity is given. For example, you have uncertainty about the persistence behavior of an application provided as a service, to verify its behavior you want to refine Log Level so all executed queries are logged, for such case it might not be wise to restart the service for this sole reason, same said for cache configurations, connection pool configurations, etc.

Difficulties of using JPA dynamically in OSGi Environments

For a developer to use JPA dynamically in OSGi Environments, he will need to work on two issues: Providing Entity Manager Factories that could be consumed dynamically and making client code (code that consumes entity managers for implementing business logic) aware of runtime changes to the Persistence Units in use.

Issues when providing Entity Manager Factories

Following are the main difficulties/issues that need to be addressed when providing Entity Manager Factories:

  • Class Loader issues: Since using JPA means that there is a high probability of having multiple versions of the same Persistence Unit (multiple versions of a Persistence Unit provided by different bundles) you will need to make sure that whenever an Entity Manager Factory is created the appropriate class loader for it is used.
  • Service Registration issues: The best way to expose a functionality dynamically in OSGi is by providing OSGi Services. To register a service you will have to write an Activator that will create Entity Manager Factories and register them as OSGi Services. This might not be a difficult task, but creating an Activator for the sole reason of registering the Entity Manager Factory and repeating the same process for each bundle that contains 'persistence.xml' file is not a development efficient approach.

Issues when using JPA dynamically on the client side

Following are the issues that you need to address when using JPA dynamically in JPA consumer code.

  • Dynamic Entity Managers: Entity manager should by updatable at runtime and aware of changes to its Persistence Unit.
  • Transaction awareness: Updates to Entity Managers shouldn't conflict with running transactions.

How Dynamic-JPA simplifies dynamic use of JPA in OSGi Environments?

On the service side (the part which provides Entity Manager Factories), Dynamic-JPA handles all tasks and leaves nothing for the developer other than providing 'persistence.xml' files in their bundles, on its part, Dynamic-JPA registers Entity Manager Factories as OSGi Services and a Dynamic Entity Manager Factory service for each Persistence Unit name which creates updatable Entity Managers that will be updated when a newer version of a Persistence Unit with the same name is installed. As for the client side, all the client code/application has to do is to consume the Dynamic Entity Manager Factory OSGi Service and create Entity Managers using it, update logic to Entity Managers are handled by Dynamic-JPA and made transparent to the client.

So all the developer has to do is providing "persistence.xml" files in bundles and consuming Entity Manager Factories as OSGi Services.

How Dynamic-JPA works?

Dynamic-JPA scans the active bundles of the OSGi Environment for ones that contain the file "META-INF/persistence.xml", for each one of matching bundle it creates an Entity Manager Factory per Persistence Unit contained in the "persistence.xml" file and registers it as an OSGi Service - Consuming these services directly is discouraged, rather it's better to consume the Dynamic Entity Manager Factory service. For each unique Persistence Unit name in the OSGi Environment, Dynamic-JPA creates and registers a Dynamic Entity Manager Factory service which creates Updatable Entity Managers.

Dynamic Entity Manager Factory

What differs the Dynamic Entity Manager Factory from plain Entity Manager Factories is that it produces updatable Entity Managers and holds weak references to them so it could update them when newer versions of the Persistence Unit are available. Dynamic Entity Manager Factory doesn't create Entity Managers directly, rather it looks for Entity Manager Factories in the OSGi Environment with the same Persistence Unit name and uses the best candidate (currently the service with highest rank) to create Entity Managers. Instead of returning the created Entity Manager, it encapsulates it in an Entity Manager Holder (Entity Manager Holder acts as an Entity Manager since it implements the EntityManager interface), saves a weak reference to the holder and returns the holder to the client.

When an Entity Manger Factory service for the same Persistence Unit name with a higher rank is registered (mostly as a result of starting a new bundle that contains a Persistence Unit with the same name), the Dynamic Entity Manager Factory uses it to produce new Entity Managers that will be passed to the created Entity Manager Holders in order to update them with newer Entity Managers for newer versions of the same Persistence Unit.

For each unique Persistence Unit name there is only one Dynamic Entity Manager Factory service, it has the Service Rank 1000 - The high rank was given to it to make sure that clients will use this service by default instead of plain Entity Manager Factories.

Updatable Entity Manager (Entity Manager Holder)

Entity Manager Holder acts as an Entity Manger since it implements the javax.persistence.EntityManager, instead of performing Entity Manager tasks itself, it holds an Entity Manager to which it delegates all method calls. Entity Manager Holder provides a public method which allows to update the encapsulated Entity Manager, Dynamic Entity Manger Factory uses this method to update the Entity Manager when a newer Entity Manager Factory for the same Persistence Unit becomes available.

Example Application

The Example Application consists of four application bundles: A bundle that contains entity classes, a bundle that provides a Persistence Unit (contained in the "META-INF/persistence.xml") which uses Toplink and H2, a bundle that provides a Persistence Unit which uses OpenJPA with HSQLDB and a client bundle that executes a test scenario in which an Entity Manager created by the Dynamic Entity Manager Factory is used to demonstrate how changes are transparent to the client application. The Example Application can be downloaded from the link below.

dynamic-jpa-example.zip

The client bundle contains two classes, the Activator which binds the Dynamic Entity Manager Factory to DynamicJpaTestScenario. DynamicJpaTestScenario installs the bundles that provide Persistence Units to show how the created Entity Manager reacts to runtime changes. Below are the code for the Activator and the DynamicJpaTestScenario.

import javax.persistence.EntityManagerFactory;
import org.dynamicjava.osgi.dynamic_jpa.DynamicJpaConstants;
import org.dynamicjava.osgi.service_binding_utils.OsgiServiceBinder;
import org.dynamicjava.osgi.service_binding_utils.ServiceFilter;
import ...;

public class Activator implements BundleActivator {
	
	//@Override
	public void start(BundleContext bundleContext) throws Exception {
		try {
			/// the OsgiServiceBinder is used to bind the Dynamic Entity
			/// Manager Factory to the DynamicJpaTestScenario using the 
			/// setter method 'setEntityManagerFactory'
			osgiServiceBinder = new OsgiServiceBinder(bundleContext);
			
			dynamicJpaTestScenario = new DynamicJpaTestScenario(bundleContext);
			osgiServiceBinder.bind(dynamicJpaTestScenario,
					"setEntityManagerFactory",
					ServiceFilter.forInterfaceAndServiceProperties(
							EntityManagerFactory.class.getName(),
							getEntityManagerFactoryServiceProperties()));
			
			dynamicJpaTestScenario.executeTestScenario();
		} catch (Exception ex) {
			System.out.println(
				"An Exception was thrown when running Dynamic JPA Test Scenario: "
				+ ex.getMessage());
			ex.printStackTrace();
		}
	}
	
	//@Override
	public void stop(BundleContext bundleContext) throws Exception {
	}
	
	
	protected Dictionary getEntityManagerFactoryServiceProperties() {
		Dictionary result = new Hashtable();
		result.put(DynamicJpaConstants.PERSISTENCE_UNIT_PROPERTY,
				"OrmTestDbPersistenceUnit");
		/// Alternative code if you don't want to have references to
		/// Dynamic-JPA from your code
		// result.put("persistenceUnit", "OrmTestDbPersistenceUnit");
		
		/// Since we are interested in the Dynamic Factory (Factories which 
		/// produce updatable Entity Managers and look for Entity Manager 
		/// Factories in the OSGi Environment) not ordinary ones for this 
		/// persistence unit we added this line of code:
		result.put(DynamicJpaConstants.IS_DYNAMIC_FACTORY_PROPERTY, "true");
		/// Alternative code if you don't want to have references to
		/// Dynamic-JPA from your code
		// result.put("isDynamicFactory", "true");
		
		return result;
	}
	
	
	private OsgiServiceBinder osgiServiceBinder;
	
	private DynamicJpaTestScenario dynamicJpaTestScenario;
	
}
import org.dynamicjava.examples.osgi.dynamicjpa.basic_example.entities.Product;
import ....;

public class DynamicJpaTestScenario {
	
	public void executeTestScenario() throws Exception {
		System.out.println("# Since we have no bundle with persistence.xml file"
			+ " installed yet, the getEntityManagerFactory() should return a null "
			+ " value, lets verify this fact:");
		if (getEntityManagerFactory() == null) {
			System.out.println("\tgetEntityManagerFactory() == null");
		} else {
			throw new RuntimeException("Dynamic-JPA Test Scenario failed"
				+ " since (getEntityManagerFactory() != null)");
		}
		
		System.out.println("\n# Now lets install the bundle which contains a"
			+ " Persistence Unit that uses Toplink with H2 database. As a result"
			+ " the getEntityManagerFactory() will not return a null value since"
			+ " an Entity Manager Factory service will be bound. You can also"
			+ " check the log file "
			+ "'./dynamic-jpa-example-application/logs/dynamic-jpa.log'");
		File toplinkPersistenceUnitBundleFile = new File(
				getPersistenceUnitBundlesDir(),
				"toplink-h2-persistence-unit-1.0.0.jar");
		Bundle toplinkPersistenceUnitBundle = getBundleContext().installBundle(
				"file:" + toplinkPersistenceUnitBundleFile.toString());
		toplinkPersistenceUnitBundle.start();
		waitForEntityManagerFactory();
		if (getEntityManagerFactory() != null) {
			System.out.println("\tgetEntityManagerFactory() != null");
		} else {
			throw new RuntimeException("Dynamic-JPA Test Scenario failed since "
				+ "(getEntityManagerFactory() == null)");
		}
		
		System.out.println("\n# Now, lets create an Entity Manager. Please note"
			+ " that the Entity Manager Factory that we use is a dynamic entity"
			+ " manager factory provided by Dynamic-JPA.");
		EntityManager entityManager =
				getEntityManagerFactory().createEntityManager();
		System.out.println("\n# We begin a transaction, add a new product"
			+ " (Product 1) to 'products' table and commit the transaction.");
		entityManager.getTransaction().begin();
		entityManager.persist(new Product(
				1, "Product 1", 10, "product 1 description"));
		entityManager.getTransaction().commit();
		System.out.println("\n# This is the contents of 'products' table in the "
			+ "H2 database:");
		printDatabaseRecords(entityManager);
		
		System.out.println("\n# We begin a new transaction, add a new product "
			+ "(Product 2) to 'products' table but we don't commit the"
			+ " transaction, instead we install the bundle that provides the"
			+ " persistence unit which uses OpenJPA with HSQLDB database.");
		entityManager.getTransaction().begin();
		entityManager.persist(new Product(
				2, "Product 2", 20, "product 2 description"));
		
		System.out.println("\n# Now lets install the bundle which contains a"
			+ " Persistence Unit that uses OpenJPA with HSQLDB database. Naturally"
			+ " our entity manager will be updated, but since we have a running"
			+ " transaction, the entity manager will be updated only when the"
			+ " transaction is finished (committed or rolled back).");
		File openJpaPersistenceUnitBundleFile = new File(
				getPersistenceUnitBundlesDir(),
				"openjpa-hsqldb-persistence-unit-1.0.0.jar");
		Bundle openJpaPersistenceUnitBundle =
			getBundleContext().installBundle(
				"file:" + openJpaPersistenceUnitBundleFile.toString());
		openJpaPersistenceUnitBundle.start();
		Thread.sleep(3000);
		
		System.out.println("\n# To verify the fact that the entity manager was not"
			+ " updated yet, lets print the products in the database. H2 contains"
			+ " two product (though Product 2 insertion is not complete yet). If"
			+ " the entity manager was updated then the database in use should be"
			+ " HSQLDB which is currently empty.");
		printDatabaseRecords(entityManager);
		
		entityManager.getTransaction().commit();
		
		System.out.println("\n# This is the contents of 'products' table of the"
			+ " entity manager after the transaction was commited. The reason that"
			+ " it's empty is because HSQLDB database is now in use:");
		printDatabaseRecords(entityManager);
		
		System.out.println("\n# Now lets add a new product (Product 3) to"
			+ " 'products' table and commit the transaction.");
		entityManager.getTransaction().begin();
		entityManager.persist(new Product(
				3, "Product 3", 30, "product 3 description"));
		entityManager.getTransaction().commit();
		System.out.println("\n# This is the contents of 'products' table in the"
			+ " HSQLDB database:");
		printDatabaseRecords(entityManager);
		
		System.out.println("\n# Lets uninstall the bundle which provides the"
			+ " persistence unit that uses OpenJPA with HSQLDB. As a result entity"
			+ " manager should be updated to use the older availablie persistence"
			+ " unit which uses Toplink with H2.");
		openJpaPersistenceUnitBundle.uninstall();
		
		System.out.println("\n# These are the contents of 'products' table of the"
			+ " entity manager. As you can see the database in use is H2.");
		printDatabaseRecords(entityManager);
		
		System.out.println("\n\n# Well, thats all. As you can see that all we had"
			+ " to do is to provide bundles that contain 'persistence.xml' file"
			+ "and Dynamic-JPA took care of class loading issues plus Entity"
			+ " Manager creation and service registration. As for the client side"
			+ " (JPA consumer code) all we had to do is to register a service"
			+ " listener for the Entity Manager Factory. You've also saw how"
			+ " Dynamic-JPA handled updates when newer bundles that contain"
			+ " 'persistence.xml' with same persistence unit name are installed,"
			+ " the update was completely transparent to us (we didn't have to"
			+ " create a new Entity Manager to use the new persistence unit),"
			+ " also Dynamic-JPA made sure that active transactions are not"
			+ " interrupted by persistence unit updates."
			+ "\n\nLeave scenario code a side, to have a Dynamic Entity Manager"
			+ "for Persistence Units in an OSGi Environment using Dynamic-JPA"
			+ " was a matter of 5-15 lines of code.");
	}
	
	
	///
	/// Other Supportive Code
	///
}
Dynamic-JPA and the Example Application are fully compatible with the three major OSGi Frameworks, i.e., Equinox, Felix and Knopflerfish and was tested in Java Environments JRE and JDK, versions 1.5 and 1.6.