# 数据库交互
上面我们了解了查询构造器,并知道了如何利用它来生成sql语句。
但实际项目中,我们通常需要连接到数据库,并修改数据库中的数据或是获取到查询结果。
目前,easysql添加了jdbc子项目,基于jdbc做了数据库交互的实现(以后会逐步添加其他的数据库驱动实现)
下面我们以jdbc
子项目为例,来介绍easysql与数据库交互的方式:
首先,我们需要一个数据库连接池,使用者可以自行替换成实现了DataSource
接口的连接池,如Hikari、Druid、C3P0等。
import easysql.database.*
// 此处省略连接池配置
val dataSource: DataSource = ???
// 第一个参数为数据库类型的枚举,第二个参数为连接池,用户可自行根据项目需要替换
// 比如MySQL和使用了MySQL方言的数据库如Doris等,此处便可以使用DB.MYSQL方言
val db = new JdbcConnection(DB.MYSQL, dataSource)
然后我们需要配置日志管理,任意String => Unit
都可以作为日志的接收器,此处我们使用given
来创建(以java内置的Logger,以及println为例):
import easysql.database.*
// java自带Logger
val logger = java.util.logging.Logger.getLogger("")
given Logger = logger.info
// println
given Logger = println
配置好之后,我们可以在此之上操作数据库:
# 执行sql
对于insert、update、delete等修改数据的sql,我们可以使用run
方法执行,并返回Int类型的受影响行数:
import easysql.database.*
import easysql.dsl.*
val users = List(User(1, Some("x")), User(2, Some("y")))
val insert = insert(users)
val result: Int = db.run(insert)
如果insert操作时,数据库中有自增主键,我们可以使用runAndReturnKey
方法,返回一个List[Long]类型的结果集:
import easysql.database.*
import easysql.dsl.*
val users = List(User(1, Some("x")), User(2, Some("y")))
val insert = insert(users)
val result: List[Long] = db.runAndReturnKey(insert)
# 查询结果集
使用query
来将查询结果映射到List:
import easysql.database.*
import easysql.dsl.*
val s1 = from(user)
val result1: List[Option[User]] = db.query(s1)
val s2 = select (user, post) from user join post on user.id === post.userId
val result2: List[(Option[User], Option[Post])] = db.query(s2)
查询结果会根据select的参数自动推断,不需要手动指定。
单表或单列的查询,如果返回结果是List[Option[T]],是因为有些时候,空行是有业务含义的,如果你的应用场景不需要这个额外的Option,那么可以用querySkipNoneRows
来简化返回结果类型:
import easysql.database.*
import easysql.dsl.*
val s = from(user)
val result: List[User] = db.querySkipNoneRows(s)
# 查询单条结果
使用find
来将符合条件的第一条结果映射到Option:
import easysql.database.*
import easysql.dsl.*
val s = select (user) from user
val result: Option[Option[User]] = db.find(s)
# 分页查询
使用page
,返回结果是一个Page
类型,其定义如下:
case class Page[T](totalPage: Long, totalCount: Long, currentPage: Long, pageSize: Long, data: List[T])
分页查询参数中除了需要传入查询dsl之外,还需要依次传入一页的结果集条数,页数,和是否需要查询count;
其中最后一个参数,默认值为true,为true时会附带执行一个查询count的sql,如无必要,请传入false,以便提升效率:
import easysql.database.*
import easysql.dsl.*
val s = select (user) from user
val result: Page[Option[User]] = db.page(s)(10, 1, false)
# 查询count
使用fetchCount
方法来查询结果集大小,返回结果是Long
类型。
此处会对生成的sql语法树进行复制,并去除对于查询count无用的order by和limit信息,并把select列表替换成COUNT(*),以提高查询效率,适用于查询中没有group by的情况:
import easysql.database.*
import easysql.dsl.*
val s = select (user.id, user.name) from user orderBy user.id.asc limit 10 offset 10
val count: Long = db.fetchCount(s)
此处实际生成的sql为:
SELECT COUNT(*) AS count FROM user
# 数据库事务
使用transaction
函数来产生一个事务,transaction
是一个高阶函数。
高阶函数中如果出现了异常,将会进行回滚,如无异常,将会提交事务,函数结算后回收数据库连接,并返回函数中的返回值,我们也可以通过try catch
处理异常:
import easysql.database.*
val result: Int = try {
db.transaction {
run(...) // 高阶函数里可以执行一些查询
}
} catch case e: Exception => -1
我们也可以手动传入java.sql.Connection
中定义的隔离级别,比如:
import easysql.database.*
db.transactionIsolation(TRANSACTION_READ_UNCOMMITTED) {
run(...)
}
在很多应用中,为了方便处理,我们可能需要事务的上下文在不同的方法中传播,这种情况也非常容易,只需要在方法中使用using
添加一个隐式参数即可:
import easysql.database.*
import easysql.dsl.*
def add(user: User)(using JdbcTransaction) = runAndReturnKey(insert(user)).head.toInt
def remove(id: Int)(using JdbcTransaction) = run(delete[User](id))
def addAndRemove(user: User)(using JdbcTransaction) = {
val id = add(user)
remove(id)
}
val user = User(1, Some("x"))
val result: Int = try {
db.transaction {
addAndRemove(user)
}
} catch case e: Exception => -1