Squeryl: Run query explicitly

别说谁变了你拦得住时间么 提交于 2019-12-05 13:17:37

The general problem that I see here is that you try to combine the following two ideas:

  • lazy computation of data; here: database results

  • hiding the need for a post-processing action that must be triggered when the computation is done; here: hiding from your controller or view that the database session must be closed

Since your computation is lazy and since you are not obliged to perform it to the very end (here: to iterate over the whole result set), there is no obvious hook that could trigger the post-processing step.

Your suggestion of invoking Query[T].toList does not exhibit this problem, since the computation is performed to the very end, and requesting the last element of the result set can be used as a trigger for closing the session.

That said, the best I could come up with is the following, which is an adaptation of the code inside org.squeryl.dsl.QueryDsl._using:

class IterableQuery[T](val q: Query[T]) extends Iterable[T] {
  private var lifeCycleState: Int = 0
  private var session: Session = null
  private var prevSession: Option[Session] = None

  def start() {
    assert(lifeCycleState == 0, "Queries may not be restarted.")
    lifeCycleState = 1

    /* Create a new session for this query. */
    session = SessionFactory.newSession

    /* Store and unbind a possibly existing session. */
    val prevSession = Session.currentSessionOption
    if(prevSession != None) prevSession.get.unbindFromCurrentThread

    /* Bind newly created session. */
    session.bindToCurrentThread
  }

  def iterator = {
    assert(lifeCycleState == 1, "Query is not active.")
    q.toStream.iterator
  }

  def stop() {
    assert(lifeCycleState == 1, "Query is not active.")
    lifeCycleState = 2

    /* Unbind session and close it. */
    session.unbindFromCurrentThread
    session.close

    /* Re-bind previous session, if it existed. */
    if(prevSession != None) prevSession.get.bindToCurrentThread
  }
}

Clients can use the query wrapper as follows:

var manualIt = new IterableQuery(booksQuery)
manualIt.start()
manualIt.foreach(println)
manualIt.stop()
//      manualIt.foreach(println) /* Fails, as expected */

manualIt = new IterableQuery(booksQuery) /* Queries can be reused */
manualIt.start()
manualIt.foreach(b => println("Book: " + b))
manualIt.stop()

The invocation of manualIt.start() could already be done when the object is created, i.e., inside the constructor of IterableQuery, or before the object is passed to the controller.

However, working with resources (files, database connections, etc.) in such a way is very fragile, because the post-processing is not triggered in case of exceptions. If you look at the implementation of org.squeryl.dsl.QueryDsl._using you will see a couple of try ... finally blocks that are missing from IterableQuery.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!