Monday, March 31, 2008

JPA From Lift

This has been discussed a little on the lift mailing list so I thought I'd write something up. Lift has a nice, built-in mapper framework for doing database-object mapping, but it's geared mainly toward small, lightweight schemas. When you start to throw a lot of tables at it, or if you need to do legacy or out-of-the-mainstream work, you can start to run into limitations or concept mistmatches that can end up making more sophisticated (and heavyweight) frameworks look more attractive. In my case I have an existing Java project that has its own persistence layer using JPA (Hibernate EntityManager), so it really seemed like it would be best to reuse the existing code rather than trying to forklift the entire thing.

To start with, accessing JPA entities from Scala is really no different that accessing them from Java. In either case you're getting a JPA EntityManager interface from somewhere (JNDI in my case) and using the persist, merge, etc. methods to control object persistence management. There are, however, a few things you can do with Scala to make it a little simpler to use and more Scala-ish.

The first issue I came to was that all of my JPA entities use Java collections for their collection interfaces. In my case they're all java.util.Sets, but it's nicer to have a Scala set to get access to all of the nice methods like exists, toList, etc. For that, I start out with an implicit definition:


implicit def utilSetToSet[A] (s : java.util.Set[A]) =
new SetWrapper[A] { override def underlying = s}


That way, when I access a set member of a given entity, I can automatically use it as a Scala set.

The next thing I've added to make things a little easier is to create a "using" method to automatically handle closing the entity manager and handling any exceptions thrown:


def usingEM[A](f : EntityManager => Can[A]) : Can[A] = {
val em = emf.createEntityManager()

try {
f(em)
} catch {
case e: Exception => Failure(e.getMessage(), Full(e), Nil)
} finally {
em.close()
}
}


I've been vacillating on this particular method, since in the past I usually have used a filter to open an EntityManager and then close it at the end. Doing it this way allows me to better handle errors via Scala's nice pattern matching, but it also makes the code more "baroque":


object entityName extends RequestVar[Can[String]](Empty)
def view (xhtml : NodeSeq) : NodeSeq = usingEM({em =>
val myEntity = em.createNamedQuery("findEntityByName")
.setParamater("name", entityName.is openOr "non-existant")
.getSingleResult()

Full(bind("me", xhtml, // blah blah))
}) match {
case Failure(_, e : NoResultException, _) => S.error("Could not find entity by name")
case Failure(msg, exception, _) => log(msg, exception); S.error("Error retrieving entity"); Text("")
case Full(x) => x
}


I'm not convinced that this is the right way to do this so I'm open to suggestions.

Derek

No comments: