How can I encapsulate the session/transaction acquisition into the lazy-init of relations in Squeryl?

561 views Asked by At

I am trying to implement a One-To-Many relation using Squeryl, and following the instructions on their site.

The documentation gives the following example:

object SchoolDb extends Schema {    
  val courses = table[Course]    
  val subjects = table[Subject]      
  val subjectToCourses =
    oneToManyRelation(subjects, courses).
    via((s,c) => s.id === c.subjectId)  
}

class Course(val subjectId: Long) extends SchoolDb2Object {    
  lazy val subject: ManyToOne[Subject] = SchoolDb.subjectToCourses.right(this)
}

class Subject(val name: String) extends SchoolDb2Object {    
  lazy val courses: OneToMany[Course] = SchoolDb.subjectToCourses.left(this)
}

I find that any calls to Course.subject or Subject.courses needs to be wrapped in a transaction. However, One of my goals in using an ORM is to hide these details from callers. As such, I don't want the calling code to have to wrap a call to these fields in a transaction.

It seems that if I modify the example to wrap the lazy init function in a transaction, like so:

class Subject(val name: String) extends SchoolDb2Object {    
  lazy val courses: OneToMany[Course] = {
    inTransaction {
      SchoolDb.subjectToCourses.left(this)
    }
}

I get the following exception:

Exception in thread "main" java.lang.RuntimeException: no session is bound to current thread, a session must be created via Session.create 
and bound to the thread via 'work' or 'bindToCurrentThread'
    at scala.Predef$.error(Predef.scala:58)
    at org.squeryl.Session$$anonfun$currentSession$1.apply(Session.scala:111)
    at org.squeryl.Session$$anonfun$currentSession$1.apply(Session.scala:111)
    at scala.Option.getOrElse(Option.scala:104)
    at org.squeryl.Session$.currentSession(Session.scala:110)
    at org.squeryl.dsl.AbstractQuery.org$squeryl$dsl$AbstractQuery$$_dbAdapter(AbstractQuery.scala:116)
    at org.squeryl.dsl.AbstractQuery$$anon$1.<init>(AbstractQuery.scala:120)
    at org.squeryl.dsl.AbstractQuery.iterator(AbstractQuery.scala:118)
    at org.squeryl.dsl.DelegateQuery.iterator(DelegateQuery.scala:9)

But, like I said, if I wrap the caller in a transaction, then everything works.

So, how can I encapsulate the fact that this object is backed by a database in the object itself?

1

There are 1 answers

2
thoredge On

I assume you get this error in calls on the courses object?

I don't know very much about how Squeryl works, but I believe that the OneToMany[Course] is a live object. That means that the calls on the courses object need a session since any call may lazily go to the database to fetch data.

How you organise this depends on what type of application you use. In a web application it often makes sense to add a filter (first point of entry) to start and stop the transaction. In a GUI client, say a swing application, it's a good solution to start the transaction at the point where you receive the user interaction. That way you get transactions that are not to long and also stretches over calls which you expect to be performed atomically (either fully or not at all).