[ https://issues.apache.org/jira/browse/OPENJPA-2051?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Heath Thomann resolved OPENJPA-2051. ------------------------------------ Resolution: Fixed Fix Version/s: 2.1.2 1.2.3 > Entities in a relationship are not properly cascaded after a > EntityManager.flush is executed. > --------------------------------------------------------------------------------------------- > > Key: OPENJPA-2051 > URL: https://issues.apache.org/jira/browse/OPENJPA-2051 > Project: OpenJPA > Issue Type: Bug > Affects Versions: 2.0.0, 2.1.1, 2.2.0 > Reporter: Heath Thomann > Assignee: Heath Thomann > Priority: Minor > Fix For: 1.2.3, 2.0.2, 2.1.2, 2.2.0 > > Attachments: CascadePersistIssue.test.patch > > > I've found a scenario where upon transaction commit, certain entities which > should be persisted via a cascade operation are not present in the database. > To properly describe this issue, let me start by first introducing a few > entities, and then list a snippet of a test which uses these entities to > recreate an issue with cascading entities. That said, take the following > three entities: > public class Vertex { > @Id > @GeneratedValue(strategy = GenerationType.AUTO) > private long oid; > @OneToMany(mappedBy = "source", cascade = CascadeType.ALL) > private List<Edge> outgoing; > @OneToMany(mappedBy = "target", cascade = CascadeType.ALL) > private List<Edge> incoming; > @ManyToOne(cascade = CascadeType.ALL) > @JoinColumn(name = "TYPE_OID") > private VertexType type; > public Edge newEdge( Vertex target ) { > Edge t = new Edge( this ); > outgoing.add( t ); > t.setTarget( target ); > return t; > } > ......... > public class VertexType { > @Id > @GeneratedValue(strategy = GenerationType.AUTO) > private long oid; > @OneToMany(mappedBy = "type", cascade = CascadeType.ALL) > List<Vertex> instances; > private String name; > ......... > public class Edge { > @Id > @GeneratedValue(strategy = GenerationType.AUTO) > private long oid; > @ManyToOne(cascade = CascadeType.ALL) > @JoinColumn(name = "SOURCE_OID") > private Vertex source; > @ManyToOne(cascade = CascadeType.ALL) > @JoinColumn(name = "TARGET_OID") > private Vertex target; > ......... > Before describing the test case, let me point out a couple important things > about these entities. First you will notice that each entity contains a > generated id. Second, notice that there are multiple relationships between > these entities. > Now let me introduce the test: > EntityManager em = emf.createEntityManager(); > EntityTransaction tx = em.getTransaction(); > tx.begin(); > em.flush(); > VertexType defaultType = new VertexType( "default" ); > VertexType specialType = new VertexType( "special" ); > em.persist(defaultType); > em.persist(specialType); > Vertex src = new Vertex( defaultType ); > Vertex target = new Vertex( specialType ); > Edge t = src.newEdge( target ); > assertNotNull( t ); > em.persist(src); > tx.commit(); > Notice that one of the first things the test does is to perform a flush. > This may seem slightly odd, however this flush is important to cause the > issue. We could execute a query or some other operation which would cause a > flush under the covers, however, calling a flush directly makes it clear that > a flush has occurred when looking at the rest of the test. > With the entities and test case in place, let me now describe the issue. > After this test case executes, there should exist in the database two Vertex, > two VertexType, and one Edge given the cascade type defined in the entities. > However, I find that one of the Vertex is missing. > In working with Rick Curtis on this issue, he found that the 'flush' at the > beginning of the test had an effect on the cascade persist at the end of the > test. That is, when 'flush' is called, this causes the '_flags' variable in > BrokerImpl to be set to flushed as follows: > _flags |= FLAG_FLUSHED; > This of course effects the return value of the method BrokerImpl.hasFlushed: > private boolean hasFlushed() { > return (_flags & FLAG_FLUSHED) != 0; > } > I will now describe how the return value of this method effects the outcome > of the test. However, in an effort of time I am going to skip over some > details which I'll leave as an exercise for the reader to figure out (e.g. > execute the test in a debugger). Basically this has an effect on > SingleFieldManager.persist when called by StateManagerImpl.cascadePersist. > That is, SingleFieldManager.persist makes a call to method 'isDetached' on > the broker here: > case JavaTypes.PC_UNTYPED: > if (!_broker.isDetachedNew() && _broker.isDetached(objval)) > return; // allow but ignore > _broker.persist(objval, true, call); > break; > Code within 'isDetached' eventually makes a call to 'hasFlushed'. Given that > 'hasFlushed' returns true, it ultimately effects the result of 'isDetached' > and thus causing the persist method in the previously posted code block to be > skipped. Again, I'm glossing over some details, but the code path described > is also affected by the fact that the ids in these entities are auto > generated. > In order to resolve this problem, we feel the best solution is to change the > '_flags' variable to indicate a flush has not occurred. To that end, we > propose adding the assignment '_flags &= ~FLAG_FLUSHED' > to this portion of code in BrokerImpl.setStateManager: > case STATUS_INIT: > _flags &= ~FLAG_FLUSHED; > _cache.add(sm); > break; > In addition, this new assignment will be gated by a compatibility property. > I've included a test patch, named 'CascadePersistIssue.test.patch' which > replicates the issue. -- This message is automatically generated by JIRA. If you think it was sent incorrectly, please contact your JIRA administrators: https://issues.apache.org/jira/secure/ContactAdministrators!default.jspa For more information on JIRA, see: http://www.atlassian.com/software/jira