Attempted fluent interface and uncommited JPA transactions (Hibernate/OC4J)

  4 posts   Feedicon  
Replies: 3 - Last Post: August 27, 2010 01:38
by: Michael Strasser
showing 1 - 4 of 4
 
Posted: August 03, 2010 04:28 by Michael Strasser

Here is an interesting one. I am beginning to modernise a Java application and have created a solution to a current requirement that entities can be persisted using database credentials other than those used to define the persistence unit's DataSource.

The ideal solution to the different credentials problem would be for the persistence provider to implement the javax.persistence.EntityManagerFactory.createEntityManager(Map) interface so that when new credentials are supplied, they are used. Apache OpenJPA does this but we are using Hibernate and it does not. So I created a Hibernate-specific solution for us that finds the org.hibernate.connection.ConnectionProvider instance and calls its configure method before calling createEntityManager().

It works well and calling the EJB that provides the business interface is like this:

@EJB
private RunningSheetManager manager;

    // In a method...
    manager.setCredentials(user, pass);
    manager.addRunningSheetEntry( /* params */ );

Then I tried creating a fluent interface to make the call something like:

@EJB
private RunningSheetManager manager;

    // In a method...
    manager.withCredentials(user, pass)
           .addRunningSheetEntry( /* params */ );

It all ran without error and Hibernate dumps all the same SQL statements to the console as before, but nothing was saved into the database! I can only conclude that the transaction was rolled back instead of being committed.

Has anybody seen this kind of behaviour before? Did I miss something in the use of fluent interfaces with EJBs? Perhaps this is a peculiarity of the app server or of Hibernate.

I ran this on OC4J 10.1.3.4 (a partial Java EE 5 implementation) and Hibernate 3.3.

Any thoughts? I have scrapped the fluent interface and restored the original one.

 
Posted: August 04, 2010 16:11 by abien
You could also inject the EntityManagerFactory and fetch it on demand. I implemented that with Glassfish v2 and TopLink. I assume it also works on GF v3 and EclipseLink.

You know that OC4J is kind of EOLed?

Keep your refactorings going!

adam
 
Posted: August 04, 2010 23:20 by Michael Strasser

Adam

Thanks for your feedback. I know OC4J is EOL but our two major customers use it. They will migrate to WebLogic Server during the next 12 months so I am working with that.

Fluent Interface

The stuff about user credentials was the background to the reason for my original post. There is more detail about that below.

I had a servlet that injects a bean, then uses it like this:

    manager.setCredentials(user, pass);
    manager.doBusinessMethod(cat, dog);

It works as expected. But my fluent-interface version of the bean failed to commit the transaction in OC4J. The calling sequence was:

    manager.withCredentials(user, pass)
           .doBusinessMethod(cat, dog);

The difference is what happens to the manager object between the calls.

  • In the fluent interface, the withCredentials method returns this and the business method is performed directly on it.
  • In the original version, the container appears to do something with the object before the business method is called.

Have you seen anything like this elsewhere?

User credentials and EntityManagerFactory

I didn’t show you the intermediate classes, but I did create one that injects EntityManagerFactory. Here it is:

@Stateless
@Local(CredentialsEntityManagerFactory.class)
public class CredentialsEntityManagerFactoryBean
      implements CredentialsEntityManagerFactory {

  @PersistenceUnit
  private EntityManagerFactory emf;

  public EntityManager getEntityManager(String username, String password) {

    if (username == null || password == null) {
      throw new IllegalStateException("CredentialsEntityManagerFactoryBean: username and password must not be null");
    }

    if (emf instanceof HibernateEntityManagerFactory) {
      // Find the connection provider, which will be an instance of
      // org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider
      // in an EJB.
      SessionFactory fact = ((HibernateEntityManagerFactory)emf).getSessionFactory();
      ConnectionProvider prov = ((SessionFactoryImplementor)fact).getConnectionProvider();
      // Username and password properties, if set, will be used by
      // a Hibernate connection provider when getting a connection from
      // the data source.
      Properties props = new Properties();
      props.setProperty(Environment.USER, username);
      props.setProperty(Environment.PASS, password);
      prov.configure(props);
    } else {
      throw new IllegalStateException("CredentialsEntityManagerFactoryBean requires Hibernate as the persistence provider");
    }

    return emf.createEntityManager();

  }

}

An example of its use is a stateless bean called RunningSheetManagerBean with:

  @EJB
  private CredentialsEntityManagerFactory cemf;

It also contains a setCredentials method and a business method that includes:

    EntityManager em = cemf.getEntityManager(username, password);
    try {
      em.persist(entry);
    } finally {
      // We must close the entity manager explicitly.
      em.close();
    }

—Michael

 
Posted: August 27, 2010 01:38 by Michael Strasser

I have finally solved my issue around unexpected results with persistence. It all has to do with threading. I tried all other kinds of ideas but this is the only one that is totally thread-safe.

In short, the only solution to what I want to do is to have a class with static, synchronized methods like this one:

public static synchronized void persist(EntityManagerFactory emf, String username, String password, Object pdo) {

    EntityManager em = getEntityManager(emf, username, password);

    try {
        em.persist(pdo);
        em.flush();
        em.refresh(pdo);
    } catch (RuntimeException ex) {
        logger.error(String.format("persist(%S) exception", username), ex);
        throw ex;
    } finally {
        try {
            em.close();
        } catch (RuntimeException ex) {
            // etc.
        }
    }
}

Where getEntityManager() is now a private, static method that calls Hibernate code to do as I described before.

I even fell into the EJB 101 trap of trying to save state to a stateless session bean! You can get away with it in simple, slow tests but as soon as you try significant load testing you get into trouble. (Hands up if you spotted that error in my previous posts!)

Adam, I used a thread-renaming interceptor in my troubleshooting and it really helped to trace where things were going wrong. Thanks!

—Michael

Replies: 3 - Last Post: August 27, 2010 01:38
by: Michael Strasser
  • Mysql
  • Glassfish
  • Jruby
  • Rails
  • Nblogo
Terms of Use; Privacy Policy;
© 2010, Oracle Corporation and/or its affiliates
(revision 20120518.3c65429)
 
 
Close
loading
Please Confirm
Close