cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Joe Baldwin <jfbald...@earthlink.net>
Subject Re: Cayenne vs Hibernate Comparison
Date Mon, 06 Sep 2010 16:16:00 GMT
Robert,

All I can say is "wow", thanks for the insights.  This is especially important because you
use both frameworks.

Please let me ask some more questions.  (Note: as I said, I was initially attracted to Cayenne
because it had familiar design patterns to EOF, which I thought was fairly mature at the time,
so I may not understand the Hibernate-way of "thinking").

RE ObjectContext vs Session
I may be mixed up but it sounds like the ObjectContext is similar in concept to EOF.  It sounds
like you are saying that among other things the Hibernate-Session makes simple transactional
tasks much more difficult and may even interfere with a Factory-Method approach to building
data objects within a transaction.

RE Lazy Fetching
If I have the concept correct, this is another term for what EOF calls "faulting" behavior.
 IMO optimized/smart faulting behavior is the single most important reason to use an ORM.
 The conceptual differences between a RDBMS and an OO language can result in massive problems
with a macro design, one that a good ORM solves via intelligent faulting algorithms. I had
just assumed that this was a "moot" issue based on the fundamental solutions offered by EOF.
 Are there any essential differences in features of "Lazy Fetching" that you can point out?

RE dbentity/objentity differences

Is the reason associated with the maturity of the "faulting" behavior (or something else)?

RE Google "hibernate lazyinitializationexception" to see what I mean.

OK, I googled it as you suggested, and found a few (what I call dissertations) on the subject
that suggest that Hibernate does not have a cogent "faulting" design, and that the Hibernate-Session
is not as mature a model for transactions as is Cayenne's ObjectContext (if I understand the
issues).  Is this correct?

Correct me if I am wrong (please :) ), but it is starting to sound similar to the discussions
of C++ garbage collection vs Java garbage collection (i.e. C++ doesn't really embrace garbage
collection as a problem that should be handled by anyone but the programmer)

Thanks again for your input,
Joe






 







On Sep 6, 2010, at 11:06 AM, Robert Zeigler wrote:

> Hi Joe,
> 
> First, this e-mail wound up a lot longer than I intended, apologies! The short version
is: having used both a fair bit, I prefer cayenne, but they both have strengths and weaknesses.
Most of this e-mail details what I view as weaknesses in Hibernate. :)
> 
> On to the long version! 
> 
> I still know cayenne better than hibernate, but I've used both extensively (2+ years
of experience with hibernate, on a fairly large system with > 80 tables; I've used cayenne
for > 5 years now).  I don't have time right now to put together a systematic comparison,
but here are a few notes:
> 
> Hibernate: POJO - some people love it, some hate it. I'm in the latter camp.  You lose
out on a lot of code-reuse, debugging is more difficult, and it necessitates constructs like
hibernate proxies, which are a PITA to deal with, IMO.
> Cayenne: interface (inheritance, in practice)-based design.  Some people don't like the
domain-structure constraints it imposes.  I find it makes debugging easier and results in
more code re-use. And, no proxies.  Objects are what they are.  More importantly, objects
are what you think they are (in hibernate-land, I've had code like the following:
> 
> public class MyObject2 extends MyObject1 {...}
> 
> elsewhere:
> 
> MyObject1 someobj = someCodeThatReturnsMyObject1();
> if (MyObject2.class.isInstance(someobj)) {
>  ((MyObject2)someobj).callMyObject2Method();
> }
> 
> And the above code will throw a cast class exception.  Yup.  That's right.  Because someobj
is a proxy.  A call to getClass() returns the getClass() of the underlying object (an instance
of MyObject2), but someobj, the proxy, technically only implements MyObject1.  So you get
a ClassCastException.  Other variants include your code not executing at all, even when you
know that someobj /should/ be an instance of MyObject2. The code above is, of course, contrived,
but I've hit this in numerous "real world" scenarios.
> 
> Hibernate & Cayenne both support "lazy fetching" in some form, but Cayenne's support
is far superior, IMO (with bonafide faulting, etc.).  This is a function, I think, of having
supported it far longer (this was initially the reason that I used Cayenne rather than Hibernate).
 Google "hibernate lazyinitializationexception" to see what I mean.  In particular, if you
go the hibernate route, be very /very/ careful what you do in event listeners (pre/post commit,
etc.) because it's very easy to hit exceptions there.  Basically, I find that in Hibernate,
event listeners are just barely better than useless.  A great idea, but you can't really /do/
anything useful in them.  And even the 3rd party modules, written by long-time hibernate developers,
hit these edge cases (eg: hibernate envers for entity auditing/logging has had issues reported
where they hit issues with lazyinitializationexception from using lifecycle listeners).  The
hibernate "way" was originally to not support lazy fetching at all; either all of the data
you needed came into the request/session at once, or it wasn't there at all.  This probably
resulted in more /performant/ code (fewer queriest/hits to the db, for instance), but is basically
not feasible in the world of 3rd party modules/integration/development: it's not always possible
to know exactly what information you need at the beginning of, eg, a web request.
> 
> I have to give kudos to the hibernate team for the extremely flexible mappings they support.
 I find cayenne mapping more /intuitive/ (thanks in part to the modeler), but there are edge
mapping cases that are supported in hibernate that are not, to the best of my knowledge, supported
in cayenne (cayenne 3.0 improves this discrepancy, though).  As an example, hibernate supports
more inheritance modeling schemes (table per concrete subclass, table per class, single table)
than does cayenne, although cayenne 3.0 has improved in this regard.  For simple mapping,
hibernate may even be more straightforward than cayenne due to it's ability to analyze your
domain objects and figure out the appropriate tables, etc. to create.  On the other hand,
I personally shy away from having hibernate auto-create my table structure.  I find it results
in less thinking about what's really occurring at the db level.  Although that is, to a greater
or lesser extent, the point of an ORM system, it's my opinion that it's still important to
think about how the data is physically mapped at the db level.  (I should note that you can
specify the exact mapping characteristics in hibernate.  But my observation is that the tendency
is to let hibernate "do it's thing" until you find a problem with the way it did it's thing,
and you tell hibernate the "right way" to do it). 
> 
> Metadata: There's no "dbentity" vs. "objentity" separation.  That's great for some people...
but really too bad. :) My personal experience is that cayenne's meta-data support is more
accessible and richer than Hibernate's, but that's probably a function, at least in part,
of familiarity with the frameworks.  
> 
> pks: Cayenne's approach is: "these are a database-artifact and shouldn't pollute your
data model, unless you need them to be there".  Hibernate's approach is: "pk's are an integral
part of your domain object" (for the most part).
> 
> ObjectContext vs. Session.  Session is a poor man's ObjectContext. ;) That's an opinion,
of course.  But. On the surface, these two objects do similar sorts of things: save, commit
transactions, etc.  But in reality, they are completely different paradigms.  In Cayenne,
an ObjectContext is very much a "sandbox" where you can make changes, roll them back, commit
them, etc.  A hibernate session is more like a command queue: you instruct it to update, save,
or delete specific objects, ask it for "Criteria" for criteria-based queries, etc.  They may
sound similar but there's a big difference in how you use them.  Basically, hibernate doesn't
have the notion of "modified or new object that needs to be saved at some point in the future,
but which I should retain a reference to now." :) In cayenne, you can do something like this:
> 
> void someMethod(ObjectContext context) {
>   context.newObject(SomePersistentObject.class).setSomeProperty("foo");
>   ...
> }
> 
> Now when that particular context is committed, a new instance of SomePersistentObject
will be committed, without the calling code having to know about it.  Arguably, this is a
method witih "side effects" that should be avoided, but there are legitimate use cases for
this.  Consider a recent example I encountered.  A hibernate project I work on manages a set
of "projects".  Changes to projects are audited, except when the project is first created/in
state "project_created" (a custom flag, unrelated to hibernate).  I recently needed to add
support for one auditing operation: record the date of creation, and the user who created
the project.  WIthout getting into gory details, the simplest way to do this would have been
to modify the service responsible for creating all project types, along the lines of this
(how I would do this in cayenne):
> 
> public <T extends Project> T createProject(Class<T> type) {
>   T project = codeToCreateProject();
>   Audit a = objectContext.newObject(Audit.class);
>   a.setProject(project);
>   a.setMessage("Project Created");
>   a.setDate(new Date());
>   return project;
> }
> 
> Notes: the project creator is not (and cannot, due to design constraints) commit the
project to the database at this point in the code.  That's fine in cayenne: as long as the
calling code is using the same object context (it always would be in my case), the Audit object
would be committed at the same time the project is, and life would be happy.  But the project
is not cayenne. It is hibernate.  So:
> 
> public <T extends Project> T createProject(Class<T> type) {
>   T project = codeToCreateProject();
>   Audit a = new Audit();
>   a.setProject(project);
>   a.setMessage("Project Created");
>   a.setDate(new Date());
>   return project;
> }
> 
> Except, what happens to a? The answer is: nothing.  It isn't ever saved.  It would be,
if Project had a reverse relationship to audit (List<Audit> getAudits()), that was set
to cascade the "save" and "update" operations.
> But Project didn't/doesn't, and I wasn't allowed to add it.  There was no way to tell
hibernate: "Look, I've got this object, and I wan't you to save it, but, not right this second".
 You can call: session.save(a).  But that results in an immediate commit the audit object
(and ONLY the audit object!), so if the project isn't yet persisted to the db, you get a relationship
constraint violation, trying to save a relationship to an unsaved object.  There's also a
session.persist(a) method, part of EJB3 spec, which is theoretically like cayenne's "register",
but in hibernate, its functionally equivalent (or very nearly so) to session.save(a): it triggers
an immediate commit to the database (at least in our application setup).  There is no equivalent
to cayenne's "context.register(a)".  I finally solved this issue via life cycle event listeners,
and it was a pain (you have to be /extremely/ careful about what you do in hibernate event
listeners.  In particular, read operations that result in a hit to the database will cause
you major grief, even if you don't modify anything, and modification of any kind is next to
impossible).  
> 
> All that said, there are /some/ good ideas in hibernate. :)  For one thing, Cayenne's
/requirement/ that two objects with a shared relationship be in the same ObjectContext can
cause grief, particularly in web applications.  Imagine you have a form to create a new object
of type Foo.  Foo has a relationship to Bar.  You may not want to register this object with
the context until you know that the new Foo object is a "valid" object (lest you wind up with
"dirty" objects polluting subsequent commits, using an ObjectContext-per-user session paradigm).
 But you can't do that: when you set the Bar relationship, Foo will be registered with the
context.  That's usually fine... you can usually rollback the changes... but it does mean
sometimes having to think carefully about what "state" your objects are in.
> 
> I've yet to find the "perfect" ORM.  THere isn't one, as far as I'm concerned, b/c there's
simply a mismatch between the db model and the object model that will result in tradeoffs.
 But I find Cayenne far easier to learn and use than Hibernate.
> 
> Cheers,
> 
> Robert
> 
> On Sep 5, 2010, at 9/51:21 PM , Joe Baldwin wrote:
> 
>> Hi,
>> 
>> I am again responsible for making a cogent Cayenne vs Hibernate Comparison.  Before
I "reinvent the wheel" so-to speak with a new evaluation, I would like to find out if anyone
has done a recent and fair comparison/evaluation (and has published it).
>> 
>> When I initially performed my evaluation of the two, it seemed like a very easy decision.
 While Hibernate had been widely adopted (and was on a number of job listings), it seemed
like the core decision was made mostly because "everyone else was using it" (which I thought
was a bit thin).
>> 
>> I base my decision on the fact that Cayenne (at the time) supported enough of the
core ORM features that I needed, in addition to being very similar conceptually to NeXT EOF
(which was the first stable Enterprise-ready ORM implementations).  Cayenne seems to support
a more "agile" development model, while being as (or more) mature than EOF.  (In my opinion.
:) )
>> 
>> It seem like there is an explosion of standards, which appear to be driven by "camps"
of opinions on the best practices for accomplishing abstraction of persistence supporting
both native apps and highly distributed SOA's.
>> 
>> My vote is obviously for Cayenne, but I would definitely like to update my understanding
of the comparison.
>> 
>> Thanks,
>> Joe
>> 
> 


Mime
View raw message