简体   繁体   English

在 Scala + JDBC 中避免可变变量

[英]Avoiding mutable variables in Scala + JDBC

I have the following code in Scala to access a database with JDBC.我在 Scala 中有以下代码来使用 JDBC 访问数据库。 It works fine, however it uses several mutable variables ( var declarations) because these need to be available in the finally clause for the JDBC elements to be closed.它工作正常,但是它使用了几个可变变量( var声明),因为这些变量需要在finally子句中可用才能关闭 JDBC 元素。 Can this code be changed to use only immutable variables ( val declarations)?可以将此代码更改为仅使用不可变变量( val声明)吗?

var connection:Connection = null
var statement:Statement = null
var resultSet:ResultSet = null

try {
  val url = "someUrl"
  val user = "someUser"
  val password = "somePwd"
  Class.forName("mySql")
  connection = DriverManager.getConnection(url, user, password)
  statement = connection.createStatement()
  resultSet = statement.executeQuery("SELECT column FROM table")
  while ( resultSet.next() ) {
    val col = resultSet.getString(1)
    println(col)
  }

} 
catch {
  case e:Exception => e.printStackTrace
}
finally {
  if (resultSet != null) resultSet.close
  if (statement != null) statement.close
  if (connection != null) connection.close
}
  • You can create small function to return connection.您可以创建小函数来返回连接。
  • You don't have to declare variables at the top(eg. connection , statement , resultSet ) when you don't have values for them.当您没有变量值时,您不必在顶部声明变量(例如connectionstatementresultSet )。 Instead you can create functions to return actual value as Option[T]相反,您可以创建函数以将实际值返回为Option[T]

  • Or since you need to have reference to connection , statement etc to close them, this answer has nice explanation on closing resources.或者因为您需要参考connectionstatement等来关闭它们,这个答案对关闭资源有很好的解释 I'm stealing the same code to use here.我正在窃取相同的代码在这里使用。 see the function autoClean which takes the resource you want to work on and the code to operate after which resource will be cleaned.请参阅autoClean函数,该函数获取您要处理的资源,以及在清理资源后进行操作的代码。

  • Also see immutable way of collecting resultSet data.另请参阅收集resultSet数据的不可变方式。

So, your would look like,所以,你会看起来像,

import java.sql.{Connection, DriverManager}

object AutoCleanJdbcConnection {

  val url = "someUrl"
  val user = "someUser"
  val password = "somePwd"

  def queryDb(): Option[Seq[String]] = {

    autoClean(dbConnection) { connection =>

      autoClean(connection.createStatement()) { statement =>

        autoClean(statement.executeQuery("SELECT column FROM table")) { result =>

          new Iterator[String] {
            def hasNext: Boolean = result.next()

            def next(): String = result.getString(1)
          }.toSeq

        }
      }
    }.flatten.flatten

  }

  def dbConnection: Connection = {
    Class.forName("mySql")
    DriverManager.getConnection(url, user, password)
  }

  def main(args: Array[String]): Unit = {
    queryDb().foreach { data =>
      println(data)
    }
  }

  def autoClean[A <: AutoCloseable, B](resource: A)(ops: A => B): Option[B] = cleanly(resource)(_.close())(ops)

  /**
    * can be returning Either[B, Throwable]
    */
  def cleanly[A, B](resource: => A)(resourceCleanupCode: A => Unit)(operationCode: A => B): Option[B] = {
    try {
      val res = resource
      try {
        Some(operationCode(res))
      }
      finally {
        resourceCleanupCode(res)
      }
    } catch {
      case e: Exception => None
    }
  }
}

NOTE: The code is compiled but I did not run this against database.注意:代码已编译,但我没有针对数据库运行它。

Here's a relatively straight translation of what you've got into something a little more FP.这是将您所获得的内容相对直接地翻译成更多 FP 的内容。 Type annotations added for clarity.为清晰起见添加了类型注释。 (I haven't run this, but it does compile.) (我还没有运行这个,但它确实编译。)

import scala.util.Try

val connection:Try[Connection]= Try(DriverManager.getConnection(url, user, password))
val statement: Try[Statement] = connection.map(_.createStatement())
val resultSet: Try[ResultSet] = statement.map(_.executeQuery("SELECT column FROM table"))

resultSet.map(rs => while (rs.next()) println(rs.getString(1)))  
         .recover{case e => e.printStackTrace()}

resultSet.foreach(_.close())
statement.foreach(_.close())
connection.foreach(_.close())

The idea is to avoid var s by making the failure condition part of the variable's type.这个想法是通过将失败条件作为变量类型的一部分来避免var s。

In this case you won't attempt to createStatement() if the getConnection() failed, you won't attempt to executeQuery() if the createStatement() failed, and you'll close() only those resources that didn't fail.在这种情况下,你不会尝试createStatement()如果getConnection()失败了,你会不会尝试executeQuery()如果createStatement()失败,你会close()只有那些资源没有失败.

Here is an approach that is based on Scala's Try and Stream classes.这是一种基于 Scala 的 Try 和 Stream 类的方法。 It is built on top of jwvh's answer by adding exception handling to ensure that all resources get closed even in an error scenario.它建立在 jwvh 的答案之上,通过添加异常处理来确保即使在错误情况下也能关闭所有资源。

This approach avoids var's by capturing the exceptions into Scala's Try abstraction and it avoids the while loop by converting the JDBC row handling into a Scala collection.这种方法通过将异常捕获到 Scala 的 Try 抽象中来避免 var ,并且通过将 JDBC 行处理转换为 Scala 集合来避免 while 循环。

Try can hold two states, a successful value or an exception. Try 可以保存两种状态,成功值或异常。 tryInstance.map will only be invoked if the value of tryInstance holds a successful value. tryInstance.map 仅在 tryInstance 的值持有成功值时才会被调用。

Scala Stream's are lazy, that is they calculate one value at a time and delay calculation of the next value. Scala Stream 是惰性的,即它们一次计算一个值并延迟计算下一个值。 This lets us move the while loop into the Scala collection libraries at the cost of having a little extra logic to detect the end of the stream and thus close the resources at the correct time.这让我们可以将 while 循环移动到 Scala 集合库中,但代价是需要一些额外的逻辑来检测流的结尾,从而在正确的时间关闭资源。

NB this code has not been tested, however I have successfully used this strategy in production code.注意此代码尚未经过测试,但我已成功在生产代码中使用此策略。 I include the following code here for illustrative purposes.出于说明目的,我在此处包含以下代码。

import Stream._

Class.forName("mySql")

def doQuery( url:String, user:String, password:String ): List[String] = {
  val conn      = Try(DriverManager.getConnection(url, user, password))
  val statement = conn.map( _.createStatement() )
  val rs        = statement.map( _.executeQuery("SQL") )

  toStream(rs,statement,conn).toList
}

private def toStream(rs:Try[ResultSet], s:Try[Statement], c:Try[Connection]) : Stream[String] = {
  try {
    val resultSet = rs.get // will throw an exception if any previous problems had occurred while opening the connection and parsing the sql

    if ( resultSet.next() ) {
      resultSet.getString(1) #:: toStream(rs,s,c)
    } else {
      close(rs,s,c)

      Stream.empty
    }
  } catch {
    case ex =>
      close(rs,s,c)
      throw ex
  }
}

I see that there is a lot of boiler plate code like getting a connection, closing a connection, handling failures!我看到有很多样板代码,比如获取连接、关闭连接、处理故障! This is how Java should be done, but you want to write Scala. Java 应该这样做,但是您想编写 Scala。 So if not using JDBC library directly is an option, you could try using some mapping libraries which does this boiler plate for you.因此,如果不直接使用 JDBC 库是一种选择,您可以尝试使用一些为您做这个样板的映射库。

For example., you could have a look at Slick which helps you writing database abstractions with keeping functional paradigm intact!例如,您可以查看 Slick,它可以帮助您编写数据库抽象,同时保持功能范式完整无缺!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM