Scala入門到精通——第二十九節 Scala資料庫程式設計
本節主要內容
- Scala Maven工程的建立
- Scala JDBC方式訪問MySQL
- Slick簡介
- Slick資料庫程式設計實戰
- SQL與Slick相互轉換
1. Scala Maven工程的建立
本節的工程專案採用的是Maven Project,在POM.xml檔案中新增下面兩個依賴就可以使用scala進行JDBC方式及Slick框架操作MySQL資料庫:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId >
<version>5.1.18</version>
</dependency>
<dependency>
<groupId>com.typesafe.slick</groupId>
<artifactId>slick_2.11</artifactId>
<version>2.1.0</version>
</dependency>
scala IDE for eclipse 中建立scala Maven專案的方式如下:
在Eclispe 中點選” File->new->other”,如下圖
輸入Maven可以看到Maven Project:
直接next,得到
再點選next,在filter中輸入scala得到:
選中,然後next輸入相應的groupId等,直接finish即可。建立完專案將上述依賴新增到pom.xml檔案當中,這樣就完成了scala maven Project的建立。
2. Scala JDBC方式訪問MySQL
下面給出的是scala採用JDBC訪問MySQL的程式碼示例
package cn.scala.xtwy.jdbc
import java.sql.{ Connection, DriverManager }
object ScalaJdbcConnectSelect extends App {
// 訪問本地MySQL伺服器,通過3306埠訪問mysql資料庫
val url = "jdbc:mysql://localhost:3306/mysql"
//驅動名稱
val driver = "com.mysql.jdbc.Driver"
//使用者名稱
val username = "root"
//密碼
val password = "123"
//初始化資料連線
var connection: Connection = _
try {
//註冊Driver
Class.forName(driver)
//得到連線
connection = DriverManager.getConnection(url, username, password)
val statement = connection.createStatement
//執行查詢語句,並返回結果
val rs = statement.executeQuery("SELECT host, user FROM user")
//列印返回結果
while (rs.next) {
val host = rs.getString("host")
val user = rs.getString("user")
println("host = %s, user = %s".format(host, user))
}
} catch {
case e: Exception => e.printStackTrace
}
//關閉連線,釋放資源
connection.close
}
3. Slick簡介
在前一小節中我們演示瞭如何通過JDBC進行資料庫訪問,同樣在Scala中也可以利用JAVA中的ORM框架如Hibernate、IBatis等進行資料庫的操縱,但它們都是Java風格的資料庫操縱方式,Scala語言中也有著自己的ORM框架,目前比較流行的框架包括:
1、Slick (typesafe公司開發)
2、Squeryl
3、Anorm
4、ScalaActiveRecord (基於Squeryl之上)
5、circumflex-orm
6、activate-framework(Scala版的Hibernate)
本節課程要講的便是Slick框架,它是Scala語言建立者所成立的公司TypeSafe所開發的一個Scala風格的開源資料庫操縱框架,它目前支援下面幾種主流的資料:
DB2 (via slick-extensions)
Derby/JavaDB
H2
HSQLDB/HyperSQL
Microsoft Access
Microsoft SQL Server (via slick-extensions)
MySQL
Oracle (via slick-extensions)
PostgreSQL
SQLite
當然它也支援其它資料,只不過功能可能還不完善。在Slick中,可以像訪問Scala自身的集合一樣對資料庫進行操作,它具有如下幾個特點:
1 資料庫的訪問採用Scala風格:
//下面給出的是資料查詢操作
class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") {
def name = column[String]("COF_NAME", O.PrimaryKey)
def price = column[Double]("PRICE")
def * = (name, price)
}
val coffees = TableQuery[Coffees]
//下面給出的資料訪問API
// Query that only returns the "name" column
coffees.map(_.name)
// Query that does a "where price < 10.0"
coffees.filter(_.price < 10.0)
從上面的程式碼可以看到,Slick訪問資料庫就跟Scala操縱自身的集合一樣.
2 Slick資料操縱是型別安全的
// The result of "select PRICE from COFFEES" is a Seq of Double
// because of the type safe column definitions
val coffeeNames: Seq[Double] = coffees.map(_.price).list
// Query builders are type safe:
coffees.filter(_.price < 10.0)
// Using a string in the filter would result in a compilation error
3 支援鏈式操作
// Create a query for coffee names with a price less than 10, sorted by name
coffees.filter(_.price < 10.0).sortBy(_.name).map(_.name)
// The generated SQL is equivalent to:
// select name from COFFEES where PRICE < 10.0 order by NAME
4. Slick 資料庫程式設計實戰
下面的程式碼演示了Slick如何建立資料庫表、如何進行資料插入操作及如何進行資料的查詢操作(以MySQL為例):
package cn.scala.xtwy
//匯入MySQL相關方法
import scala.slick.driver.MySQLDriver.simple._
object UseInvoker extends App {
// 定義一個Test表
//表中包含兩列,分別是id,name
class Test(tag: Tag) extends Table[(Int, String)](tag, "Test") {
def k = column[Int]("id", O.PrimaryKey)
def v = column[String]("name")
// Every table needs a * projection with the same type as the table's type parameter
//每個Table中都應該有*方法,它的型別必須與前面定義的型別引數(Int, String)一致
def * = (k, v)
}
//建立TableQuery物件(這裡呼叫的是TableQuery的apply方法
//沒有顯式地呼叫new
val ts = TableQuery[Test]
//forURL註冊MySQL驅動器,傳入URL,使用者名稱及密碼
//方法回返的是一個DatabaseDef物件,然後再呼叫withSession方法
Database.forURL("jdbc:mysql://localhost:3306/slick", "root","123",
driver = "com.mysql.jdbc.Driver") withSession {
//定義一個隱式值
//implicit session: MySQLDriverbackend.Session
//後續方法中當做隱式引數傳遞
implicit session =>
// 建立Test表
//create方法中帶有一個隱式引數
//def create(implicit session: JdbcBackend.SessionDef): Unit
ts.ddl.create
//插入資料
//def insertAll(values: U*)(implicit session: JdbcBackend.SessionDef): MultiInsertResult
ts.insertAll(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d", 5 -> "e")
//資料庫查詢(這裡返回所有資料)
ts.foreach { x => println("k="+x._1+" v="+x._2) }
//這裡查詢返回所有主鍵 <3的
ts.filter { _.k <3 }.foreach { x => println("k="+x._1+" v="+x._2) }
}
//模式匹配方式
ts.foreach { case(id,name) => println("id="+id+" name="+name) }
}
下面我們再給一個更為複雜的例子來演示Slick中是如何進行資料的入庫與查詢操作的:
package cn.scala.xtwy
import scala.slick.driver.MySQLDriver.simple._
object CoffeeExample extends App {
// Definition of the SUPPLIERS table
//定義Suppliers表
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey) // This is the primary key column
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
// Every table needs a * projection with the same type as the table's type parameter
def * = (id, name, street, city, state, zip)
}
val suppliers = TableQuery[Suppliers]
// Definition of the COFFEES table
//定義Coffees表
class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
def name = column[String]("COF_NAME", O.PrimaryKey)
def supID = column[Int]("SUP_ID")
def price = column[Double]("PRICE")
def sales = column[Int]("SALES")
def total = column[Int]("TOTAL")
def * = (name, supID, price, sales, total)
// A reified foreign key relation that
//can be navigated to create a join
//外來鍵定義,它使得supID域中的值關聯到suppliers中的id
//從而保證表中資料的正確性
//它定義的其實是一個n:1的關係,
//即一個Coffees對應只有一個Suppliers,
//而一個Suppliers可以對應多個Coffees
def supplier = foreignKey("SUP_FK", supID, suppliers)(_.id)
}
val coffees = TableQuery[Coffees]
Database.forURL("jdbc:mysql://localhost:3306/slick", "root", "123",
driver = "com.mysql.jdbc.Driver") withSession {
implicit session =>
// Create the tables, including primary and foreign keys
//按順序建立表
(suppliers.ddl ++ coffees.ddl).create
// Insert some suppliers
//插入操作,集合的方式
suppliers += (101, "Acme, Inc.", "99 Market Street", "Groundsville", "CA", "95199")
suppliers += (49, "Superior Coffee", "1 Party Place", "Mendocino", "CA", "95460")
suppliers += (150, "The High Ground", "100 Coffee Lane", "Meadows", "CA", "93966")
// Insert some coffees (using JDBC's batch insert feature, if supported by the DB)
//批量插入操作,集合方式
coffees ++= Seq(
("Colombian", 101, 7.99, 0, 0),
("French_Roast", 49, 8.99, 0, 0),
("Espresso", 150, 9.99, 0, 0),
("Colombian_Decaf", 101, 8.99, 0, 0),
("French_Roast_Decaf", 49, 9.99, 0, 0))
//返回表中所有資料,相當於SELECT * FROM COFFEES
coffees foreach {
case (name, supID, price, sales, total) =>
println(" " + name + "\t" + supID + "\t" + price + "\t" + sales + "\t" + total)
}
//返回表中所有資料,只不過這次是直接讓資料庫幫我們進行轉換
val q1 = for (c <- coffees)
yield LiteralColumn(" ") ++ c.name ++ "\t" ++ c.supID.asColumnOf[String] ++
"\t" ++ c.price.asColumnOf[String] ++ "\t" ++ c.sales.asColumnOf[String] ++
"\t" ++ c.total.asColumnOf[String]
// The first string constant needs to be lifted manually to a LiteralColumn
// so that the proper ++ operator is found
q1 foreach println
//聯合查詢
//採用===進行比較操作,而非==操作符,用於進行值比較
//同樣的還有!=值不等比較符
//甚至其它比較操作符與scala是一致的 <, <=, >=, >
val q2 = for {
c <- coffees if c.price < 9.0
s <- suppliers if s.id === c.supID
} yield (c.name, s.name)
}
5. SQL與Slick相互轉換
package cn.scala.xtwy
import scala.slick.driver.MySQLDriver.simple._
import scala.slick.jdbc.StaticQuery.interpolation
import scala.slick.jdbc.GetResult
object SQLAndSlick extends App {
type Person = (Int, String, Int, Int)
class People(tag: Tag) extends Table[Person](tag, "PERSON") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[String]("NAME")
def age = column[Int]("AGE")
def addressId = column[Int]("ADDRESS_ID")
def * = (id, name, age, addressId)
def address = foreignKey("ADDRESS", addressId, addresses)(_.id)
}
lazy val people = TableQuery[People]
type Address = (Int, String, String)
class Addresses(tag: Tag) extends Table[Address](tag, "ADDRESS") {
def id = column[Int]("ID", O.PrimaryKey)
def street = column[String]("STREET")
def city = column[String]("CITY")
def * = (id, street, city)
}
lazy val addresses = TableQuery[Addresses]
val dbUrl = "jdbc:mysql://localhost:3306/slick"
val jdbcDriver = "com.mysql.jdbc.Driver"
val user = "root"
val password = "123"
val db = Database.forURL(dbUrl, user, password, driver = jdbcDriver)
db.withSession { implicit session =>
(people.ddl ++ addresses.ddl).create
//插入address資料
addresses += (23, "文一西路", "浙江省杭州市")
addresses += (41, "紫荊花路", "浙江省杭州市")
//插入people資料
people += (1, "搖擺少年夢", 27, 23)
people += (2, "john", 28, 41)
people += (3, "stephen", 28, 23)
//下面兩條語句是等同的(獲取固定幾個欄位)
val query = sql"select ID, NAME, AGE from PERSON".as[(Int, String, Int)]
query.list.foreach(x => println("id=" + x._1 + " name=" + x._2 + " age=" + x._3))
val query2 = people.map(p => (p.id, p.name, p.age))
query2.list.foreach(x => println("id=" + x._1 + " name=" + x._2 + " age=" + x._3))
//下面兩條語句是等同的(Where語句)
val query3 = sql"select * from PERSON where AGE >= 18 AND NAME = '搖擺少年夢'".as[Person]
query3.list.foreach(x => println("id=" + x._1 + " name=" + x._2 + " age=" + x._3))
val query4 = people.filter(p => p.age >= 18 && p.name === "搖擺少年夢")
query4.list.foreach(x => println("id=" + x._1 + " name=" + x._2 + " age=" + x._3))
//orderBy
sql"select * from PERSON order by AGE asc, NAME".as[Person].list
people.sortBy(p => (p.age.asc, p.name)).list
//max
sql"select max(AGE) from PERSON".as[Option[Int]].first
people.map(_.age).max
//隱式join
sql"""
select P.NAME, A.CITY
from PERSON P, ADDRESS A
where P.ADDRESS_ID = a.id
""".as[(String, String)].list
people.flatMap(p =>
addresses.filter(a => p.addressId === a.id)
.map(a => (p.name, a.city))).run
// or equivalent for-expression:
(for (
p <- people;
a <- addresses if p.addressId === a.id
) yield (p.name, a.city)).run
//join操作
sql"""select P.NAME, A.CITY from PERSON P join ADDRESS A on P.ADDRESS_ID = a.id """.as[(String, String)].list
(people join addresses on (_.addressId === _.id))
.map{ case (p, a) => (p.name, a.city) }.run
}
}
新增公眾微訊號,可以瞭解更多最新Spark、Scala相關技術資訊