Anorm隐式连接样板

时间:2015-07-30 19:16:08

标签: scala boilerplate anorm

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

我有一个DB对象:

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上使用这个Connection => A取消函数implicit connection的方法会很好,所以我可以写简单:

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 *)
}

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

1 个答案:

答案 0 :(得分:1)

取决于你想要去兔洞的距离......在我的一些DAO代码中,我使用类似的东西定义一个免费的monad over SQL操作...首先定义一个包装泛型的case类连接到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)))
}

然后,为它定义一个免费的monadic类型(你可以使用Scalaz Free类型,只要你定义了一个函子):

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

为了让事情变得简单,您可以直接定义一个隐含的提升操作到Free monad:

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

一旦你完成了所有这些机制的定义,你就需要为自由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中,以便集中处理异常,然后映射到Validation或Disjunction类型。 (这就是SQLResult类型......)

一旦你把所有这些打到一起,在你的DAO课程中,你就可以添加一些这样的通用方法,例如:

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

您可以放在基类中。将run方法添加到基类(解释器位),然后在更高级别的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