2

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?

pkaeding
  • 36,513
  • 30
  • 103
  • 141

1 Answers1

1

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).

thoredge
  • 12,237
  • 1
  • 40
  • 55
  • Hmm, maybe my example was too simplified. In reality, I am not trying the example from the squeryl docs, but my code is not much more complicated (the only difference in structure is that mine has more columns). I get an instance of my objects through a call to a function in a companion object, and that function does use a transaction, but that seems fine to me, from an encapsulation standpoint, since the companion object is linked to the class that is backed by the DB. My application actually doesn't have any user interaction (except that a user starts it). It just generates reports. – pkaeding Jan 29 '11 at 04:40
  • Since you're creating a report I assume you're not doing any writes. There should not be any harm in packing the whole operation into the transaction. – thoredge Jan 29 '11 at 08:25