Anorm隐式连接样板

Anorm implicit connection boilerplate

有没有一种简单的方法可以摆脱异常的隐式连接样板文件?

我有一个数据库对象:

import java.sql.Connection
import scalikejdbc.ConnectionPool

object DB {
  def withConnection[A](block: Connection => A): A = {
    val connection: Connection = ConnectionPool.borrow()

    try {
      block(connection)
    } finally {
      connection.close()
    }
  }
}

然后所有查询都必须包含在这个模式中

def myMethod(par1: Int, par2: String): Seq[MyClass] = {
    DB.run SQL("SELECT a,b,c FROM table WHERE foo={par1} AND bar={par2}")
        .on('par1=par1, 'par2=par2)
        .as(MyClass.myRowParser *)
}

在 DB 上有一个方法可以用这个 implicit connection 取消函数 Connection => A 的需要,这样我就可以简单地写:

def myMethod(par1: Int, par2: String): Seq[MyClass] = {
    DB.run SQL("SELECT a,b,c FROM table WHERE foo={par1} AND bar={par2}")
        .on('par1=par1, 'par2=par2)
        .as(MyClass.myRowParser *)
}

有没有办法轻松做到这一点?

取决于你想深入兔子洞多远...在我的一些 DAO 代码中,我在 SQL 操作上定义了一个 Free monad,使用类似这样的东西...首先定义一个case class 包装了一个通用函数,该函数接受一个连接到 A:

final case class SQLOperation[A](block: Connection ⇒ A)

然后,为它定义一个函子:

implicit object SQLOperationFunctor extends Functor[SQLOperation] {
    def map[A, B](a: SQLOperation[A])(f: A ⇒ B) = SQLOperation[B]((c:Connection) ⇒ f(a.block(c)))
}

然后,为它定义一个 Free monadic 类型(你可以使用 Scalaz Free 类型 class,只要你定义了一个仿函数):

type FreeSQLOperation[A] = Free[SQLOperation, A]

为了让事情更简单,您可以定义一个隐式的提升操作到自由 monad 中:

implicit def liftSQLOperationToFree[A](op : SQLOperation[A]) : FreeSQLOperation[A] = {
    Free.liftF(op)
}

一旦你定义了所有的机制,你就需要为 Free monadic 动作定义一个解释器,像这样的东西为你的执行提供了一个命令和控制的中心点:

final def run[A](t : ⇒ FreeSQLOperation[A]) : SQLResult[A]  = t.fold(
    (a: A) ⇒  a.right,
    (op : SQLOperation[FreeSQLOperation[A]]) ⇒ {
        DB.withConnection { implicit c ⇒
            val t= Try(run(op.block(c)))
            t match {
                case scala.util.Success(a) ⇒  a
                case scala.util.Failure(ex) ⇒  ex.getMessage.left
            }
        }
    }
)

在这种情况下,所有内容都包含在 Try 中,以便集中处理异常,然后映射到验证或析取类型。 (这就是 SQL 结果类型...)

一旦你把所有这些都放在一起,在你的 DAO classes 中,你就可以添加一些像这样的通用方法,例如:

def selectAll(params : ⇒ Seq[NamedParameter]) : FreeSQLOperation[List[V]] = {
    val clause = params.map(p ⇒ s"${p.name} = {${p.name}}").mkString(" and ")
    SQLOperation( implicit c ⇒
                      SQL(s"""
                             | select * from $source
                             |    where $clause
            """.stripMargin).on(params : _*).as(rowParser *).flatten
    )
}

或者这个:

def insert(params : ⇒ Seq[NamedParameter]) : FreeSQLOperation[Int] = {
    val columns = params.map(p ⇒ s"${p.name}").mkString(",")
    val values = params.map(p ⇒ s"{${p.name}}").mkString(",")
    SQLOperation( implicit c ⇒
        SQL(s"""
             | insert into $source ($columns)
             | values ($values)
             """.stripMargin).on(params : _*).executeInsert(scalar[Int].single)
    )
}

您可以将其放入基数 class。将 运行 方法添加到基础 class(解释器位),然后在更高级别的 DAO 对象中,您可以编写基本上如下所示的代码:

override def read(key: String): SQLResult[Principal] = {
    run {
        selectOne {
            Seq("uid" → key)
        }
   }
}

或者这个,在这里你可以看到 Free monad 如何允许你将操作链接在一起:

run {
        for {
            m1 ← updateByUid(uid){Seq("uid" → value.uid)}
            m2 ← updateByUid(value.uid){Seq("secret" → value.secret)}
            m3 ← updateByUid(value.uid){Seq("modified" → DateTime.now)}
            p ← selectOne { Seq("uid" → value.uid) }
        } yield (p)
    }

设置东西需要做很多繁琐的工作,但是一旦你完成了,你的实际业务逻辑就会变得更加清晰、整洁,这也意味着你可以换一个不同的解释器(定义运行 函数)如果你想采用不同的策略来处理异常、日志记录等...

HTH