Thursday, April 3, 2008

More progress on JPA

I've refactored my code now that I have a request-per-session. So far, this is what the Model object looks like:


object Model extends Logger {
def logname = "Model"

lazy val factory = Persistence.createEntityManagerFactory("foo")

// Per-request entity manager
//object emVar extends RequestVar[EntityManager](null)
//def em = emVar.is

// Temporarily using ThreadLocal until we get lifecycle handling in RequestVar
val emVar = new ThreadLocal[EntityManager]
def em = emVar.get()

/**
This method allows me to clean up my code a bit and only handle JPA-related exceptions.
An example usage would be:

def addFood(newFood : Food) =
wrapEM({
em.persist(newFood)
S.redirectTo("/food/list.html")
}, {
case cve : ConstraintViolationException => S.error("That food already exists!")
case _ => S.error("Internal error adding food")
})

Note that if I used normal try/catch then the wildcard match would trap the RedirectException
thrown by S.redirectTo.
*/
def wrapEM[A](f : => A, handler : PartialFunction[Throwable, A]) : A = {
try {
val tx = em.getTransaction()

tx.begin()
try {
val ret : A = f
ret
} finally {
// make sure that we commit even with a redirectexception
tx.commit()
}
} catch {
// Special case. Usually we want to know why it failed to commit, not just that it failed
case re : RollbackException => {
val (cause,message) = if (re.getCause() == null) {
(re,"No cause")
} else {
(re.getCause(), re.getCause().getMessage())
}
this.error("EM Commit error: {}", message)
handler(cause)
}
case he : HibernateException => {
this.error("Hibernate error", he)
handler(he)
}
case pe : PersistenceException => {
this.error("EM Error", pe)
handler(pe)
}
}
}

/**
Bridging utility method. This should eventually go away.
*/

def usingEM[A](f: EntityManager => Can[A]) : Can[A] = wrapEM(
{f(em)}, {
case e : Exception => Failure(e.getMessage(), Full(e), Nil)
})

/**
Loads the given object and returns a Full can with the object if it could be loaded, Empty if it's not found.
*/
def load[A](id : java.lang.Long, clazz : Class[A]) : Can[A] = load(this.em, id, clazz)
def load[A](em : EntityManager, id : java.lang.Long, clazz : Class[A]) : Can[A] = em.find(clazz, id) match {
case found : A => Full(found)
case null => Empty
}

/**
Queries for a single instance of an object based on the queryName and params.

@return Full(x) if a single result is returned, Empty if no results are returned, Failure on any errors

*/
def find[A](providedEM : EntityManager, queryName : String, params : Pair[String,Any]*) : Can[A] = {
try {
val query = providedEM.createNamedQuery(queryName)

params foreach { param => query.setParameter(param._1, param._2) }

Full(query.getSingleResult().asInstanceOf[A])
} catch {
case e: NoResultException => Empty
}
}

/**
Queries for a result list of objects based on the queryName and params

@return A scala.collection.jcl.Buffer of results
*/
def findAll[A](em : EntityManager, queryName : String, params : Pair[String,Any]*) : Buffer[A] = {
val query = em.createNamedQuery(queryName)

params foreach { param => query.setParameter(param._1, param._2) }

val result = query.getResultList().asInstanceOf[java.util.List[A]]

new BufferWrapper[A] { override def underlying = result }
}

// Implicit defs to help with entity member access
implicit def setToWrapper[A](set : java.util.Set[A]) = new SetWrapper[A]{override def underlying = set}
implicit def listToWrapper[A](list : java.util.List[A]) = new BufferWrapper[A]{override def underlying = list}
}


I just found out that I can do JNDI and JTA in Jetty, so I might take a crack at getting that working once I've ripped out all of the old JPA access code in my various classes.

Wednesday, April 2, 2008

Evolution of JPA from Lift

Edit: Forgot that I could use RequestVar. Also, Look at my next post for a further evolution of the Model object

Well, with some suggestions and info from David and Viktor I've started overhauling my use of JPA from within Lift. First and foremost, I've added in support for an EntityManager-per-request. This requires a little setup and utility class in my case:


object Model {
def createEM() = // fill this in however you want: JNDI, local Persistence, etc

// functions for setting a per-request entity manager
object emVar extends RequestVar[EntityManager](null)
def em = emVar.is

def wrapEM(f : => Unit, msg : String) : Unit = {
try {
f
} catch {
case e : Exception => this.error("Unexpected exception", e); S.error(msg)
}
}
}


The wrapEM method is something I've put together to make my code a little more concise when all I want to do is display an error when something goes wrong. The real meat of EM-per-session happens in Boot:


// Set up a LoanWrapper to automatically instantiate and tear down the EntityManager on a per-request basis
S.addAround(List(
new LoanWrapper {
def apply[T] (f : => T): T = {
val em = Model.createEM()

// Add EM into S scope
Model.emVar(em)

try {
f
} finally {
em.close()
}
}
}
))