http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/lifecycle-events.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/lifecycle-events.xml b/docs/docbook/cayenne-guide/src/docbkx/lifecycle-events.xml deleted file mode 100644 index 364b5da..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/lifecycle-events.xml +++ /dev/null @@ -1,333 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" - version="5.0" xml:id="lifecycle-events"> - <title>Lifecycle Events</title> - <para>An application might be interested in getting notified when a Persistent object moves - through its lifecycle (i.e. fetched from DB, created, modified, committed). E.g. when a new - object is created, the application may want to initialize its default properties (this can't - be done in constructor, as constructor is also called when an object is fetched from DB). - Before save, the application may perform validation and/or set some properties (e.g. - "updatedTimestamp"). After save it may want to create an audit record for each saved object, - etc., etc. </para> - <para>All this can be achieved by declaring callback methods either in Persistent objects or in - non-persistent listener classes defined by the application (further simply called - "listeners"). There are eight types of lifecycle events supported by Cayenne, listed later - in this chapter. When any such event occurs (e.g. an object is committed), Cayenne would - invoke all appropriate callbacks. Persistent objects would receive their own events, while - listeners would receive events from any objects. </para> - <para>Cayenne allows to build rather powerful and complex "workflows" or "processors" tied to - objects lifecycle, especially with listeners, as they have full access to the application - evnironment outside Cayenne. This power comes from such features as filtering which entity - events are sent to a given listener and the ability to create a common operation context for - multiple callback invocations. All of these are discussed later in this chapter.</para> - <section xml:id="types-of-lifecycle-events"> - <title>Types of Lifecycle Events</title> - <para>Cayenne defines the following 8 types of lifecycle events for which callbacks can be - regsitered:<table frame="void"> - <caption>Lifecycle Event Types</caption> - <col width="16%"/> - <col width="84%"/> - <thead> - <tr> - <th>Event</th> - <th>Occurs...</th> - </tr> - </thead> - <tbody> - <tr> - <td>PostAdd</td> - <td>right after a new object is created inside - <code>ObjectContext.newObject()</code>. When this event is fired the - object is already registered with its ObjectContext and has its ObjectId - and ObjectContext properties set.</td> - </tr> - <tr> - <td>PrePersist</td> - <td>right before a new object is committed, inside - <code>ObjectContext.commitChanges()</code> and - <code>ObjectContext.commitChangesToParent()</code> (and prior to - "<code>validateForInsert()</code>").</td> - </tr> - <tr> - <td>PreUpdate</td> - <td>right before a modified object is committed, inside - <code>ObjectContext.commitChanges()</code> and - <code>ObjectContext.commitChangesToParent()</code> (and prior to - "<code>validateForUpdate()</code>").</td> - </tr> - <tr> - <td>PreRemove</td> - <td>right before an object is deleted, inside - <code>ObjectContext.deleteObjects()</code>. The event is also - generated for each object indirectly deleted as a result of CASCADE - delete rule.</td> - </tr> - <tr> - <td>PostPersist</td> - <td>right after a commit of a new object is done, inside - <code>ObjectContext.commitChanges()</code>.</td> - </tr> - <tr> - <td>PostUpdate</td> - <td>right after a commit of a modified object is done, inside - <code>ObjectContext.commitChanges()</code>.</td> - </tr> - <tr> - <td>PostRemove</td> - <td>right after a commit of a deleted object is done, inside - <code>ObjectContext.commitChanges()</code>.</td> - </tr> - <tr> - <td>PostLoad</td> - <td> - <itemizedlist> - <listitem> - <para>After an object is fetched inside - <code>ObjectContext.performQuery()</code>.</para> - </listitem> - <listitem> - <para>After an object is reverted inside - <code>ObjectContext.rollbackChanges()</code>.</para> - </listitem> - <listitem> - <para>Anytime a faulted object is resolved (i.e. if a - relationship is fetched).</para> - </listitem> - </itemizedlist> - </td> - </tr> - </tbody> - </table></para> - </section> - <section xml:id="callback-persistent"> - <title>Callbacks on Persistent Objects</title> - <para>Callback methods on Persistent classes are mapped in CayenneModeler for each - ObjEntity. Empty callback methods are automatically created as a part of class - generation (either with Maven, Ant or the Modeler) and are later filled with appropriate - logic by the programmer. E.g. assuming we mapped a 'post-add' callback called - 'onNewOrder' in ObjEntity 'Order', the following code will be - generated:<programlisting language="java">public abstract class _Order extends CayenneDataObject { - protected abstract void onNewOrder(); -} - -public class Order extends _Order { - - @Override - protected void onNewOrder() { - //TODO: implement onNewOrder - } -}</programlisting></para> - <para>As <code>onNewOrder()</code> is already declared in the mapping, it does not need to - be registered explicitly. Implementing the method in subclass to do something meaningful - is all that is required at this point. </para> - <para>As a rule callback methods do not have any knowledge of the outside application, and - can only access the state of the object itself and possibly the state of other - persistent objects via object's own ObjectContext.</para> - <para> - <note> - <para><emphasis role="italic">Validation and callbacks:</emphasis> There is a clear - overlap in functionality between object callbacks and - <code>DataObject.validateForX()</code> methods. In the future validation may - be completely superceeded by callbacks. It is a good idea to use "validateForX" - strictly for validation (or not use it at all). Updating the state before commit - should be done via callbacks.</para> - </note> - </para> - </section> - <section xml:id="callback-non-persistent"> - <title>Callbacks on Non-Persistent Listeners</title> - - <para> - <note> - <para>While listener callback methods can be declared in the Modeler (at least - as of this wrting), which ensures their automatic registration in runtime, - there's a big downside to it. The power of the listeners lies in their - complete separation from the XML mapping. The mapping once created, can be - reused in different contexts each having a different set of listeners. - Placing a Java class of the listener in the XML mapping, and relying on - Cayenne to instantiate the listeners severly limits mapping reusability. - Further down in this chapter we'll assume that the listener classes are - never present in the DataMap and are registered via API.</para> - </note> - </para> - <para>A listener is simply some application class that has one or more annotated - callback methods. A callback method signature should be <code>void - someMethod(SomePersistentType object)</code>. It can be public, private, protected - or use default access:</para> - <para> - <programlisting language="java"> public class OrderListener { - - @PostAdd(Order.class) - public void setDefaultsForNewOrder(Order o) { - o.setCreatedOn(new Date()); - } -}</programlisting> - </para> - <para>Notice that the example above contains an annotation on the callback method that - defines the type of the event this method should be called for. Before we go into - annotation details, we'll show how to create and register a listener with Cayenne. It is - always a user responsibility to register desired application listeners, usually right - after ServerRuntime is started. Here is an example:</para> - <para>First let's define 2 simple - listeners.<programlisting language="java">public class Listener1 { - - @PostAdd(MyEntity.class) - void postAdd(Persistent object) { - // do something - } -} - -public class Listener2 { - - @PostRemove({ MyEntity1.class, MyEntity2.class }) - void postRemove(Persistent object) { - // do something - } - - @PostUpdate({ MyEntity1.class, MyEntity2.class }) - void postUpdate(Persistent object) { - // do something - } -}</programlisting></para> - <para>Ignore the annotations for a minute. The important point here is that the listeners - are arbitrary classes unmapped and unknown to Cayenne, that contain some callback - methods. Now let's register them with - runtime:<programlisting language="java">ServerRuntime runtime = ... - -LifecycleCallbackRegistry registry = - runtime.getDataDomain().getEntityResolver().getCallbackRegistry(); - -registry.addListener(new Listener1()); -registry.addListener(new Listener2());</programlisting></para> - <para>Listeners in this example are very simple. However they don't have to be. Unlike - Persistent objects, normally listeners initialization is managed by the application - code, not Cayenne, so listeners may have knowledge of various application services, - operation transactional context, etc. Besides a single listener can apply to multiple - entities. As a consequence their callbacks can do more than just access a single - ObjectContext. </para> - <para>Now let's discuss the annotations. There are eight annotations exactly matching the - names of eight lifecycle events. A callback method in a listener should be annotated - with at least one, but possibly with more than one of them. Annotation itself defines - what event the callback should react to. Annotation parameters are essentially an entity - filter, defining a subset of ObjEntities whose events we are interested - in:<programlisting language="java">// this callback will be invoked on PostRemove event of any object -// belonging to MyEntity1, MyEntity2 or their subclasses -@PostRemove({ MyEntity1.class, MyEntity2.class }) -void postRemove(Persistent object) { - ... -}</programlisting><programlisting language="java">// similar example with multipe annotations on a single method -// each matching just one entity -@PostPersist(MyEntity1.class) -@PostRemove(MyEntity1.class) -@PostUpdate(MyEntity1.class) -void postCommit(MyEntity1 object) { - ... -}</programlisting></para> - <para>As shown above, "value" (the implicit annotation parameter) can contain one or more - entity classes. Only these entities' events will result in callback invocation. There's - also another way to match entities - via custom annotations. This allows to match any - number of entities without even knowing what they are. Here is an example. We'll first - define a custom - annotation:<programlisting language="java">@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface Tag { - -}</programlisting></para> - <para>Now we can define a listener that will react to events from ObjEntities annotated with - this - annotation:<programlisting language="java">public class Listener3 { - - @PostAdd(entityAnnotations = Tag.class) - void postAdd(Persistent object) { - // do something - } -}</programlisting></para> - <para>As you see we don't have any entities yet, still we can define a listener that does - something useful. Now let's annotate some - entities:<programlisting language="java">@Tag -public class MyEntity1 extends _MyEntity1 { - -} - -@Tag -public class MyEntity2 extends _MyEntity2 { - -}</programlisting></para> - </section> - - <section xml:id="comining-listeners-with-datachannelfilters"> - <title>Combining Listeners with DataChannelFilters</title> - <para>A final touch in the listeners design is preserving the state of the listener within a - single select or commit, so that events generated by multiple objects can be collected - and processed all together. To do that you will need to implement a - <code>DataChannelFilter</code>, and add some callback methods to it. They will store - their state in a ThreadLocal variable of the filter. Here is an example filter that does - something pretty meaningless - counts how many total objects were committed. However it - demonstrates the important pattern of aggregating multiple events and presenting a - combined - result:<programlisting language="java">public class CommittedObjectCounter implements DataChannelFilter { - - private ThreadLocal<int[]> counter; - - @Override - public void init(DataChannel channel) { - counter = new ThreadLocal<int[]>(); - } - - @Override - public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) { - return filterChain.onQuery(originatingContext, query); - } - - @Override - public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, - DataChannelFilterChain filterChain) { - - // init the counter for the current commit - counter.set(new int[1]); - - try { - return filterChain.onSync(originatingContext, changes, syncType); - } finally { - - // process aggregated result and release the counter - System.out.println("Committed " + counter.get()[0] + " object(s)"); - counter.set(null); - } - } - - @PostPersist(entityAnnotations = Tag.class) - @PostUpdate(entityAnnotations = Tag.class) - @PostRemove(entityAnnotations = Tag.class) - void afterCommit(Persistent object) { - counter.get()[0]++; - } -}</programlisting></para> - <para>Now since this is both a filter and a listener, it needs to be registered as - such:<programlisting language="java">CommittedObjectCounter counter = new CommittedObjectCounter(); - -ServerRuntime runtime = ... -DataDomain domain = runtime.getDataDomain(); - -// register filter -domain.addFilter(counter); - -// register listener -domain.getEntityResolver().getCallbackRegistry().addListener(counter);</programlisting></para> - </section> -</chapter>
http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/orderings.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/orderings.xml b/docs/docbook/cayenne-guide/src/docbkx/orderings.xml deleted file mode 100644 index f1b9c0e..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/orderings.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" - version="5.0" xml:id="orderings"> - <title>Orderings</title> - <para>An Ordering object defines how a list of objects should be ordered. Orderings are - essentially path expressions combined with a sorting strategy. Creating an Ordering: - <programlisting language="java">Ordering o = new Ordering(Painting.NAME_PROPERTY, SortOrder.ASENDING);</programlisting></para> - <para>Like expressions, orderings are translated into SQL as parts of queries (and the sorting - occurs in the database). Also like expressions, orderings can be used in memory, naturally - - to sort - objects:<programlisting language="java">Ordering o = new Ordering(Painting.NAME_PROPERTY, SortOrder.ASCENDING_INSENSITIVE); -List<Painting> list = ... -o.orderList(list);</programlisting>Note - that unlike filtering with Expressions, ordering is performed in-place. This list object is - reordered and no new list is created.</para> -</chapter> http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/part1.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/part1.xml b/docs/docbook/cayenne-guide/src/docbkx/part1.xml deleted file mode 100644 index ac79499..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/part1.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<part xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" - xml:id="cayenne-guide-part1" xmlns:xi="http://www.w3.org/2001/XInclude"> - <title>Object Relational Mapping with Cayenne</title> - <xi:include href="setup.xml"/> - <xi:include href="cayenne-mapping-structure.xml"/> - <xi:include href="cayennemodeler-application.xml"/> -</part> http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/part2.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/part2.xml b/docs/docbook/cayenne-guide/src/docbkx/part2.xml deleted file mode 100644 index c5f8201..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/part2.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<part xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" - xml:id="cayenne-guide-part2" xmlns:xi="http://www.w3.org/2001/XInclude"> - <title>Cayenne Framework</title> - <xi:include href="including-cayenne-in-project.xml"/> - <xi:include href="starting-cayenne.xml"/> - <xi:include href="persistent-objects-objectcontext.xml"/> - <xi:include href="expressions.xml"/> - <xi:include href="orderings.xml"/> - <xi:include href="queries.xml"/> - <xi:include href="lifecycle-events.xml"/> - <xi:include href="performance-tuning.xml"/> - <xi:include href="customizing-cayenne-runtime.xml"/> -</part> http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/part3.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/part3.xml b/docs/docbook/cayenne-guide/src/docbkx/part3.xml deleted file mode 100644 index 75c60b3..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/part3.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<part xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" - xml:id="cayenne-guide-part3" xmlns:xi="http://www.w3.org/2001/XInclude"> - <title>Cayenne Framework - Remote Object Persistence </title> - <xi:include href="rop-introduction.xml"/> - <xi:include href="rop-setup.xml"/> - <xi:include href="implementing-rop-server.xml"/> - <xi:include href="implementing-rop-client.xml"/> - <xi:include href="rop-deployment.xml"/> - <xi:include href="current-limitations.xml"/> -</part> http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/performance-tuning.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/performance-tuning.xml b/docs/docbook/cayenne-guide/src/docbkx/performance-tuning.xml deleted file mode 100644 index 18bb0bc..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/performance-tuning.xml +++ /dev/null @@ -1,289 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" - version="5.0" xml:id="performance-tuning"> - <title>Performance Tuning</title> - <section xml:id="prefetching"> - <title>Prefetching</title> - <para>Prefetching is a technique that allows to bring back in one query not only the queried - objects, but also objects related to them. In other words it is a controlled eager - relationship resolving mechanism. Prefetching is discussed in the "Performance Tuning" - chapter, as it is a powerful performance optimization method. However another common - application of prefetching is to refresh stale object relationships, so more generally - it can be viewed as a technique for managing subsets of the object graph.</para> - <para>Prefetching example: - <programlisting language="java">SelectQuery query = new SelectQuery(Artist.class); - -// this instructs Cayenne to prefetch one of Artist's relationships -query.addPrefetch("paintings"); - -// query is expecuted as usual, but the resulting Artists will have -// their paintings "inflated" -List<Artist> artists = context.performQuery(query);</programlisting>All - types of relationships can be preftetched - to-one, to-many, flattened. </para> - <para>A prefetch can span multiple relationships: - <programlisting language="java"> query.addPrefetch("paintings.gallery");</programlisting></para> - <para>A query can have multiple - prefetches:<programlisting language="java">query.addPrefetch("paintings"); -query.addPrefetch("paintings.gallery"); </programlisting></para> - <para>If a query is fetching DataRows, all "disjoint" prefetches are ignored, only "joint" - prefetches are executed (see prefetching semantics discussion below for what disjoint and - joint prefetches mean).</para> - - <section xml:id="prefetching-semantics"> - <title>Prefetching Semantics</title> - <para>Prefetching semantics defines a strategy to prefetch relationships. Depending on - it, Cayenne would generate different types of queries. The end result is the same - - query root objects with related objects fully resolved. However semantics can affect - preformance, in some cases significantly. There are 3 types of prefetch semantics, - all defined as constants in - org.apache.cayenne.query.PrefetchTreeNode:<programlisting language="java">PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS -PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS -PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS</programlisting></para> - <para>Each query has a default prefetch semantics, so generally users do not have to - worry about changing it, except when performance is a concern, or a few special - cases when a default sematics can't produce the correct result. SelectQuery uses - DISJOINT_PREFETCH_SEMANTICS by default. Semantics can be changed as - follows:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class); -query.addPrefetch("paintings").setSemantics( - PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS); </programlisting></para> - <para>There's no limitation on mixing different types of semantics in the same - SelectQuery. Multiple prefetches each can have its own semantics. </para> - <para>SQLTemplate and ProcedureQuery are both using JOINT_PREFETCH_SEMANTICS and it can - not be changed due to the nature of these two queries.</para> - </section> - <section xml:id="disjoint-prefetch-semantics"> - <title>Disjoint Prefetching Semantics</title> - <para>This semantics (only applicable to SelectQuery) results in Cayenne generatiing one - SQL statement for the main objects, and a separate statement for each prefetch path - (hence "disjoint" - related objects are not fetched with the main query). Each - additional SQL statement uses a qualifier of the main query plus a set of joins - traversing the preftech path between the main and related entity. </para> - <para>This strategy has an advantage of efficient JVM memory use, and faster overall - result processing by Cayenne, but it requires (1+N) SQL statements to be executed, - where N is the number of prefetched relationships.</para> - - </section> - <section xml:id="disjoint-by-id-prefetch-semantics"> - <title>Disjoint-by-ID Prefetching Semantics</title> - <para>This is a variation of disjoint prefetch where related objects are matched against - a set of IDs derived from the fetched main objects (or intermediate objects in a - multi-step prefetch). Cayenne limits the size of the generated WHERE clause, as most - DBs can't parse arbitrary large SQL. So prefetch queries are broken into smaller - queries. The size of is controlled by the DI property - Constants.SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY (the default number of conditions in - the generated WHERE clause is 10000). Cayenne will generate (1 + N * M) SQL - statements for each query using disjoint-by-ID prefetches, where N is the number of - relationships to prefetch, and M is the number of queries for a given prefetch that - is dependent on the number of objects in the result (ideally M = 1).</para> - <para>The advantage of this type of prefetch is that matching database rows by ID may be - much faster than matching the qualifier of the original query. Moreover this is - <emphasis role="bold">the only type of prefetch</emphasis> that can handle - SelectQueries with <emphasis role="bold">fetch limit</emphasis>. Both joint and - regular disjoint prefetches may produce invalid results or generate inefficient - fetch-the-entire table SQL when fetch limit is in effect. </para> - <para>The disadvantage is that query SQL can get unwieldy for large result sets, as each - object will have to have its own condition in the WHERE clause of the generated - SQL.</para> - </section> - <section xml:id="joint-prefetch-semantics"> - <title>Joint Prefetching Semantics</title> - <para>Joint semantics results in a single SQL statement for root objects and any number - of jointly prefetched paths. Cayenne processes in memory a cartesian product of the - entities involved, converting it to an object tree. It uses OUTER joins to connect - prefetched entities.</para> - <para>Joint is the most efficient prefetch type of the three as far as generated SQL - goes. There's always just 1 SQL query generated. Its downsides are the potentially - increased amount of data that needs to get across the network between the - application server and the database, and more data processing that needs to be done - on the Cayenne side.</para> - </section> - </section> - <section xml:id="datarows"> - <title>Data Rows</title> - <para>Converting result set data to Persistent objects and registering these objects in the - ObjectContext can be an expensive operation compareable to the time spent running the - query (and frequently exceeding it). Internally Cayenne builds the result as a list of - DataRows, that are later converted to objects. Skipping the last step and using data in - the form of DataRows can significantly increase performance. </para> - <para>DataRow is a simply a map of values keyed by their DB column name. It is a ubiqutous - representation of DB data used internally by Cayenne. And it can be quite usable as is - in the application in many cases. So performance sensitive selects should consider - DataRows - it saves memory and CPU cycles. All selecting queries support DataRows - option, - e.g.:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class); -query.setFetchingDataRows(true); - -List<DataRow> rows = context.performQuery(query); </programlisting><programlisting language="java">SQLTemplate query = new SQLTemplate(Artist.class, "SELECT * FROM ARTIST"); -query.setFetchingDataRows(true); - -List<DataRow> rows = context.performQuery(query);</programlisting></para> - <para>Moreover DataRows may be converted to Persistent objects later as needed. So e.g. you - may implement some in-memory filtering, only converting a subset of fetched - objects:<programlisting language="java">// you need to cast ObjectContext to DataContext to get access to 'objectFromDataRow' -DataContext dataContext = (DataContext) context; - -for(DataRow row : rows) { - if(row.get("DATE_OF_BIRTH") != null) { - Artist artist = dataContext.objectFromDataRow(Artist.class, row); - // do something with Artist... - ... - } -}</programlisting></para> - </section> - <section xml:id="iterated-queries"> - <title>Iterated Queries</title> - <para>While contemporary hardware may easily allow applications to fetch hundreds of - thousands or even millions of objects into memory, it doesn't mean this is always a good - idea to do so. You can optimize processing of very large result sets with two techniques - discussed in this and the following chapter - iterated and paginated queries. </para> - <para>Iterated query is not actually a special query. Any selecting query can be executed in - iterated mode by the DataContext (like in the previous example, a cast to DataContext is - needed). DataContext returns an object called <code>ResultIterator</code> that is backed - by an open ResultSet. Data is read from ResultIterator one row at a time until it is - exhausted. Data comes as a DataRows regardless of whether the orginating query was - configured to fetch DataRows or not. A ResultIterator must be explicitly closed to avoid - JDBC resource leak.</para> - <para>Iterated query provides constant memory performance for arbitrarily large ResultSets. - This is true at least on the Cayenne end, as JDBC driver may still decide to bring the - entire ResultSet into the JVM memory. </para> - <para>Here is a full - example:<programlisting language="java">// you need to cast ObjectContext to DataContext to get access to 'performIteratedQuery' -DataContext dataContext = (DataContext) context; - -// create a regular query -SelectQuery q = new SelectQuery(Artist.class); - -// ResultIterator operations all throw checked CayenneException -// moreover 'finally' is required to close it -try { - - ResultIterator it = dataContext.performIteratedQuery(q); - - try { - while(it.hasNextRow()) { - // normally we'd read a row, process its data, and throw it away - // this gives us constant memory performance - Map row = (Map) it.nextRow(); - - // do something with the row... - ... - } - } - finally { - it.close(); - } -} -catch(CayenneException e) { - e.printStackTrace(); -} -</programlisting>Also - common sense tells us that ResultIterators should be processed and closed as soon as - possible to release the DB connection. E.g. storing open iterators between HTTP requests - and for unpredictable length of time would quickly exhaust the connection pool.</para> - </section> - <section xml:id="paginated-queries"> - <title>Paginated Queries</title> - <para>Enabling query pagination allows to load very large result sets in a Java app with - very little memory overhead (much smaller than even the DataRows option discussed - above). Moreover it is completely transparent to the application - a user gets what - appears to be a list of Persistent objects - there's no iterator to close or DataRows to - convert to objects:</para> - <para> - <programlisting language="java">SelectQuery query = new SelectQuery(Artist.class); -query.setPageSize(50); - -// the fact that result is paginated is transparent -List<Artist> artists = ctxt.performQuery(query);</programlisting> - </para> - <para>Having said that, DataRows option can be combined with pagination, providing the best - of both - worlds:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class); -query.setPageSize(50); -query.setFetchingDataRows(true); - -List<DataRow> rows = ctxt.performQuery(query);</programlisting></para> - <para>The way pagination works internally, it first fetches a list of IDs for the root - entity of the query. This is very fast and initially takes very little memory. Then when - an object is requested at an arbitrary index in the list, this object and adjacent - objects (a "page" of objects that is determined by the query pageSize parameter) are - fetched together by ID. Subsequent requests to the objects of this "page" are served - from memory.</para> - <para>An obvious limitation of pagination is that if you eventually access all objects in - the list, the memory use will end up being the same as with no pagination. However it is - still a very useful approach. With some lists (e.g. multi-page search results) only a - few top objects are normally accessed. At the same time pagination allows to estimate - the full list size without fetching all the objects. And again - it is completely - transparent and looks like a normal query.</para> - </section> - <section xml:id="caching-and-fresh-data"> - <title>Caching and Fresh Data</title> - <section xml:id="object-caching"> - <title>Object Caching</title> - </section> - <section xml:id="query-result-caching"> - <title>Query Result Caching</title> - </section> - </section> - <section xml:id="turning-off-synchronization-of-objectcontexts"> - <title>Turning off Synchronization of ObjectContexts</title> - <para>By default when a single ObjectContext commits its changes, all other contexts in the - same runtime receive an event that contains all the committed changes. This allows them - to update their cached object state to match the latest committed data. There are - however many problems with this ostensibly helpful feature. In short - it works well in - environments with few contexts and in unclustered scenarios, such as single user desktop - applications, or simple webapps with only a few users. More specifically:<itemizedlist> - <listitem> - <para>The performance of synchronization is (probably worse than) O(N) where N - is the number of peer ObjectContexts in the system. In a typical webapp N - can be quite large. Besides for any given context, due to locking on - synchronization, context own performance will depend not only on the queries - that it runs, but also on external events that it does not control. This is - unacceptable in most situations. </para> - </listitem> - <listitem> - <para>Commit events are untargeted - even contexts that do not hold a given - updated object will receive the full event that they will have to - process.</para> - </listitem> - <listitem> - <para>Clustering between JVMs doesn't scale - apps with large volumes of commits - will quickly saturate the network with events, while most of those will be - thrown away on the receiving end as mentioned above.</para> - </listitem> - <listitem> - <para>Some contexts may not want to be refreshed. A refresh in the middle of an - operation may lead to unpredictable results. </para> - </listitem> - <listitem> - <para>Synchronization will interfere with optimistic locking. </para> - </listitem> - </itemizedlist>So we've made a good case for disabling synchronization in most webapps. - To do that, set to "false" the following DI property - - <code>Constants.SERVER_CONTEXTS_SYNC_PROPERTY</code>, using one of the standard - Cayenne DI approaches. E.g. from command - line:<programlisting language="java">java -Dcayenne.server.contexts_sync_strategy=false</programlisting>Or - by changing the standard properties Map in a custom extensions - module:<programlisting language="java">public class MyModule implements Module { - - @Override - public void configure(Binder binder) { - binder.bindMap(Constants.PROPERTIES_MAP).put(Constants.SERVER_CONTEXTS_SYNC_PROPERTY, "false"); - } -}</programlisting></para> - </section> -</chapter> http://git-wip-us.apache.org/repos/asf/cayenne/blob/7783cd34/docs/docbook/cayenne-guide/src/docbkx/persistent-objects-objectcontext.xml ---------------------------------------------------------------------- diff --git a/docs/docbook/cayenne-guide/src/docbkx/persistent-objects-objectcontext.xml b/docs/docbook/cayenne-guide/src/docbkx/persistent-objects-objectcontext.xml deleted file mode 100644 index 471cca1..0000000 --- a/docs/docbook/cayenne-guide/src/docbkx/persistent-objects-objectcontext.xml +++ /dev/null @@ -1,300 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to you under the Apache License, Version - 2.0 (the "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 Unless required by - applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for - the specific language governing permissions and limitations under the - License. ---> -<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" - version="5.0" xml:id="persistent-objects-objectcontext"> - <title>Persistent Objects and ObjectContext</title> - <section xml:id="objectcontext"> - <title>ObjectContext</title> - <para>ObjectContext is an interface that users normally work with to access the database. It - provides the API to execute database operations and to manage persistent objects. A - context is obtained from the - ServerRuntime:<programlisting language="java">ObjectContext context = runtime.getContext();</programlisting></para> - <para>The call above creates a new instance of ObjectContext that can access the database via this - runtime. ObjectContext is a single "work area" in Cayenne, storing persistent objects. - ObjectContext guarantees that for each database row with a unique ID it will contain at - most one instance of an object, thus ensuring object graph consistency between multiple - selects (a feature called "uniquing"). At the same time different ObjectContexts will - have independent copies of objects for each unique database row. This allows users to - isolate object changes from one another by using separate ObjectContexts.</para> - <para>These properties directly affect the strategies for scoping and sharing (or not - sharing) ObjectContexts. Contexts that are only used to fetch objects from the database - and whose objects are never modified by the application can be shared between mutliple - users (and multiple threads). Contexts that store modified objects should be accessed - only by a single user (e.g. a web application user might reuse a context instance - between multiple web requests in the same HttpSession, thus carrying uncommitted changes - to objects from request to request, until he decides to commit or rollback them). Even - for a single user it might make sense to use mutliple ObjectContexts (e.g. - request-scoped contexts to allow concurrent requests from the browser that change and - commit objects independently).</para> - <para>ObjectContext is serializable and does not permanently hold to any of the application - resources. So it does not have to be closed. If the context is not used anymore, it - should simply be allowed to go out of scope and get garbage collected, just like any - other Java object.</para> - </section> - <section xml:id="persistent-lifecycle"> - <title>Persistent Object and its Lifecycle</title> - <para>Cayenne can persist Java objects that implement <code>org.apache.cayenne.Persistent</code> - interface. Generally persistent classes are generated from the model as described above, - so users do not have to worry about superclass and property implementation details. </para> - <para>Persistent interface provides access to 3 persistence-related properties - objectId, - persistenceState and objectContext. All 3 are initialized by Cayenne runtime framework. - Application code should not attempt to change them them. However it is allowed to read - them, which provides valuable runtime information. E.g. ObjectId can be used for quick - equality check of 2 objects, knowing persistence state would allow highlighting changed - objects, etc.</para> - <para>Each persistent object belongs to a single ObjectContext, and can be in one of the following - persistence states (as defined in <code>org.apache.cayenne.PersistenceState</code>) - :<table frame="void"> - <caption>Persistence States</caption> - <col width="16%"/> - <col width="84%"/> - <tbody> - <tr> - <td>TRANSIENT</td> - <td>The object is not registered with an ObjectContext and will not be - persisted.</td> - </tr> - <tr> - <td>NEW</td> - <td>The object is freshly registered in an ObjectContext, but has not been - saved to the database yet and there is no matching database row.</td> - </tr> - <tr> - <td>COMMITTED</td> - <td>The object is registered in an ObjectContext, there is a row in the - database corresponding to this object, and the object state corresponds - to the last known state of the matching database row.</td> - </tr> - <tr> - <td>MODIFIED</td> - <td>The object is registered in an ObjectContext, there is a row in the - database corresponding to this object, but the object in-memory state - has diverged from the last known state of the matching database - row.</td> - </tr> - <tr> - <td>HOLLOW</td> - <td>The object is registered in an ObjectContext, there is a row in the - database corresponding to this object, but the object state is unknown. - Whenever an application tries to access a property of such object, - Cayenne attempts reading its values from the database and "inflate" the - object, turning it to COMMITED.</td> - </tr> - <tr> - <td>DELETED</td> - <td>The object is registered in an ObjectContext and has been marked for - deletion in-memory. The corresponding row in the database will get - deleted upon ObjectContext commit, and the object state will be turned - into TRANSIENT.</td> - </tr> - </tbody> - </table></para> - </section> - <section xml:id="persistent-operations"> - <title>ObjectContext Persistence API</title> - <para>One of the first things users usually want to do with an ObjectContext is to select - some objects from a database. This is done by calling "<emphasis role="italic" - >performQuery</emphasis>" - method:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class); -List<Artist> artists = context.performQuery(query);</programlisting>We'll - discuss queries in some detail in the following chapters. The example above is - self-explanatory - we create a SelectQuery that matches all Artist objects present in - the database, and then call "performQuery", getting a list of Artist objects.</para> - <para>Some queries can be quite complex, returning multiple result sets or even updating the - database. For such queries ObjectContext provides "<emphasis role="italic" - >performGenericQuery</emphasis>"method. While not nearly as commonly-used as - "performQuery", it is nevertheless important in some situations. - E.g.:<programlisting language="java">Collection<Query> queries = ... // multiple queries that need to be run together -QueryChain query = new QueryChain(queries); - -QueryResponse response = context.performGenericQuery(query);</programlisting></para> - <para>An application might modify selected objects. E.g.:</para> - <programlisting language="java">Artist selectedArtist = artists.get(0); -selectedArtist.setName("Dali");</programlisting> - <para>The first time the object property is changed, the object's state is automatically set - to "MODIFIED" by Cayenne. Cayenne tracks all in-memory changes until a user calls - "<emphasis role="italic" - >commitChanges</emphasis>":<programlisting language="java">context.commitChanges();</programlisting>At - this point all in-memory changes are analyzed and a minimal set of SQL statements is - issued in a single transaction to synchronize the database with the in-memory state. In - our example "commitChanges" commits just one object, but generally it can be any number - of objects. </para> - <para>If instead of commit, we wanted to reset all changed objects to the previously - committed state, we'd call <emphasis>rollbackChanges</emphasis> - instead:<programlisting language="java">context.rollbackChanges();</programlisting></para> - <para>"<emphasis role="italic">newObject</emphasis>" method call creates a persistent object - and sets its state to - "NEW":<programlisting language="java">Artist newArtist = context.newObject(Artist.class); -newArtist.setName("Picasso");</programlisting></para> - <para>It will only exist in memory until "commitChanges" is issued. On commit Cayenne might - generate a new primary key (unless a user set it explicitly, or a PK was inferred from a - relationship) and issue an INSERT SQL statement to permanently store the object.</para> - <para><emphasis>deleteObjects</emphasis> method takes one or more Persistent objects and - marks them as - "DELETED":<programlisting language="java">context.deleteObjects(artist1); -context.deleteObjects(artist2, artist3, artist4);</programlisting>Additionally - "deleteObjects" processes all delete rules modeled for the affected objects. This may - result in implicitly deleting or modifying extra related objects. Same as insert and - update, delete operations are sent to the database only when "commitChanges" is called. - Similarly "rollbackChanges" will undo the effect of "newObject" and - "deleteObjects".</para> - <para><emphasis>localObject</emphasis> returns a copy of a given persistent object that is - "local" to a given ObjectContext:</para> - <para>Since an application often works with more than one context, "localObject" is a rather - common operation. E.g. to improve performance a user might utilize a single shared - context to select and cache data, and then occasionally transfer some selected objects - to another context to modify and commit - them:<programlisting language="java">ObjectContext editingContext = runtime.getContext(); -Artist localArtist = editingContext.localObject(artist);</programlisting></para> - <para>Often an appliction needs to inspect mapping metadata. This information is stored in - the EntityResolver object, accessible via the - ObjectContext:<programlisting language="java">EntityResolver resolver = objectContext.getEntityResolver();</programlisting></para> - <para>Here we discussed the most commonly used subset of the ObjectContext API. There are - other useful methods, e.g. those allowing to inspect registered objects state en bulk, - etc. Check the latest JavaDocs for details.</para> - </section> - <section xml:id="cayenne-helper-class"> - <title>Cayenne Helper Class</title> - <para>There is a useful helper class called "Cayenne" (fully-qualified name - <code>"org.apache.cayenne.Cayenne"</code>) that builds on ObjectContext API to - provide a number of very common operations. E.g. get a primary key (most entities do not - model PK as an object property) - :<programlisting language="java">long pk = Cayenne.longPKForObject(artist);</programlisting></para> - <para>It also provides the reverse operation - finding an object given a known - PK:<programlisting language="java">Artist artist = Cayenne.objectForPK(context, Artist.class, 34579);</programlisting></para> - <para>If a query is expected to return 0 or 1 object, Cayenne helper class can be used to find - this object. It throws an exception if more than one object matched the - query:<programlisting language="java">Artist artist = (Artist) Cayenne.objectForQuery(context, new SelectQuery(Artist.class));</programlisting></para> - <para>Feel free to explore Cayenne class API for other useful methods.</para> - </section> - <section xml:id="objectcontext-nesting"> - <title>ObjectContext Nesting</title> - <para>In all the examples shown so far an ObjectContext would directly connect to a database - to select data or synchronize its state (either via commit or rollback). However another - context can be used in all these scenarios instead of a database. This concept is called - ObjectContext "nesting". Nesting is a parent/child relationship between two contexts, - where child is a nested context and selects or commits its objects via a parent. </para> - <para>Nesting is useful to create isolated object editing areas (child contexts) that need - to all be committed to an intermediate in-memory store (parent context), or rolled back - without affecting changes already recorded in the parent. Think cascading GUI dialogs, - or parallel AJAX requests coming to the same session.</para> - <para>In theory Cayenne supports any number of nesting levels, however applications should - generally stay with one or two, as deep hierarchies will most certainly degrade the - performance of the deeply nested child contexts. This is due to the fact that each - context in a nesting chain has to update its own objects during most operations. </para> - <para>Cayenne ROP is an extreme case of nesting when a child context is located in a - separate JVM and communicates with its parent via a web service. ROP is discussed in - details in the following chapters. Here we concentrate on the same-VM nesting.</para> - <para>To create a nested context, use an instance of ServerRuntime, passing it the desired - parent:<programlisting language="java">ObjectContext parent = runtime.getContext(); -ObjectContext nested = runtime.getContext((DataChannel) parent);</programlisting>From - here a nested context operates just like a regular context (you can perform queries, - create and delete objects, etc.). The only difference is that commit and rollback - operations can either be limited to synchronization with the parent, or cascade all the - way to the - database:<programlisting language="java">// merges nested context changes into the parent context -nested.commitChangesToParent(); - -// regular 'commitChanges' cascades commit through the chain -// of parent contexts all the way to the database -nested.commitChanges();</programlisting><programlisting language="java">// unrolls all local changes, getting context in a state identical to parent -nested.rollbackChangesLocally(); - -// regular 'rollbackChanges' cascades rollback through the chain of contexts -// all the way to the topmost parent -nested.rollbackChanges();</programlisting></para> - </section> - <section xml:id="generic-persistent-objects"> - <title>Generic Persistent Objects</title> - <para>As described in the CayenneModeler chapter, Cayenne supports mapping of completely - generic classes to specific entities. Although for conveniece most applications should - stick with entity-specific class mappings, the generic feature offers some interesting - possibilities, such as creating mappings completely on the fly in a running application, - etc.</para> - <para>Generic objects are first class citizens in Cayenne, and all common persistent - operations apply to them as well. There are some pecularities however, described - below.</para> - <para>When creating a new generic object, either cast your ObjectContext to DataContext - (that provides "newObject(String)" API), or provide your object with an explicit - ObjectId:<programlisting language="java">DataObject generic = ((DataContext) context).newObject("GenericEntity");</programlisting><programlisting language="java">DataObject generic = new CayenneDataObject(); -generic.setObjectId(new ObjectId("GenericEntity")); -context.registerNewObject(generic);</programlisting>SelectQuery - for generic object should be created passing entity name String in constructor, instead - of a Java - class:<programlisting language="java">SelectQuery query = new SelectQuery("GenericEntity");</programlisting>Use - DataObject API to access and modify properties of a generic - object:<programlisting language="java">String name = (String) generic.readProperty("name"); -generic.writeProperty("name", "New Name");</programlisting>This - is how an application can obtain entity name of a generic - object:<programlisting language="java">String entityName = generic.getObjectId().getEntityName();</programlisting></para> - </section> - <section xml:id="transactions"> - <title>Transactions</title> - <para>Considering how much attention is given to managing transactions in most other ORMs, - transactions have been conspicuously absent from the ObjectContext discussion till now. - The reason is that transactions are seamless in Cayenne in all but a few special cases. - ObjectContext is an in-memory container of objects that is disconnected from the - database, except when it needs to run an operation. So it does not care about any - surrounding transaction scope. Sure enough all database operations are transactional, so - when an application does a commit, all SQL execution is wrapped in a database - transaction. But this is done behind the scenes and is rarely a concern to the - application code.</para> - <para>Two cases where transactions need to be taken into consideration are container-managed - and application-managed transactions. </para> - <para>If you are using an EJB container (or some other JTA environment), you'll likely need - to switch Cayenne runtime into "external transactions mode". This is either done in the - Modeler (check DataDomain > 'Container-Managed Transactions' checkbox), or in the - code:<programlisting language="java">runtime.getDataDomain().setUsingExternalTransactions(true);</programlisting>In - this case Cayenne assumes that JDBC Connections obtained by runtime whenever that might - happen are all coming from a transactional DataSource managed by the container. In this - case Cayenne does not attempt to commit or rollback the connections, leaving it up to - the container to do that when appropriate.</para> - <para>In the second scenario, an application might need to define its own transaction scope - that spans more than one Cayenne operation. E.g. two sequential commits that need to be - rolled back together in case of failure. This can be done with an explicit thread-bound - transaction that surrounds a set of operations. Application is responsible for - committing or rolling it - back:<programlisting language="java">Transaction tx = runtime.getDataDomain().createTransaction(); -Transaction.bindThreadTransaction(tx); - -try { - // commit one or more contexts - context1.commitChanges(); - context2.commitChanges(); - .... - // after changing some objects in context1, commit again - context1.commitChnages(); - .... - // if no failures, commit - tx.commit(); -} -catch (Exception ex) { - tx.setRollbackOnly(); -} -finally { - Transaction.bindThreadTransaction(null); - - if (tx.getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) { - try { - tx.rollback(); - } - catch (Exception rollbackEx) { - } - } -} </programlisting></para> - </section> -</chapter>