Jim --
My suggestion would be to keep all the user-entered values around in
an object that's not the manufacturer, but is solely a value object
that is very specific to this particular UI. In most cases, this is
the page class itself:
class CreateEditManufacturer extends BasePage {
public abstract get/setName();
public abstract get/setFlavor();
public abstract get/setBossesFavoriteMarxBrother();
...etc...
public IPage saveManufacturer() {
try {
getManufacturer().setName(getName());
getManufacturer().setBossesFavoriteMarxBrother
(getBossesFavoriteMarxBrother());
...etc...
getManufacturerService().createManufacturer();
} catch (Exception e) {
setError("Could not create Manufacturer: " + e.getMessage());
return this;
}
}
}
But perhaps you have a multi-page process or something, where it
becomes simpler to build up the user's responses in a _value object_:
class CreateEditManufacturer extends BasePage {
@Persist("server")
public abstract ManufacturerEntryForm get/
setManufacturerEntryForm();
public IPage saveManufacturer() {
try {
values = getManufacturerValues();
getManufacturer().setName(values.getName());
getManufacturer().setBossesFavoriteMarxBrother
(values.getBossesFavoriteMarxBrother());
...etc...
getManufacturerService().createManufacturer();
} catch (Exception e) {
setError("Could not create Manufacturer: " + e.getMessage());
return this;
}
}
}
class ManufacturerEntryForm implements Serializable { // *not*
persistable!
...
}
Either way, the point is that you keep the user's input in a value
object of some kind, page or otherwise, that is -- this is the key --
modeled after the user interface, *not* after the domain object. The
value object is a transcription of what the user entered.
Only when you're ready to do something committable do you apply
changes from the UI to the domain. So you're not cloning the domain
object; you're just not eagerly applying changes to the domain at
each step.
This is not an "always the right answer" practice; it's just a
"usually works best" practice.
But yes, in any case, you're quite right: Hibernate doesn't do any
kind of in-memory rollback on your objects! No matter what approach
you take, you *cannot* manipulate a domain object unless you're
prepared to either (1) commit it or (2) discard it.
Cheers,
Paul
On May 2, 2006, at 7:16 PM, Jim Steinberger wrote:
Paul,
Thanks much -- I take your points well; there are certainly some
things
I'm considering changing. If you could, though, I'm having trouble
relating them to this specific case; here's a basic listener:
public IPage saveManufacturer() {
try {
getManufacturerService().createManufacturer(getManufacturer());
} catch (Exception e) {
setError("Could not create Manufacturer: " + e.getMessage());
return this;
}
}
You suggested throwing away the bad object (getManufacturer()), but
if I
do that, I'm throwing away all the data the user entered, forcing them
to retype everything -- and I couldn't query that from persistence
because it was never persisted in the first place (the primary key
on my
object is invalid because the INSERT never happened).
I had previously assumed that Hibernate not change my objects if the
database transaction didn't complete. e.g. if the INSERT fails, I
assumed Hibernate wouldn't set my primary key. I only found out
recently that this was a bad assumption; if something goes wrong, the
objects passed in can be mutated.
_Effective Java_ would tell me to, then, clone my objects before
passing them to an API that could potentially mutate them, since I
need
to keep the original data around (the user input). But I've never
seen
anyone doing this, so I'm asking about this situation here because
either I'm missing something or a lot of other people are.
Jim
-----Original Message-----
From: Paul Cantrell [mailto:[EMAIL PROTECTED]
Sent: Tuesday, May 02, 2006 7:12 PM
To: Tapestry users
Subject: Re: Insert/Update pages and Hibernate
There are several practices you can use to avoid painting yourself
into this corner:
(1) Probably the best and simplest: Use a straightforward transaction-
per-request model, and don't ever hold on to persistent objects
between requests.* Instead, hold on to the IDs of your persistent
objects. Think of the persistent objects as a fleeting view of the
app's persistent state, a view that will vanish at the end of the
request. Keep enough user input and working values in your page to be
able to get all the way from a cleanly fetched object to a
committable one, in one go -- instead of poking and prodding the
persistent object to its ultimate committable state across several
requests.
Having a custom Tapestry SqueezeAdaptor for your persistent objects
makes this quite simple: you page properties can all look like the
full persistent objects, but Tapestry will only store the ID between
requests.
It sounds like your problems are cause by having a broken persistent
object dangling from your page. Solution? Don't attach it to the
page! Throw it away when the request is done. Let your listeners
fetch it again clean next time.
(2) On the heels of (1), always roll back on error. You can do this
in your transaction interceptor or servlet filter; it's also possible
to do it in validator / listener methods. A mix of both may be
appropriate.
Sounds like you may already be doing this -- but when combined with
(1), you know that your DB and your objects are clean after a
failure, and only the page itself remains messy.
(3) Make your domain objects "fail fast:" enforce constraints up
front, and when possible, don't let objects get into an invalid state
to begin with. In your case, that means not storing the PK back to
the page until the create has succeeded.
(4) To make (3) really work, ensure your domain objects observe
strict failure atomicity. See _Effective Java_ for an explanation.
(Order a copy if you don't own it! It's a marvelous book.)
I hope that is helpful.
Cheers,
Paul
* In some situations, holding persistent objects between requests is
the right approach. However, I believe these situations are rare. The
pitfalls of this approach are large, and the performance benefits are
often overstated (especially if you use caching).
On May 2, 2006, at 5:42 PM, Jim Steinberger wrote:
In Tapestry, we use model objects as the data-buffer. If we're
editing an entity, and something goes wrong, we can set an
errorMessage
property, "return this;", and expect the user to see the form still
filled-out with what they'd just typed with an explanation of what
went
wrong.
If you're using Hibernate, however, I don't think this use-case is
guaranteed; mainly because I'm currently dealing with cases where it
isn't.
What I mean by that: If something goes wrong in a Hibernate
session,
your entities are not guaranteed to be left in a stable state. i.e.
they're not guaranteed to match their counterparts in the persistence
layer.
In my case, I'm trying to insert an object with some not-null
properties left as null. Hibernate sets the primary key of my object
(when it queries my Postgres sequence), but then fails on the actual
entity-insert. An exception is thrown, but the primary key is
left on
my object. Since my Tapestry page is designed to handle both
creating
and updating this entity -- and since it does this by testing
whether or
not an object's primary key exists -- the user is told there was a
problem, but as far as Tapestry is concerned, the user is now editing
the entity, not creating it.
I can hack it by explicitly setting the primary key to be null
where I
catch the Exception, but who knows what else Hibernate might be
mutating
without going through the source code.
Am I crazy, or does this Hibernate practice mean that,
everywhere we
expect to be able to use an object even if something goes wrong in
Hibernate, we have to clone our entity first or open ourselves up to
potential problems? Are people doing this?
Jim
_________________________________________________________________
Piano music podcast: http://inthehands.com
Other interesting stuff: http://innig.net
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
_________________________________________________________________
Piano music podcast: http://inthehands.com
Other interesting stuff: http://innig.net
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]