Thursday 7 May 2009

Frakkin' Hibernate vol 1. "deleted entity passed to persist"

Well, I haven't posted to this blog in what seems like a decade. As usual - so much has happened in my personal life, but that is another story for another blog (or, at least, another blog post).

Today, I'm here to talk about Hibernate, some of the problems I've had, in the hope that writing them down, not only helps me think around the subject but also commits it to memory.

I've attempted to author O/R frameworks myself, in the form of a fast lane reader for a previous company, back in the days when decent and mature O/R frameworks were a rarity. That said, I like Hibernate - I appreciate how impossibly hard something like an O/R framework is to keep simple, and Hibernate does a pretty good job. I like it's principal where you simply treat the objects as normal objects, never expect anything automatic to have (like automatic persisting) and bingo - simple and elegant.

Doesn't stop it being a pain in the back-side.

For a start, some of the error messages could be more vague. If they tried, really hard. Maybe. It has a raft of quirks that aren't really acknowledged in error messages.

Finding solutions to errors can be a real pain. Mostly because with Hibernate and JPA, Annotations and XML configuration, Spring managed or not, different package configuration and versions - what ever error you have, it's very difficult to pinpoint and solve the specific issue.

Take the inspiration for this post; "deleted entity passed to persist" was the given error message. Like any other programmer in the world, you encounter a problem, bosh it into Google and search for those who'd have the problem before and try their solutions, which if they don't solve the problem will usually teach you something new. Now - try that - and search for it in Google (here), and witness the number of like mind people - then find one that actually has a solution. Yep! None!

Now the basic premise is this;

class Parent extends BaseDomainObject {
@OneToMany( mappedBy="child", cascade=CascadeType.ALL, fetch=FetchType.EAGER ) // bi-directional
private Set clubMatchRecords = new OrderedHashSet();
}

public class Child extends BaseDomainObject {

@ManyToOne( cascade=CascadeType.ALL, fetch=FetchType.EAGER ) // bi
@JoinColumn( name="parent_id" )
private Parent parent;
}

(Note : OrderedHashSet is simply a combination of Set and List, allowing an indexed set, BaseDomainObject is er... the base domain object. What can I say... I like obvious names)
(Note 2 : observe the use of the Standard Colin Notation (SCN) in the code... :) )

This sort of setup is prevailent in many tutorials (for example, here; http://mikedesjardins.us/wordpress/2008/01/new-jpa-tutorial-pizza-shop/)

The problem? This line...;

@ManyToOne( cascade=CascadeType.ALL, fetch=FetchType.EAGER ) // bi

Basically, with a delete, it tries to delete the parent, and marks it as deleted. It the then tried to delete the child, the child in turn cascades the delete to the parent and hence an error is thrown.

cacade = Parent -> Child -> Parent

I've got to admit, looking at it now, it seems so obvious, but when you've Object graphs 5 layers deep, all with multiple collections of children etc., it's very hard to actually spot what is going on. Still, lesson learnt.

One of the reasons I'm highlighting this as a factor as it affects the design stage rather than the code stage. The life-cycle of objects, how they created and destroyed becomes a design concern and really should be tackled by designers before coding.

This, along with another issue have been my biggest "gotcha" of Hibernate so far. The other issue? Again - it's another that requires an element of pre-design, and hence it's significance. It's down to lazy retrieval of Collections of a abstract classes. To fully access the children, have them form of the correct type of objects (i.e. have collections of Animal be of types Cat and Dog, rather than a complex Animal proxy type), for valid object comparisions, the fetch type needs to be EAGER. Hence the designer should take this into consideration when designing the object model.

Thinking about it - does it also break polymorphism...? It's been a while since I had to deal with this issue and now I'm curious! Hmmm...

It does work as a general rule of thumb, that if an object needs to cascade from parent to child, it should also be an eager fetch, and vice versa, if there no need for cascade or eager fetch, there is no need for the other (i.e. going back up the object stack).

Finally, hibernate does loads of great things but I'm struggling to find one feature - create or drop the database programmatically? I'd like to set up a test suite to drop, create, populate, run some tests and be able to repeat the process again for other test suites. The problem is that I can't find in the API of JPA or Hibernate programatical means to do this. We've got a fraking XML property we can set to do this once at start-up (or once of each and every test) and hence once during testing but flexibility.... I'm sure it's there somewhere but bugger I can find it! *bangs head on wall*

No comments: