美文网首页
3K整合系列(一) Ktorm + Druid

3K整合系列(一) Ktorm + Druid

作者: 何晓杰Dev | 来源:发表于2023-08-22 17:25 被阅读0次

3K = Kotlin + Ktor + Ktorm,不要记错了哦

一直以来都用惯了 MyBatis,但是随着我越来越多的使用 Kotlin 而不是 Java,在 MyBatis 上遇到的问题也在不断增多。比如说那个经典的 kotlin.Int 作为参数的问题,由于 Kotlin 在编译期间会依据不同的情况将 kotlin.Int 映射为 int 或者 java.lang.Integer,从而导致 MyBatis 有可能无法找到正确的函数签名从而调用失败。诸如此类的问题还有很多,让我不得不想要用一种新的方案来替代掉 MyBatis。

同时,由于我将主力的服务端开发框架从 Springboot 切换到 Ktor,使得整个研发体系都已经是完整的 Kotlin 环境,因此也需要有一个更加 Kotlin 的 ORM 框架,来实现对技术栈的统一。

在我的认知中,我所需要的 ORM 框架应当具备以下特点:

  1. 尽可能的不直接编写 SQL 语句,但是又要能精准的控制生成的 SQL 语句
  2. 更加 Kotlin 风格的链式调用
  3. 能够简单的处理复杂的连表查询等情况
  4. 效率优先,尽量让我不要思考或更少的思考

在经过一番搜索后,我找到了 Ktorm,简单看了下它是 2018 年年底发布的,至今迭代到 3.6.0 版本,3.7 也正在开发中,看了一下 issue,也是比较活跃的,作者亲自解答问题。目前也算是很稳定了,又简单看了一下文档,感觉非常简单且有意思,应该是我理想中的 ORM 框架。嗯,就决定是你了!


在实际场景中,我需要连接池以及序列化的能力,参考了 Ktorm 的文档,作者也是强烈建义与连接池一起使用,那正中下怀,赶紧动手吧。

dependencies {
    implementation("org.ktorm:ktorm-core:3.6.0")
    implementation("org.ktorm:ktorm-jackson:3.6.0")
    implementation("com.alibaba:druid:1.2.18")
    implementation("mysql:mysql-connector-java:8.0.33")
    implementation("org.ktorm:ktorm-support-mysql:3.6.0")
}
data class DatabaseConfig(
    val driverClassName: String,    // 驱动的类名
    val url: String,                // jdbc url
    val username: String,           // 用户名
    val password: String,           // 密码
    val initialSize: Int = 10,      // 默认连接数
    val maxActive: Int = 25,        // 最大连接数
    val maxWait: Long = 3000,       // 最大等待时间
    val logLevel: LogLevel = LogLevel.DEBUG // 输出的日志级别
)
object WHDatabase {
    lateinit var database: Database
    private lateinit var dataSource: DataSource
    fun initDatabase(config: DatabaseConfig) {
        val prop = Properties()
        config.javaClass.declaredFields.forEach {
            prop[it.name] = "${it.apply { isAccessible = true }.get(config)}"
        }
        dataSource = DruidDataSourceFactory.createDataSource(prop)
        database = Database.connect(
            dataSource = dataSource,
            dialect = MySqlDialect(),
            logger = ConsoleLogger(config.logLevel)
        )
    }
}

现在我们可以直接使用 WHDatabase.database 来访问 database 对象,这个东西是后面一切 Ktorm 操作的开端。

下面重头戏就来了,Ktorm 独特的地方就是它的表结构与实体类,在这里可以玩出很多花来,不得不说作者在 Kotlin 上的深度是非常优秀的,从这块的设计上来看就可见一斑。下面我直接写一个真实案例。

以下是建表语句:

create table sys_user (
    user_id bigint primary key auto_increment,
    user_name varchar(32) not null default '',
    dept_id bigint not null default 0,   /* 部门 id */
    email varchar(255),
    status char(1) not null default '0', /* 0正常, 1已停用 */
    update_time datetime                 /* 更新时间 */
)

create table sys_dept (
    dept_id bigint primary key auto_increment,
    dept_name varchar(32) not null default '',
    update_time datetime
)

针对这数据结构,先定义实体类,也就是说我们在程序里传递的对象形态,这里先定义实体类是因为后面定义表结构时需要进行绑定,以便实体类的改动可以简单的传递到数据库。

interface SysUser : Entity<SysUser> {
    companion object : Entity.Factory<SysUser>()
    var userId: Long
    var userName: String
    var deptId: Long
    var email: String?
    var status: Boolean // true正常, false已停用
    var updateTime: LocalDate?
    val dept: SysDept?  // 部门实体
}

interface SysDept: Entity<SysDept> {
    companion object : Entity.Factory<SysDept>()
    var deptId: Long
    var deptName: String
    var updateTime: LocalDate?
}

好了,这就是我们需要传递的实体类了,下面将要实现的就是表结构定义了,在定义表结构的时候,我们可以同时定义表结构与实体类的绑定关系,可以引用其他的表结构,也可以对数据类型进行转换,这也是 Ktorm 为我们提供的巨大方便。

object SysUsers : Table<SysUser>("sys_user") {
    var userId = long("user_id").primaryKey().bindTo { it.userId }
    var userName = varchar("user_name").bindTo { it.userName }
    var deptId = long("dept_id").references(SysDepts) { it.dept }.bindTo { it.deptId }
    var email = varchar("email").bindTo { it.email }
    var status = varchar("status").transform({ it == "0" }, { if (it) "0" else "1" }).bindTo { it.status }
    var updateTime = date("update_time").bindTo { it.updateTime }
}

val Database.sysUsers get() = this.sequenceOf(SysUsers)

object SysDepts : Table<SysDept>("sys_dept") {
    var deptId = long("dept_id").primaryKey().bindTo { it.deptId }
    var deptName = varchar("dept_name").bindTo { it.deptName }
    var updateTime = date("update_time").bindTo { it.updateTime }
}

val Database.sysDepts get() = this.sequenceOf(SysDepts)

在这里就能看到很多有意思的东西了,比如说 SysUser 实体类中包含了 dept 对象,它是一个 SysDept 实体,也就是说需要从 sys_dept 表中查出相应的数据,这类需求在 Ktorm 里可以轻松被满足,只需要在绑定 dept_id 的时候顺便做一个 reference 就好了。

还有一个需求是 status 字段,在数据库里是 char 型,而在实体类里是 Boolean 型,这里的数据类型映射是需要进行转换的,Ktorm 也提供了 transform 方法来实现这一能力。

最后我们可以把这两个表结构挂到 Database 上,以便从 Database 直接进行访问,提供更方便的操作。


现在我们已经可以利用 Ktorm 提供的便利来进行增删改查了,比如说这样:

// 新增
val user = SysUser {
    userName = "Sample User"
    deptId = 1
    status = true
}
database.sysUsers.add(user)

// 修改
user.status = false
user.flushChanges()

// 删除
user.delete()

// 查询
val user = database.sysUsers.find { SysUsers.userId eq 1 }

除此之外,Ktorm 还提供了非常符合 SQL 书写习惯的 DSL,可以很方便的进行联表查询,比如说知道部门名称,要查该部门有哪些人,就可以这样做:

fun queryUsersInDept(deptName: String): List<SysUser> =
    database.from(SysUsers)
        .leftJoin(SysDepts, on = SysUsers.deptId eq SysDepts.deptId)
        .select(SysUsers.columns).where {
            SysDepts.deptName eq deptName
        }.map {
            SysUsers.createEntity(it)
        }

到此,目前我们已经可以正常的使用 Ktorm 来进行对数据的操作了,同时也将 Druid 连接池成功配置。

目前还需要知道的一点是,Ktorm 支持使用 Jackson 进行 JSON 序列化,这个将对后面的内容起到至关重要的作用,敬请等待 3K 整合系列的第二篇。

相关文章

网友评论

      本文标题:3K整合系列(一) Ktorm + Druid

      本文链接:https://www.haomeiwen.com/subject/gmyumdtx.html