Kotlin 数据库操作
数据库是应用开发的核心组件。本章将介绍如何在 Kotlin 中使用 JDBC 和 ORM 框架进行数据库操作。
使用 JDBC
JDBC 是 Java 标准的数据库连接 API,Kotlin 完全兼容。
基本配置
// build.gradle.kts
dependencies {
implementation("mysql:mysql-connector-java:8.0.33")
}
连接数据库
import java.sql.*
fun main() {
val url = "jdbc:mysql://localhost:3306/mydb"
val user = "root"
val password = "password"
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver")
// 获取连接
val connection = DriverManager.getConnection(url, user, password)
try {
println("数据库连接成功")
// 创建 Statement
val statement = connection.createStatement()
// 执行查询
val resultSet = statement.executeQuery("SELECT * FROM users")
while (resultSet.next()) {
val id = resultSet.getInt("id")
val name = resultSet.getString("name")
val email = resultSet.getString("email")
println("ID: $id, Name: $name, Email: $email")
}
resultSet.close()
statement.close()
} finally {
connection.close()
}
}
使用 use 自动关闭资源
import java.sql.*
fun main() {
val url = "jdbc:mysql://localhost:3306/mydb"
DriverManager.getConnection(url, "root", "password").use { conn ->
conn.createStatement().use { stmt ->
stmt.executeQuery("SELECT * FROM users").use { rs ->
while (rs.next()) {
println("${rs.getInt("id")}: ${rs.getString("name")}")
}
}
}
}
}
PreparedStatement 防止 SQL 注入
import java.sql.*
fun main() {
val url = "jdbc:mysql://localhost:3306/mydb"
DriverManager.getConnection(url, "root", "password").use { conn ->
// 查询
val query = conn.prepareStatement("SELECT * FROM users WHERE id = ?")
query.setInt(1, 1)
query.executeQuery().use { rs ->
while (rs.next()) {
println(rs.getString("name"))
}
}
// 插入
val insert = conn.prepareStatement(
"INSERT INTO users (name, email) VALUES (?, ?)"
)
insert.setString(1, "张三")
insert.setString(2, "[email protected]")
val rowsInserted = insert.executeUpdate()
println("插入 $rowsInserted 行")
// 更新
val update = conn.prepareStatement(
"UPDATE users SET name = ? WHERE id = ?"
)
update.setString(1, "李四")
update.setInt(2, 1)
val rowsUpdated = update.executeUpdate()
println("更新 $rowsUpdated 行")
// 删除
val delete = conn.prepareStatement("DELETE FROM users WHERE id = ?")
delete.setInt(1, 1)
val rowsDeleted = delete.executeUpdate()
println("删除 $rowsDeleted 行")
}
}
事务处理
import java.sql.*
fun main() {
val url = "jdbc:mysql://localhost:3306/mydb"
DriverManager.getConnection(url, "root", "password").use { conn ->
// 关闭自动提交
conn.autoCommit = false
try {
// 操作 1
conn.prepareStatement("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
.executeUpdate()
// 操作 2
conn.prepareStatement("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
.executeUpdate()
// 提交事务
conn.commit()
println("事务提交成功")
} catch (e: SQLException) {
// 回滚事务
conn.rollback()
println("事务回滚: ${e.message}")
} finally {
conn.autoCommit = true
}
}
}
使用 Exposed
Exposed 是 JetBrains 开发的 Kotlin ORM 框架,提供类型安全的 SQL DSL。
配置依赖
// build.gradle.kts
dependencies {
implementation("org.jetbrains.exposed:exposed-core:0.45.0")
implementation("org.jetbrains.exposed:exposed-jdbc:0.45.0")
implementation("org.jetbrains.exposed:exposed-java-time:0.45.0")
implementation("mysql:mysql-connector-java:8.0.33")
}
定义表结构
import org.jetbrains.exposed.sql.*
// 定义表
object Users : Table("users") {
val id = integer("id").autoIncrement()
val name = varchar("name", 50)
val email = varchar("email", 100).uniqueIndex()
val createdAt = datetime("created_at")
override val primaryKey = PrimaryKey(id)
}
object Posts : Table("posts") {
val id = integer("id").autoIncrement()
val title = varchar("title", 200)
val content = text("content")
val authorId = integer("author_id") references Users.id
val createdAt = datetime("created_at")
override val primaryKey = PrimaryKey(id)
}
连接数据库
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction
fun main() {
// 连接数据库
Database.connect(
url = "jdbc:mysql://localhost:3306/mydb",
driver = "com.mysql.cj.jdbc.Driver",
user = "root",
password = "password"
)
// 执行事务
transaction {
// 数据库操作
}
}
CRUD 操作
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.LocalDateTime
fun main() {
Database.connect(
"jdbc:mysql://localhost:3306/mydb",
"com.mysql.cj.jdbc.Driver",
"root",
"password"
)
transaction {
// 创建表
SchemaUtils.create(Users)
// 插入
val userId = Users.insert {
it[name] = "张三"
it[email] = "[email protected]"
it[createdAt] = LocalDateTime.now()
} get Users.id
println("插入用户 ID: $userId")
// 批量插入
Users.batchInsert(listOf("李四", "王五")) { name ->
this[Users.name] = name
this[Users.email] = "$name@example.com"
this[Users.createdAt] = LocalDateTime.now()
}
// 查询所有
Users.selectAll().forEach { row ->
println("${row[Users.id]}: ${row[Users.name]}")
}
// 条件查询
Users.select { Users.name eq "张三" }.forEach { row ->
println("找到: ${row[Users.email]}")
}
// 排序和分页
Users.selectAll()
.orderBy(Users.id to SortOrder.DESC)
.limit(10, offset = 0)
.forEach { row ->
println(row[Users.name])
}
// 更新
Users.update({ Users.id eq userId }) {
it[name] = "张三更新"
}
// 删除
Users.deleteWhere { Users.id eq userId }
}
}
关联查询
import org.jetbrains.exposed.sql.*
fun main() {
transaction {
// Join 查询
(Users innerJoin Posts)
.select { Users.id eq Posts.authorId }
.forEach { row ->
println("作者: ${row[Users.name]}, 标题: ${row[Posts.title]}")
}
// 左连接
(Users leftJoin Posts)
.slice(Users.name, Posts.title.count())
.selectAll()
.groupBy(Users.name)
.forEach { row ->
println("${row[Users.name]}: ${row[Posts.title.count()]} 篇文章")
}
}
}
使用 Room(Android)
Room 是 Android 官方推荐的数据库 ORM 库。
配置依赖
// build.gradle.kts
plugins {
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
}
dependencies {
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
}
定义实体
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val name: String,
val email: String,
val createdAt: Long = System.currentTimeMillis()
)
定义 DAO
import androidx.room.*
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): Flow<List<User>>
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getById(id: Long): User?
@Query("SELECT * FROM users WHERE name LIKE :name")
suspend fun findByName(name: String): List<User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User): Long
@Insert
suspend fun insertAll(vararg users: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("DELETE FROM users")
suspend fun deleteAll()
}
定义数据库
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
使用数据库
import androidx.room.Room
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.runBlocking
// 在 Application 或 Activity 中
fun main() = runBlocking {
// 创建数据库实例(实际使用时需要 Context)
// val db = Room.databaseBuilder(
// applicationContext,
// AppDatabase::class.java,
// "mydb"
// ).build()
// 示例用法
// val userDao = db.userDao()
// 插入
// userDao.insert(User(name = "张三", email = "[email protected]"))
// 查询
// userDao.getAll().collectLatest { users ->
// users.forEach { println(it) }
// }
}
数据库操作最佳实践
1. 使用连接池
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
fun createDataSource(): HikariDataSource {
val config = HikariConfig()
config.jdbcUrl = "jdbc:mysql://localhost:3306/mydb"
config.username = "root"
config.password = "password"
config.maximumPoolSize = 10
config.driverClassName = "com.mysql.cj.jdbc.Driver"
return HikariDataSource(config)
}
2. 异步数据库操作
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.sql.*
suspend fun queryUser(id: Int): User? = withContext(Dispatchers.IO) {
// 数据库查询操作
null
}
3. 使用事务保证数据一致性
import org.jetbrains.exposed.sql.transactions.transaction
fun transferMoney(fromId: Int, toId: Int, amount: Double) {
transaction {
// 在事务中执行多个操作
// 要么全部成功,要么全部失败
}
}
小结
本章我们学习了 Kotlin 数据库操作的核心内容:
- JDBC:Java 标准数据库连接 API
- PreparedStatement:防止 SQL 注入
- 事务处理:保证数据一致性
- Exposed:Kotlin ORM 框架,类型安全
- Room:Android 官方 ORM 库
选择合适的数据库访问方式取决于项目需求。JDBC 适合简单场景,Exposed 提供类型安全的 DSL,Room 是 Android 开发的首选。
练习
- 使用 JDBC 实现一个简单的用户管理 CRUD
- 使用 Exposed 定义表结构并进行关联查询
- 实现一个使用事务的银行转账功能
- 使用 Room 实现一个 Android 应用的数据持久化