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.

1 comment:

amy said...

hi derek! this is amy (one of debbie's old friends) and i've been trying to figure out how to find the two of you for a while (sent a christmas card and it was sent back!) . the wonders of google have thus lead to your very confusing webpage. does debbie have a new email address? her wustl hasn't worked for ages! :( hope all is well!!!