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

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 整合系列的第二篇。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容