SQLDelight介绍

1,849 阅读6分钟

根据其官方文档,SQLDelight库以从SQL语句生成类型安全的Kotlin APIs而闻名。凭借其模式验证、语句和编译时迁移,SQLDelignt提供了IDE功能,使编写和维护SQL变得简单。

那么,SQLDelight做了什么其他库没有的事情呢?让我们以最著名的本地Android开发库Room为例。

这两个库都围绕着SQLite数据库,它通过提供Kotlin和Java API来推理大多数带有编译时验证的CRUD操作,从而避免了我们使用噩梦般的SQLiteOpenHelper

SQLDelight的与众不同之处在于,除了Android之外,它还支持跨平台,可以选择将底层数据库改为MySQLPostgress 、或HSQL/H2

使用Kotlin多平台移动,SQLDelight还提供了跨平台的兼容性,有平台无关的API,可以在Android、iOS等不同平台上分享业务逻辑代码。

而且,SQLDelight提供了语法高亮和自动完成、迁移测试和导出、为Java虚拟机(JVM)项目切换底层数据库的能力,并支持coroutines和RxJava。

在这篇文章中,我们将介绍以下内容。

  1. 设置SQLDelight
  2. 设置并连接到数据库
  3. 使用SQLDelight对Insert,Update,Select, 和Delete 查询进行CRUD操作
  4. 事务操作和原子回滚

好了,让我们开始吧!

设置SQLDelight

由于JVM项目对于iOS或Android开发者来说是最具有平台中立性的,这里提到的所有操作都适用于本地移动开发。唯一的变化将是加入的数据库驱动的依赖性。

首先,打开IntelliJ ,在其中创建一个新项目。然后,选择Kotlin,并将以下依赖项添加到Console Application

buildscript {
  repositories {
    google()
    mavenCentral()
    maven { url "https://www.jetbrains.com/intellij-repository/releases" }
    maven { url "https://jetbrains.bintray.com/intellij-third-party-dependencies" }
  }
  dependencies {
    classpath 'com.squareup.sqldelight:gradle-plugin:1.5.0'
  }
}

apply plugin: 'com.squareup.sqldelight'

dependencies {
    implementation 'com.squareup.sqldelight:jdbc-driver:1.5.1'
    implementation 'com.zaxxer:HikariCP:5.0.0'
    implementation 'org.slf4j:slf4j-simple:1.7.32'
    implementation 'mysql:mysql-connector-java:8.0.26'
    testImplementation 'org.jetbrains.kotlin:kotlin-test:1.5.31'
}

sqldelight {
  Database { // This will be the name of the generated database class.
    packageName = "com.example"
    dialect = "mysql"
  }
}

设置MySQL数据库

现在我们已经为SQLDelight设置好了一切,让我们继续设置一个MySQL数据库。

首先,为你的设备下载并安装MySQLmacOS请按这里)。

然后,你可以安装一个可视化工具来可视化数据,如TablePlusWorkbench,并将你的数据库连接到可视化工具,这样我们就可以看到实时的数据变化。

创建一个名为movies_db 的数据库;你可以通过 这个csv作为一个新的表导入我们的movies_db database ,来导入一个样本数据集用于本教程。

我们的db ,然后看起来像这样。

Setting Up The Movie Database, Shows List Of Various Movies

将SQLDelight连接到MySQL数据库

现在,让我们回到我们的Java项目!

为了让SQLDelight连接到MySQL数据库,它需要一个DataSource ,我们可以从像HikariCP这样的连接管理器中获得这个实例。

然后,我们可以创建下面的函数,将DatasourceMain.kt 文件中。

private fun getSqlDriver(): SqlDriver {
    val ds = HikariDataSource()
    ds.jdbcUrl = "jdbc:mysql://localhost:3306/movies_db"
    ds.driverClassName = "com.mysql.jdbc.Driver"
    ds.username = "root"
    ds.password = "mysqlroot"
    return ds.asJdbcDriver()
}

接下来,让我们在src/sqldelight 目录下创建一个Movie.sq 文件,并添加一个SELECT 语句。

CREATE TABLE movies (
  Film text,
  Genre text,
  `Lead Studio` text,
  `Audience score` int DEFAULT NULL,
  Profitability float DEFAULT NULL,
  `Rotten Tomatoes` int DEFAULT NULL,
  `Worldwide Gross` text,
  Year int DEFAULT NULL
);

selectAll:
SELECT *
FROM movies;

随着我们项目的构建,SQLDelight会创建所需的模型和查询文件。这可以帮助我们以后进行调试和测试。

Creating Model And Query Files, Shows List Of Files

为了测试我们的初始代码,我们可以在我们的main 函数中写下以下内容并运行。

fun main(args: Array<String>) {

    val database = Database(getSqlDriver())
    val moviesQueries = database.movieQueries

    val movies = moviesQueries.selectAll().executeAsList()
    println(movies)

}

这将在终端打印出我们的Movies 表。

Table Of Movies In Database With Information Like Genre, Rotten Tomato Ranking, Profitability, And More

注意,在生产中,我们应该只初始化一次数据库代码,并将其作为单子重用,以防止内存问题。

在SQLDelight中的CRUD操作

所有基本的CRUD操作仍然与在.sq 文件中写一个SQL查询前缀的方法名称一样,SQLDelight会建立所需的函数和数据模型。

Insert

正如我们从SQLDelight构建文件中看到的那样,我们有一个从数据库中的电影表创建的Movies 数据类。

我们有两个选择来进行插入:用movies 表中的每个值创建一个函数,或者传递整个movie 对象。

如果我们按值创建一个函数,我们有如下的结果。

insert:
INSERT INTO movies(Film, Genre, `Lead Studio`, `Worldwide Gross`, Year)
VALUES(?, ?, ?, ?,?);

如果我们按对象创建一个函数,我们就有以下结果。

insertObject:
INSERT INTO movies(Film, Genre, `Lead Studio`, `Audience score`, Profitability, 
`Rotten Tomatoes`, `Worldwide Gross`, Year)
VALUES ?;

现在,我们可以在我们的main 函数中建立和运行这些方法。

fun main(args: Array<String>) {
  ....

// Insert by values
  insert(
      "Deadpool",
      "Action/Adventure",
      "20th Century Fox",
      "$734",
      2016
  )

  // Insert object
  val movie = Movies(
      Film = "Wonder Woman",
      Genre = "Action/Adventure",
      Lead_Studio = "DC Films",
      Audience_score = null,
      Profitability = null,
      Rotten_Tomatoes = null,
      Worldwide_Gross = "$66",
      Year = 2017
  )
  insert(movie)
}

private fun insert(
    film: String,
    genre: String,
    leadStudio: String,
    worldwideGross: String,
    year: Int
) {
    moviesQueries.insert(film, genre, leadStudio, worldwideGross, year)
}

private fun insert(movies: Movies) {
    moviesQueries.insertObject(movies)
}

通过打印或在查看器中查看我们的表格,我们可以验证以下数值。

Verifying We Inserted The Correct Values For The Movie, Including Year Of Release, Genre, Production Studio, And World-Wide Gross

Update

这里没有什么花哨的东西,只是通常的Update 查询,使我们能够更新我们数据库中的任何现有字段。

update:
UPDATE movies
SET `Worldwide Gross`=? WHERE Film=?;

然后它在Kotlin中构建了以下内容。

private fun update(worldwideGross: String, film: String) {
    moviesQueries.update(worldwideGross, film)
}

Delete

继续我们的查询,我们可以添加delete 查询。

delete:
DELETE FROM movies WHERE Film=?;

然后在Kotlin中建立这个。

private fun delete(film: String) {
    moviesQueries.delete(film)
}

Select

我们已经在上面看到了简单的选择查询,同时打印表格,所以让我们看看带有namedvariable 参数的Select 查询。

为了给Select 查询添加命名参数,我们必须添加以下内容。

filmOrGenre:
SELECT * FROM movies
WHERE Film LIKE :searchQuery
OR Genre LIKE :searchQuery;

然后在Kotlin中建立以下内容。

private fun searchFilmOrGenre(query: String): List<Movies> {
    return moviesQueries.filmOrGenre(searchQuery = query).executeAsList()
}

在这里,我们可以传递一组值作为参数,以便在我们的应用程序中启用搜索功能。

filmByNames:
SELECT * FROM movies
WHERE Film IN ?;

然后在Kotlin中构建。

private fun searchFilmsByName(films: List<String>): List<Movies> {
    return moviesQueries.filmByNames(films).executeAsList()
}

最后,在main 函数中,我们可以传递一个要搜索的电影列表。

searchFilmsByName(listOf("Penelope", "Valentine's Day", "Mamma Mia!"))

交易

我们还可以使用transaction 函数在一个事务中执行多个语句,并可以选择回调或回滚。

当我们有许多必须合并的查询时,这很有帮助,因为单独运行多个查询会导致内存开销。

因此,让我们在一个函数中插入多个电影,如下所示。

val bunchOfMovies = listOf(
    Movies(
        Film = "Sunny",
        Genre = "",
        Lead_Studio = "Dreams N Beyond",
        Audience_score = null,
        Profitability = null,
        Rotten_Tomatoes = null,
        Worldwide_Gross = "",
        Year = 2021
    ),
    Movies(
        Film = "Kala",
        Genre = "Crime",
        Lead_Studio = "Juvis Productions",
        Audience_score = null,
        Profitability = null,
        Rotten_Tomatoes = null,
        Worldwide_Gross = "",
        Year = 2020
    ),
)

moviesQueries.transaction {
    bunchOfMovies.forEach { movie ->
        moviesQueries.insertObject(movie)
    }
}

现在,假设我们需要在运行时根据某些条件取消整个事务;我们可以选择使用rollback() ,进行回滚。

moviesQueries.transaction {
    bunchOfMovies.forEach { movie ->
        if (movie.Genre.isNullOrEmpty())
            rollback()
        moviesQueries.insertObject(movie)
    }
}

我们还可以选择在我们的任何要求没有得到满足的情况下,获得交易完成或回滚的回调。

// Rollback
moviesQueries.transaction {
    afterCommit {
      println(
          "Transaction complete: ${bunchOfMovies.size} movies inserted"
      )
    }
    afterRollback { println("Rollback: No movies were inserted") }

    bunchOfMovies.forEach { movie ->
        if (movie.Genre.isNullOrEmpty())
            rollback()
        moviesQueries.insertObject(movie)
    }
}

结论

就这样,我们已经涵盖了使用SQLDelight进行类型安全的Kotlin-esque数据库操作的基本知识。从这里开始,你可以深入研究一些高级操作,如迁移、快速文本搜索、自定义适配器等等。

完整的工作程序可以在这个Github Repo中找到。

SQLDelight介绍》一文首次出现在LogRocket博客上。