slick 介紹
Slick(“Scala語言集成連接工具包”)是Lightbend用于Scala的功能關(guān)系映射(FRM)庫,可以輕松使用關(guān)系數(shù)據(jù)庫挥唠。它允許您使用存儲(chǔ)的數(shù)據(jù)撒踪,就像使用Scala集合一樣,同時(shí)讓您完全控制數(shù)據(jù)庫訪問何時(shí)發(fā)生以及傳輸哪些數(shù)據(jù)壳猜。您也可以直接使用SQL枚赡。數(shù)據(jù)庫操作的執(zhí)行是異步完成的氓癌,這使得Slick非常適合基于Play和Akka的被動(dòng)應(yīng)用程序。
Slick的新功能關(guān)系映射(FRM)范例允許在Scala中完成映射贫橙,具有松散耦合贪婉,最小配置要求以及許多其他主要優(yōu)勢,這些優(yōu)勢從連接關(guān)系數(shù)據(jù)庫中抽象出復(fù)雜性卢肃。
Slick沒有嘗試彌合對象模型和數(shù)據(jù)庫模型之間的差距疲迂,而是將數(shù)據(jù)庫模型引入Scala,因此開發(fā)人員不需要編寫SQL代碼莫湘。
Slick將數(shù)據(jù)庫直接集成到Scala中尤蒿,允許使用普通的Scala類和集合以與內(nèi)存數(shù)據(jù)相同的方式查詢和處理存儲(chǔ)和遠(yuǎn)程數(shù)據(jù)。
Slick用于函數(shù)式編程的FRM方法的一些主要優(yōu)點(diǎn)包括:
- 預(yù)優(yōu)化效率
FRM是一種更有效的連接方式; 與ORM不同幅垮,它能夠預(yù)先優(yōu)化與數(shù)據(jù)庫的通信 - 而使用FRM腰池,您可以開箱即用。使用FRM比使用ORM更快地制作應(yīng)用程序的道路要短得多忙芒。
- 沒有類型安全的繁瑣故障排除
FRM為構(gòu)建數(shù)據(jù)庫查詢帶來了類型安全性示弓。開發(fā)人員的工作效率更高,因?yàn)榫幾g器會(huì)自動(dòng)發(fā)現(xiàn)錯(cuò)誤呵萨,而不是在非類型化字符串中查找錯(cuò)誤所需的典型繁瑣故障排除避乏。
- 用于構(gòu)建查詢的更高效,可組合的模型
FRM支持用于構(gòu)建查詢的可組合模型甘桑。這是一個(gè)非常自然的模型,可以將各個(gè)部分組合在一起構(gòu)建查詢歹叮,然后在代碼庫中重用各個(gè)部分跑杭。
支持的數(shù)據(jù)庫
- DB2
- Derby / JavaDB
- H2
- HSQLDB (HyperSQL)
- Microsoft SQL Server
- MySQL
- Oracle
- PostgreSQL
- SQLite
slick 如何通過代碼生成器生成實(shí)體類
Slick的代碼生成器附帶一個(gè)默認(rèn)運(yùn)行器,可以從命令行或Java / Scala中使用咆耿。你可以簡單地執(zhí)行
slick.codegen.SourceCodeGenerator.main(
Array(profile, jdbcDriver, url, outputFolder, pkg, user, password)
)
- uri:在類型安全配置中配置路徑的URL和/或片段德谅,例如 url#slick.db.default
- profile:配置文件類的完全限定名稱,例如 slick.jdbc.H2Profile
- jdbcDriver:JDBC驅(qū)動(dòng)程序類的完全限定名稱萨螺,例如 org.h2.Driver
- url:JDBC url窄做,例如 jdbc:postgresql://localhost/test
- outputFolder:應(yīng)放置包文件夾結(jié)構(gòu)的位置
- pkg:Scala包生成的代碼應(yīng)該放在哪里
- user:數(shù)據(jù)庫連接用戶名
- password:數(shù)據(jù)庫連接密碼
slick 數(shù)據(jù)庫配置
- Using Typesafe Config
mydb = {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
databaseName = "mydb"
user = "myuser"
password = "secret"
}
numThreads = 10
}
val db = Database.forConfig("mydb")
- Using a JDBC URL
val db = Database.forURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1",
driver="org.h2.Driver")
- Using a DataSource(常用)
val db = Database.forDataSource(dataSource: javax.sql.DataSource,
Some(size: Int))
- Using a JNDI Name
val db = Database.forName(jndiName: String, Some(size: Int))
slick 常規(guī)操作
- SELECT *
sql"select * from PERSON".as[Person]
people.result
- SELECT
sql"""
select AGE
from PERSON
""".as[(Int,String)]
people.map(_.age).result
- WHERE
sql"select * from PERSON where AGE >= 18 AND NAME = 'C. Vogt'".as[Person]
people.filter(p => p.age >= 18 && p.name === "C. Vogt").result
- ORDER BY
sql"select * from PERSON order by AGE asc, NAME".as[Person]
people.sortBy(p => (p.age.asc, p.name)).result
- Aggregations
sql"select max(AGE) from PERSON".as[Option[Int]].head
people.map(_.age).max.result
- GROUP BY
sql"""
select ADDRESS_ID, AVG(AGE)
from PERSON
group by ADDRESS_ID
""".as[(Int,Option[Int])]
people.groupBy(p => p.addressId)
.map{ case (addressId, group) => (addressId, group.map(_.age).avg) }
.result
- HAVING
sql"""
select ADDRESS_ID
from PERSON
group by ADDRESS_ID
having avg(AGE) > 50
""".as[Int]
people.groupBy(p => p.addressId)
.map{ case (addressId, group) => (addressId, group.map(_.age).avg) }
.filter{ case (addressId, avgAge) => avgAge > 50 }
.map(_._1)
.result
- Implicit inner joins
sql"""
select P.NAME, A.CITY
from PERSON P, ADDRESS A
where P.ADDRESS_ID = a.id
""".as[(String,String)]
people.flatMap(p =>
addresses.filter(a => p.addressId === a.id)
.map(a => (p.name, a.city))
).result
// or equivalent for-expression:
(for(p <- people;
a <- addresses if p.addressId === a.id
) yield (p.name, a.city)
).result
- Explicit inner joins
sql"""
select P.NAME, A.CITY
from PERSON P
join ADDRESS A on P.ADDRESS_ID = a.id
""".as[(String,String)]
(people join addresses on (_.addressId === _.id))
.map{ case (p, a) => (p.name, a.city) }.result
- Outer joins (left/right/full)
sql"""
select P.NAME,A.CITY
from ADDRESS A
left join PERSON P on P.ADDRESS_ID = a.id
""".as[(Option[String],String)]
(addresses joinLeft people on (_.id === _.addressId))
.map{ case (a, p) => (p.map(_.name), a.city) }.result
- Subquery
sql"""
select *
from PERSON P
where P.ID in (select ID
from ADDRESS
where CITY = 'New York City')
""".as[Person]
val address_ids = addresses.filter(_.city === "New York City").map(_.id)
people.filter(_.id in address_ids).result // <- run as one query
- insert
sqlu"""
insert into PERSON (NAME, AGE, ADDRESS_ID) values ('M Odersky', 12345, 1)
"""
people.map(p => (p.name, p.age, p.addressId)) += ("M Odersky",12345,1)
- update
sqlu"""
update PERSON set NAME='M. Odersky', AGE=54321 where NAME='M Odersky'
"""
people.filter(_.name === "M Odersky")
.map(p => (p.name,p.age))
.update(("M. Odersky",54321))
- delete
sqlu"""
delete PERSON where NAME='M. Odersky'
"""
people.filter(p => p.name === "M. Odersky")
.delete
- CASE
sql"""
select
case
when ADDRESS_ID = 1 then 'A'
when ADDRESS_ID = 2 then 'B'
end
from PERSON P
""".as[Option[String]]
import slick.ast.ScalaBaseType.stringType
people.map(p =>
Case
If(p.addressId === 1) Then "A"
If(p.addressId === 2) Then "B"
).result
slick 限制
- 缺少查詢運(yùn)算符(Slick在某種程度上是可擴(kuò)展的愧驱,這意味著您可以自己添加一些缺少的操作符)
- 非最佳SQL代碼(在某些情況下,生成的查詢比手動(dòng)編寫查詢更復(fù)雜椭盏。例如组砚,Slick偶爾會(huì)生成不必要的子查詢。在MySQL <= 5.5中掏颊,這很容易導(dǎo)致不必要的表掃描或索引未被使用)
slick 純sql查詢
普通SQL在查詢使用的是通過sql糟红,sqlu和 tsql插值
sqlu插用于其產(chǎn)生的行數(shù),而不是一個(gè)結(jié)果集的DML語句乌叶。因此它們屬于類型DBIO[Int]盆偿。
注入查詢的任何變量或表達(dá)式都會(huì)在生成的查詢字符串中變?yōu)榻壎ㄗ兞俊K粫?huì)直接插入查詢字符串准浴,因此不存在SQL注入攻擊的危險(xiǎn)事扭。
def insert(c: Coffee): DBIO[Int] =
sqlu"insert into coffees values (${c.name}, ${c.supID}, ${c.price}, ${c.sales}, ${c.total})"
此方法生成的SQL語句始終相同:
insert into coffees values (?, ?, ?, ?, ?)
以下代碼使用sql內(nèi)插器返回由語句生成的結(jié)果集。插值器本身不產(chǎn)生DBIO值乐横。需要跟隨調(diào)用.as來定義行類型:
sql"""select c.name, s.name
from coffees c, suppliers s
where c.price < $price and s.id = c.sup_id""".as[(String, String)]
這導(dǎo)致了DBIO[Seq[(String, String)]]求橄。調(diào)用as采用隱式 GetResult參數(shù),該參數(shù)從結(jié)果集中提取所請求類型的數(shù)據(jù)晰奖。GetResult標(biāo)準(zhǔn)JDBC類型有預(yù)定義的含義谈撒,對于那些(表示可為空的列)的選項(xiàng)和有類型的類型的元組GetResult。對于非標(biāo)準(zhǔn)類型匾南,您必須定義自己的轉(zhuǎn)換器:
case class Coffee(name: String, supID: Int, price: Double, sales: Int, total: Int)
implicit val getCoffeeResult = GetResult(r => Coffee(r.<<, r.<<, r.<<, r.<<, r.<<))
使用快捷方法<<啃匿,該方法 返回在此處預(yù)期的任何類型的值。(當(dāng)然蛆楞,只有在這個(gè)構(gòu)造函數(shù)調(diào)用中實(shí)際知道類型時(shí)才能使用它溯乒。)