# Default database configuration db.default.driver=org.h2.Driver db.default.url=jdbc:h2:mem:play
# Orders database db.orders.driver=org.h2.Driver db.orders.url=jdbc:h2:mem:orders # Customers database db.customers.driver=org.h2.Driver db.customers.url=jdbc:h2:mem:customers
val appDependencies = Seq( "mysql" % "mysql-connector -java" % "5.1.18" )
play.api.db 包提供了访问配置数据源的方法:
import play.api.db._ val ds = DB.getDatasource()
val connection = DB.getConnection()
DB.withConnection { conn => // do whatever you need with the connection }
DB.withTransaction { conn => // do whatever you need with the connection }
db.default.driver= com.mysql.jdbc.Driver db.default.url="jdbc:mysql://localhost/world" db.default.user=root db.default.password=secret
Play 有时候会提供预编译SQL statement, 但并不是想隐藏SQL的底层细节. Play 只想节约大段查询的打字时间,你完全可以返回来使用纯的SQL.
import anorm._ DB.withConnection { implicit c => val result: Boolean = SQL("Select 1").execute() }
val result: Int = SQL("delete from City where id = 99").executeUpdate()
val sqlQuery = SQL( """ select * from Country c join CountryLanguage l on l.CountryCode = c.Code where c.code = 'FRA'; """ )
SQL( """ select * from Country c join CountryLanguage l on l.CountryCode = c.Code where c.code = {countryCode}; """ ).on("countryCode" -> "FRA")
// Create an SQL query val selectCountries = SQL("Select * from Country") // Transform the resulting Stream[Row] as a List[(String,String)] val countries = selectCountries().map(row => row[String]("code") -> row[String]("name") ).toList
// First retrieve the first row val firstRow = SQL("Select count(*) as c from Country").apply().head // Next get the content of the 'c' column as Long val countryCount = firstRow[Long]("c")
case class SmallCountry(name:String) case class BigCountry(name:String) case class France val countries = SQL("Select name,population from Country")().collect { case Row("France", _) => France() case Row(name:String, pop:Int) if(pop > 1000000) => BigCountry(name) case Row(name:String, _) => SmallCountry(name) }
注意,既然 collect(...) 会忽略未定义函数,那它就允许你的代码安全的那些你不期望的行.
SQL("Select name,indepYear from Country")().collect { case Row(name:String, Some(year:Int)) => name -> year }
SQL("Select name,indepYear from Country")().map { row => row[String]("name") -> row[Int]("indepYear") }
SQL("Select name,indepYear from Country")().map { row => row[String]("name") -> row[Option[Int]]("indepYear") }
对于parser API也是同样的情况,接下来会看到。
val rowParser = scalar[Long]
val rsParser = scalar[Long].single
val count: Long = SQL("select count(*) from Country").as(scalar[Long].single)
val populations:List[String~Int] = { SQL("select * from Country").as( str("name") ~ int("population") * ) }
val result:List[String~Int] = { SQL("select * from Country").as(get[String]("name")~get[Int]("population")*) }
那么,关于String~Int类型呢?它一个 Anorm 类型,不能在你的数据访问层外使用.
str("name") ~ int("population") map { case n~p => (n,p) }注意:我们在这里创建了一个 tuple (String,Int),但没人能阻止你RowParser转成其它的类型,例如自定义case class。
val result:List[(String,Int)] = { SQL("select * from Country").as( str("name") ~ int("population") map(flatten) * ) }
接下来,让我们创建一个更复杂的例子。怎样创建下面的查询, 使得可以获取国家名和所有的国所使用的语言记录呢?
select c.name, l.language from Country c join CountryLanguage l on l.CountryCode = c.Code where c.code = 'FRA'
var p: ResultSetParser[List[(String,String)] = { str("name") ~ str("language") map(flatten) * }
现在我们得到以下类型的结果:
List( ("France", "Arabic"), ("France", "French"), ("France", "Italian"), ("France", "Portuguese"), ("France", "Spanish"), ("France", "Turkish") )
case class SpokenLanguages(country:String, languages:Seq[String]) languages.headOption.map { f => SpokenLanguages(f._1, languages.map(_._2)) }
最后,我们得到了下面这个适用的函数:
case class SpokenLanguages(country:String, languages:Seq[String]) def spokenLanguages(countryCode: String): Option[SpokenLanguages] = { val languages: List[(String, String)] = SQL( """ select c.name, l.language from Country c join CountryLanguage l on l.CountryCode = c.Code where c.code = {code}; """ ) .on("code" -> countryCode) .as(str("name") ~ str("language") map(flatten) *) languages.headOption.map { f => SpokenLanguages(f._1, languages.map(_._2)) } }
To continue, letʼs complicate our example to separate the official language from the others:
case class SpokenLanguages( country:String, officialLanguage: Option[String], otherLanguages:Seq[String] ) def spokenLanguages(countryCode: String): Option[SpokenLanguages] = { val languages: List[(String, String, Boolean)] = SQL( """ select * from Country c join CountryLanguage l on l.CountryCode = c.Code where c.code = {code}; """ ) .on("code" -> countryCode) .as { str("name") ~ str("language") ~ str("isOfficial") map { case n~l~"T" => (n,l,true) case n~l~"F" => (n,l,false) } * } languages.headOption.map { f => SpokenLanguages( f._1, languages.find(_._3).map(_._2), languages.filterNot(_._3).map(_._2) ) } }
如果你在world sample数据库尝试该例子,你將获得:
$ spokenLanguages("FRA") > Some( SpokenLanguages(France,Some(French),List( Arabic, Italian, Portuguese, Spanish, Turkish )) )
import play.api.db._ import play.api.Play.current import org.scalaquery.ql._ import org.scalaquery.ql.TypeMapper._ import org.scalaquery.ql.extended.{ExtendedTable => Table} import org.scalaquery.ql.extended.H2Driver.Implicit._ import org.scalaquery.session._ object Task extends Table[(Long, String, Date, Boolean)]("tasks") { lazy val database = Database.forDataSource(DB.getDataSource()) def id = column[Long]("id", O PrimaryKey, O AutoInc) def name = column[String]("name", O NotNull) def dueDate = column[Date]("due_date") def done = column[Boolean]("done") def * = id ~ name ~ dueDate ~ done def findAll = database.withSession { implicit db:Session => (for(t <- this) yield t.id ~ t.name).list } }
db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play" db.default.jndiName=DefaultDS