Hibernate and Scala

Nov 12, 2007 22:55

So I spent most of the day wrestling with Hibernate and Scala. I was going to title this "Hibernate Annoyances," but that's a bit unfair. Generally, Hibernate and Scala work together very easily. But there are a couple of problems, and a bit of redemption at the end.


First, something quite trivial: Hibernate really wants collections to be declared as java.util interfaces, but of course I'd rather use the Scala collections. So I figured I'd just wrap them, but scala.collection.jcl, as far as I can tell, doesn't include wrappers or converters for the interfaces, only for the concrete classes. I can deal with this, but it's a bit irritating.

The main problem, however, has to do with Hibernate itself. I'm trying pretty hard to avoid static references of any kind. To my mind, many of the most exciting advantages of Scala lie in this area. But Hibernate really doesn't like this. As far as I can tell, it's almost impossible to get any sort of non-static context into entities without resorting to ugly thread-local hacks, etc.

I use the following pattern to avoid static references (this is basically the Cake Pattern, modernized a bit). For each entity type, say Project, I have a class ProjectImpl:

@MappedSuperclass
abstract class ProjectImpl {
val context: Context
import context._
// ... all detailed Hibernate annotations go here.
}
Then I have a single trait, Models, defined like:

trait Models { self: Context =>
@Entity { val name = "..." }
class Project extends ProjectImpl {
val context: self.type = self
}
...
}
At the top level I compose Models with a number of other similar "packaging" traits and produce a final composition. This works great. It allows me to define classes in separate files when convenient, avoid static references of any kind, but still retain the convenience of static references. I have a global context available everywhere I want it, and I don't have to pass it around. This is a huge win.

Unfortunately, Hibernate hates it. The concrete classes being mapped are non-static inner classes, which of course have no zero-arg constructor. Busted. Hibernate apparently has no way of passing in an entity factory at query time. Of all the options I can set (UserType, Tuplizer, Instantiator, ad nauseam), every single one of them must be defined by class-name in an annotation. This means that Hibernate queries are required to be basically context-insensitive. Of course, I can stick some context in a thread-local and use one of these options to instantiate my entities, but this seemed awfully ugly to me, and I'm really not sure whether it would be OK with Hibernate to make entities sensitive on context in this way. This assumption seems to be baked pretty deeply into the design.

So I threw in the towel and made the entity classes "regular POJOs" (now there's a word I can't stand). I didn't really need the context reference anyway, although I was afraid this would come back to haunt me. It came back quicker than I expected, in the form of a bunch of LazyInitializationExceptions. And in order to initialize my lazy collections, I need a reference to the Hibernate SessionFactory in the entity, but of course this is exactly what Hibernate didn't let me do in the first place.

Scala to the rescue. I've left my entities as stand-alone POJOs, and to capture the fact that certain methods are sensitive to a context, I've added an implicit parameter to those methods that need to perform lazy initialization:

def addPage(page: Page)(implicit context: Context) { ... }
There are only a few of these. In the client code, I can just declare my context reference implicit. I really only ever have one of them at a time, except in a few very special cases where I wouldn't want any implicit handling anyway. This works like a charm. The client does have to be aware that certain entity methods are context-sensitive, but beyond that, any embedded transactions are completely transparent. (And of course I could make the notion of context more granular if I wanted to.)

The only real downside to this is that it doesn't enforce that entities are used only in the context in which they were created. I'd love to find a way to solve this. I think it might be possible to recover this by parameterizing the entity types, binding the type parameter with a single unsafe-but-safe cast as they come out of the database, and then enforcing that the implicit Context parameter matches the type parameter. I'll have to experiment with this, although truth be told, I'm not sure this level of safety is worth the hassle.

I don't know Hibernate well at all, and there might be other ways to solve this. If anyone knows a good, clean solution that doesn't rely on static references (or crypto-statics like JNDI) and that doesn't involve thread-local storage, I would love to hear it. In any case, this shows how important it is to really pay attention to global resources, and how much clearer things can be when you make this stuff explicit. It also shows that Scala rules, and can be used with Hibernate very easily (well, easily modulo my own obsession with eliminating global state).

rant, computing

Previous post Next post
Up