跳到主要内容

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 数据库操作的核心内容:

  1. JDBC:Java 标准数据库连接 API
  2. PreparedStatement:防止 SQL 注入
  3. 事务处理:保证数据一致性
  4. Exposed:Kotlin ORM 框架,类型安全
  5. Room:Android 官方 ORM 库

选择合适的数据库访问方式取决于项目需求。JDBC 适合简单场景,Exposed 提供类型安全的 DSL,Room 是 Android 开发的首选。

练习

  1. 使用 JDBC 实现一个简单的用户管理 CRUD
  2. 使用 Exposed 定义表结构并进行关联查询
  3. 实现一个使用事务的银行转账功能
  4. 使用 Room 实现一个 Android 应用的数据持久化