objectdb-2.6.9_06
NetBeans8.1+Glassfish4.1.1
Mac OS X
Possibly related: How to Use a SF with extended Persistence Context?
[EDIT: 2006-09-29 New improved version of test mini web app with test mode switches at: https://www.webel.com.au/downloads/objectdb/GreensoftObjectdbTestWebMini3683.2016-09-29-B.tgz
To reproduce the problem described below, simply download the test project, open in NetBeans8.1 and then:
Set DO_FORCE_COMMIT_AFTER_BUILD = false in com.greensoft.objectdb.test.mini.ejb.ExtProjectBuilder !
Then Clean and Build (objectdb-2.6.9_06.jar is already included and referenced under ./lib), and Run (will deploy to Glassfish4.1.1 bundled with NetBeans8.1). See also explanation in 2nd post item]
UML diagram for test case: https://www.webel.com.au/downloads/objectdb/Issue3683_02.png
See image on https://www.webel.com.au/downloads/objectdb/Issue3683_02.png
(The classes all implement Serializable where necessary, via a common inherited logging service class All_, not shown.)
The following strategy used to work for objectdb-2.4.0_05, but fails for objectdb-2.6.9_01.
In the test case, only a single Project entity is used, and 2 project objects are (supposed to be) built using a "multi-shot" @Stateful ExtProjectBuilder that can be reset() and has an entity manager with an extended persistence context.
The basic idea (which used to work fine in practice) is that the @Startup @Singleton ExtConfigBean has a a @PostConstruct method that triggers the building of 2 separate - usually large and complex - Projects, via the @Stateful ExtProjectBuilder, which using a serious of cascading lazy builder methods (with complex entity building and wiring), which in turn delegate to a @Stateless ExtRequestBean, which offers lower level persistence and simpler entity wiring methods:
As simplified here to demonstrate the problem:
@Singleton @Startup public class ExtConfigBean extends All_ { @EJB private ExtProjectBuilder builder; @PostConstruct public void createData() { String $i = "ConfigBean:@PostConstruct:createData"; ... log_info($i,"create project 1 { ..."); builder.buildProject("project1"); log_info($i,"created project 1 }"); log_info($i,"create project 2 { ..."); builder.buildProject("project2"); // This is where the error is thrown log_info($i,"created project 2 }"); }
The error is thrown on the attempt to use the buildProject(String name) method a second time in a row (in recent versions of ObjectDB only):
Info: ExtConfigBean []: ConfigBean:@PostConstruct:createData: create project 1 { ... Info: ExtProjectBuilder []: buildProject: BEFORE: Extended entity manager exists Info: ExtProjectBuilder []: buildProject: em.transaction.isActive(true) Info: ExtRequestBean []: Created: com.greensoft.objectdb.test.mini.entity.Project [null]([project]) Info: ExtRequestBean []: Persisted: com.greensoft.objectdb.test.mini.entity.Project [1]([project]) Info: ExtProjectBuilder []: buildProject: AFTER: Extended entity manager exists Info: ExtConfigBean []: ConfigBean:@PostConstruct:createData: created project 1 } Info: ExtConfigBean []: ConfigBean:@PostConstruct:createData: create project 2 { ... Warning: A system exception occurred during an invocation on EJB ExtProjectBuilder, method: public com.greensoft.objectdb.test.mini.entity.Project com.greensoft.objectdb.test.mini.ejb.ExtProjectBuilder.buildProject(java.lang.String) Warning: javax.ejb.EJBException: com.objectdb.o._PersistenceException: Attempt to begin a new transaction when a transaction is active at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:2004) at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:210) at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88) at com.sun.proxy.$Proxy196.buildProject(Unknown Source) at com.greensoft.objectdb.test.mini.ejb.__EJB31_Generated__ExtProjectBuilder__Intf____Bean__.buildProject(Unknown Source) at com.greensoft.objectdb.test.mini.ejb.ExtConfigBean.createData(ExtConfigBean.java:51)
The @Stateless builder with the extended persistence context entity manager is:
@Stateful public class ExtProjectBuilder extends All_ { @PersistenceContext(type = PersistenceContextType.EXTENDED)//! protected EntityManager em; /* public ExtProjectBuilder() { }*/ @EJB public //! so not reset() and so other builders can access ExtRequestBean request; /** * Use for multi-shot builder to clear lazy builder method triggers. */ private void reset() { project = null; } public Project buildProject(String projectName) { String $i = "buildProject"; if (em == null) { log_warn($i, "BEFORE: Extended entity manager not created yet"); } else { log_info($i, "BEFORE: Extended entity manager exists"); if (em.getTransaction() == null) { log_info($i, "em.transaction == null"); } else { EntityTransaction t = em.getTransaction(); log_info($i, "em.transaction.isActive", t.isActive()); } } reset(); project().setName(projectName); if (em == null) { log_warn($i,"AFTER: Extended entity manager NOT created yet"); } else { log_info($i,"AFTER: Extended entity manager exists"); } return project(); } private Project project; public Project project() { if (project == null) { // Following should associate the transaction bound to the extended persistence context here // with the transactional persistence context of the delegated EJB 'request'. project = request.createProject(null, "[project]"); // Would normally do other builder stuff here and wiring // of entities using cascading lazy creation methods. } return project; }
Ignoring the logging diagnostics, all that matters here is that:
public Project buildProject(String projectName) {
Calls:
project().setName(projectName);
Which lazily triggers creation of a project (all complex sub-elements and wiring removed here) via:
project = request.createProject(null, "[project]");
Where in the simpler @Stateless delegate:
public Project createProject( Element owner, String name) { try { Project p = new Project(owner, name); persistElement(p); updateOwner(owner, p); return p; } catch (Exception ex) { throw new EJBException(ex.getMessage()); } }
protected void persistElement(Element e) { log_created(e); persist(e); log_persisted(e); } protected void persist(Object o) { try { em.persist(o); if (Global.USE_OBJECTDB) { em.flush(); //ObjectDB ensure id exists. } } catch (Exception ex) { log_error(ex); throw new EJBException(ex.getMessage()); } }
The transaction associated with the extended persistence context entity manager of the @Stateful is supposed to become associated with the entity manager of the delegate @Stateless (and does seem to on the first shot through). However, on attempting to use buildProject(name) a 2nd time, I get that error:
Caused by: com.objectdb.o.UserException: Attempt to begin a new transaction when a transaction is active
Again, this used not to happen with much older versions of ObjectDB (however, for other reasons they don't run with this test project).