blob: 2e1ee909ff16bb10af42316020769d460ee62b9d [file] [log] [blame]
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title xmlns:d="http://docbook.org/ns/docbook">Chapter&nbsp;6.&nbsp;Persistent Objects and ObjectContext</title><link rel="stylesheet" type="text/css" href="css/cayenne-doc.css"><meta xmlns:d="http://docbook.org/ns/docbook" name="keywords" content="Cayenne 4.0 documentation"><meta xmlns:d="http://docbook.org/ns/docbook" name="description" content="User documentation for Apache Cayenne version 4.0"><link rel="home" href="index.html" title="Cayenne Guide"><link rel="up" href="cayenne-guide-part2.html" title="Part&nbsp;II.&nbsp;Cayenne Framework"><link rel="prev" href="starting-cayenne.html" title="Chapter&nbsp;5.&nbsp;Starting Cayenne"><link rel="next" href="expressions.html" title="Chapter&nbsp;7.&nbsp;Expressions"><script xmlns:d="http://docbook.org/ns/docbook" type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-7036673-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div xmlns:d="http://docbook.org/ns/docbook" class="navheader"><table width="100%" summary="Navigation header"><tr><th class="versioninfo">v.4.0 (4.0.M5)</th><th align="center">Chapter&nbsp;6.&nbsp;Persistent Objects and ObjectContext</th><th></th></tr><tr><td width="20%" align="left"><a accesskey="p" href="starting-cayenne.html">Prev</a>&nbsp;</td><th width="60%" align="center"><a accesskey="u" href="cayenne-guide-part2.html">Part&nbsp;II.&nbsp;Cayenne Framework</a></th><td width="20%" align="right">&nbsp;<a accesskey="n" href="expressions.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="persistent-objects-objectcontext"></a>Chapter&nbsp;6.&nbsp;Persistent Objects and ObjectContext</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl class="toc"><dt><span class="section"><a href="persistent-objects-objectcontext.html#objectcontext">ObjectContext</a></span></dt><dt><span class="section"><a href="persistent-objects-objectcontext.html#persistent-lifecycle">Persistent Object and its Lifecycle</a></span></dt><dt><span class="section"><a href="persistent-objects-objectcontext.html#persistent-operations">ObjectContext Persistence API</a></span></dt><dt><span class="section"><a href="persistent-objects-objectcontext.html#cayenne-helper-class">Cayenne Helper Class</a></span></dt><dt><span class="section"><a href="persistent-objects-objectcontext.html#objectcontext-nesting">ObjectContext Nesting</a></span></dt><dt><span class="section"><a href="persistent-objects-objectcontext.html#generic-persistent-objects">Generic Persistent Objects</a></span></dt><dt><span class="section"><a href="persistent-objects-objectcontext.html#transactions">Transactions</a></span></dt></dl></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="objectcontext"></a>ObjectContext</h2></div></div></div><p>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:</p><pre class="programlisting">ObjectContext context = runtime.newContext();</pre><p>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.</p><p>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).</p><p>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.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="persistent-lifecycle"></a>Persistent Object and its Lifecycle</h2></div></div></div><p>Cayenne can persist Java objects that implement <code class="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. </p><p>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.</p><p>Each persistent object belongs to a single ObjectContext, and can be in one of the following
persistence states (as defined in <code class="code">org.apache.cayenne.PersistenceState</code>)
:</p><div class="table"><a name="d0e1498"></a><p class="title"><b>Table&nbsp;6.1.&nbsp;Persistence States</b></p><div class="table-contents"><table frame="void"><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></div></div><p><br class="table-break"></p></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="persistent-operations"></a>ObjectContext Persistence API</h2></div></div></div><p>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 "<span class="italic">performQuery</span>"
method:</p><pre class="programlisting">SelectQuery query = <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> SelectQuery(Artist.<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span>);
List&lt;Artist&gt; artists = context.performQuery(query);</pre><p>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.</p><p>Some queries can be quite complex, returning multiple result sets or even updating the
database. For such queries ObjectContext provides "<span class="italic">performGenericQuery</span>"method. While not nearly as commonly-used as
"performQuery", it is nevertheless important in some situations.
E.g.:</p><pre class="programlisting">Collection&lt;Query&gt; queries = ... <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// multiple queries that need to be run together</span>
QueryChain query = <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> QueryChain(queries);
QueryResponse response = context.performGenericQuery(query);</pre><p>An application might modify selected objects. E.g.:</p><pre class="programlisting">Artist selectedArtist = artists.get(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-number">0</span>);
selectedArtist.setName(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"Dali"</span>);</pre><p>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
"<span class="italic">commitChanges</span>":</p><pre class="programlisting">context.commitChanges();</pre><p>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. </p><p>If instead of commit, we wanted to reset all changed objects to the previously
committed state, we'd call <span class="emphasis"><em>rollbackChanges</em></span>
instead:</p><pre class="programlisting">context.rollbackChanges();</pre><p>"<span class="italic">newObject</span>" method call creates a persistent object
and sets its state to
"NEW":</p><pre class="programlisting">Artist newArtist = context.newObject(Artist.<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span>);
newArtist.setName(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"Picasso"</span>);</pre><p>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.</p><p><span class="emphasis"><em>deleteObjects</em></span> method takes one or more Persistent objects and
marks them as
"DELETED":</p><pre class="programlisting">context.deleteObjects(artist1);
context.deleteObjects(artist2, artist3, artist4);</pre><p>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".</p><p><span class="emphasis"><em>localObject</em></span> returns a copy of a given persistent object that is
"local" to a given ObjectContext:</p><p>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:</p><pre class="programlisting">ObjectContext editingContext = runtime.newContext();
Artist localArtist = editingContext.localObject(artist);</pre><p>Often an appliction needs to inspect mapping metadata. This information is stored in
the EntityResolver object, accessible via the
ObjectContext:</p><pre class="programlisting">EntityResolver resolver = objectContext.getEntityResolver();</pre><p>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.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="cayenne-helper-class"></a>Cayenne Helper Class</h2></div></div></div><p>There is a useful helper class called "Cayenne" (fully-qualified name
<code class="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)
:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">long</span> pk = Cayenne.longPKForObject(artist);</pre><p>It also provides the reverse operation - finding an object given a known
PK:</p><pre class="programlisting">Artist artist = Cayenne.objectForPK(context, Artist.<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span>, <span xmlns="http://www.w3.org/1999/xhtml" class="hl-number">34579</span>);</pre><p>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:</p><pre class="programlisting">Artist artist = (Artist) Cayenne.objectForQuery(context, <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> SelectQuery(Artist.<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span>));</pre><p>Feel free to explore Cayenne class API for other useful methods.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="objectcontext-nesting"></a>ObjectContext Nesting</h2></div></div></div><p>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. </p><p>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.</p><p>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. </p><p>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.</p><p>To create a nested context, use an instance of ServerRuntime, passing it the desired
parent:</p><pre class="programlisting">ObjectContext parent = runtime.newContext();
ObjectContext nested = runtime.newContext((DataChannel) parent);</pre><p>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:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// merges nested context changes into the parent context</span>
nested.commitChangesToParent();
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// regular 'commitChanges' cascades commit through the chain </span>
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// of parent contexts all the way to the database</span>
nested.commitChanges();</pre><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// unrolls all local changes, getting context in a state identical to parent</span>
nested.rollbackChangesLocally();
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// regular 'rollbackChanges' cascades rollback through the chain of contexts </span>
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// all the way to the topmost parent</span>
nested.rollbackChanges();</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="generic-persistent-objects"></a>Generic Persistent Objects</h2></div></div></div><p>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.</p><p>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.</p><p>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:</p><pre class="programlisting">DataObject generic = ((DataContext) context).newObject(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"GenericEntity"</span>);</pre><pre class="programlisting">DataObject generic = <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> CayenneDataObject();
generic.setObjectId(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> ObjectId(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"GenericEntity"</span>));
context.registerNewObject(generic);</pre><p>SelectQuery
for generic object should be created passing entity name String in constructor, instead
of a Java
class:</p><pre class="programlisting">SelectQuery query = <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> SelectQuery(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"GenericEntity"</span>);</pre><p>Use
DataObject API to access and modify properties of a generic
object:</p><pre class="programlisting">String name = (String) generic.readProperty(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"name"</span>);
generic.writeProperty(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"name"</span>, <span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"New Name"</span>);</pre><p>This
is how an application can obtain entity name of a generic
object:</p><pre class="programlisting">String entityName = generic.getObjectId().getEntityName();</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title"><a name="transactions"></a>Transactions</h2></div></div></div><p>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.</p><p>Two cases where transactions need to be taken into consideration are container-managed
and application-managed transactions. </p><p>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 done by setting DI
configuration property defined in <code class="code">Constants.SERVER_EXTERNAL_TX_PROPERTY</code>
(see Appendix A). If this property is set to "true", 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.</p><p>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 via
<code class="code">ServerRuntime.performInTransaction</code>
method:</p><pre class="programlisting">Integer result = runtime.performInTransaction(() -&gt; {
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// commit one or more contexts</span>
context1.commitChanges();
context2.commitChanges();
....
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// after changing some objects in context1, commit again</span>
context1.commitChanges();
....
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// return an arbitrary result or null if we don't care about the result</span>
<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">return</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-number">5</span>;
});</pre><p>When inside the transaction, current thread Transaction object can be accessed via a
static method. E.g. here is an example that initializes transaction JDBC connection with
a custom connection object
:</p><pre class="programlisting">Transaction tx = BaseTransaction.getThreadTransaction();
tx.addConnection(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"mydatanode"</span>, myConnection); </pre></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="starting-cayenne.html">Prev</a>&nbsp;</td><td width="20%" align="center"><a accesskey="u" href="cayenne-guide-part2.html">Up</a></td><td width="40%" align="right">&nbsp;<a accesskey="n" href="expressions.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter&nbsp;5.&nbsp;Starting Cayenne&nbsp;</td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top">&nbsp;Chapter&nbsp;7.&nbsp;Expressions</td></tr></table></div></body></html>