Re: Cayenne vs Hibernate Comparison

From: Robert Zeigler (robert.zeigle..oxanemy.com)
Date: Mon Sep 06 2010 - 15:06:50 UTC

  • Next message: Joe Baldwin: "Re: Cayenne vs Hibernate Comparison"

    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
    >



    This archive was generated by hypermail 2.0.0 : Mon Sep 06 2010 - 15:07:44 UTC