飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

Kotlin 的新 ORM 框架

时间:2023-06-08  作者:电脑狂魔  

如果您厌恶新框架,请不要阅读本文。对于其他类型的读者,请注意,在这里我将提出一个 API 的建议,该 API主要以声明式的方式对数据库查询进行建模,主要采用强大的Kotlin类型检查。只实现了一些围绕实体的类;现在缺少数据库连接。在这个项目中,我试图评估我迄今为止的经验和愿景。然而,并非本文​​中提出的所有想法都是全新的。其中一些我借鉴了Ujorm框架,实体概念是受Ktorm框架的启发。但是代码是新的。原型是马赛克慢慢拼凑而成的,现在已经呈现出一种值得展示的形式。

如果您没有因为介绍而气馁,我将跳过关于ORM 的一般性讨论,让您直接看代码示例。演示示例使用两个关系数据库表。这是一种员工/部门(未指定的组织)关系,其中每个员工可能(或可能没有)有主管。两个表均由以下类图中的实体描述:

两个表均由以下类图中的实体描述。

假设我们要创建一个包含唯一员工编号、员工姓名、部门名称和主管姓名(如果有)的报表。我们只对具有正标识符且部门名称以字母“D”开头的部门感兴趣。我们想按部门名称(降序)然后按员工姓名(升序)对报告进行排序。基于这些实体的查询 (SELECT) 如何查看所提供的API?

val employees: Employees = 域名oyees // Employee metamodel
val departments: Departments = 域名rtments // Department metamodel
val result: List<Employee> = 域名ct(
    域名,
    域名,
    域名rtment + 域名, // DB relation by the inner join
    域名rior + 域名) // DB relation by the left outer join
.where((域名rtment + 域名 GE 1)
    AND (域名rtment + 域名 STARTS "D"))
.orderBy(
    域名rtment + 域名 ASCENDING false,
    域名 ASCENDING true)
.toList()

今天,在数据库查询中使用 DSL 可能不会让任何人感到惊讶。然而,实体属性模型(以下简称property-descriptor )的链接是值得关注的。将它们组合起来会创建一个新的复合属性描述符,该描述符实现与其原子部分相同的接口。查询过滤器 (WHERE) 由从基本条件构造成单个二叉树的对象描述。复合属性描述符提供信息,数据库表之间的 SQL 查询会话也可从中派生。这种方法可能涵盖最常见的SQL 查询,包括递归查询。但肯定不是全部。对于其余的,必须使用替代解决方案。项目测试中有一个粗略的设计。

接下来让我们关注员工实体:

@Entity
interface Employee {
    var id: Int
    var name: String
    var higherEducation: Boolean
    var contractDay: LocalDate
    var department: Department
    var superior: Employee?
}

实体是一个没有其他依赖项的接口。优点是接口可以通过(在ORM中)无需修改二进制代码。但是,要获取对象,您必须使用提供实现的工厂方法。另一种方法是扩展一些通用类(由框架提供),我发现它更具侵入性。元模型对对象提供了创建新对象的工厂方法。

这里的每个实体都需要一个元模型,其中包含有关其类型和提供某些服务的属性的信息。元模型的属性是上面提到的对实体描述符的属性。请注意,相同的属性()方法用于创建属性描述符,它是否是会话描述(另一个实体的属性)并不重要。唯一的例外是属性(实体)类型接受 NULL。好消息是编译器会在编译时报告误用(较短的方法名称)。附上员工实体元模型的例子:

open class Employees : EntityModel<Employee>(Employee::class) {
    val id = property { 域名 }
    val name = property { 域名 }
    val higherEducation = property { 域名erEducation }
    val contractDay = property { 域名ractDay }
    val department = property { 域名rtment }
    val superior = propertyNullable { 域名rior }
}

Annotations会在实体上声明列(数据库表)的具体属性,这样元模型类就可以根据它们的实体生成一次。实体数据存储(内部)在一个对象数组中。与基于 HashMap 类的实现相比,优点是内存需求更少。

下一个示例演示了新对象的创建及其在数据库中的存储 (INSERT)。

val development: Department = 域名 {
    name = "Development"
    created = 域名(2020, 10, 1)
}
val lucy: Employee = 域名 {
    name = "Lucy"
    contractDay = 域名(2022, 1, 1)
    superior = null
    department = development
}
val joe: Employee = 域名 {
    name = "Joe"
    contractDay = 域名(2022, 2, 1)
    superior = lucy
    department = development
}
域名(development, lucy, joe)

MyDatabase类(提供元模型)是这里唯一的一个(单例设计模式)。不过,它通常可以是我们的应用程序上下文提供的任何对象(例如)。如果我们想使用服务(例如,克隆一个实体对象),我们可以使用AbstractEntityProvider类扩展(那个提供者)并使用它的资源。可以在项目测试中找到推荐注册过程(元模型类)的示例以及其他示例。

状况

条件(或标准)是我们在呈现 SELECT 语句时遇到的对象。但是,您也可以单独使用条件,例如,验证实体值或筛选集合。如果该库支持将其序列化为 JSON 文本格式(并返回),则使用范围可能会更加广泛。要构建以下条件,我们从已存储在employees变量中的元模型开始。

val crn1 = 域名 EQ "Lucy"
val crn2 = 域名 GT 1
val crn3 = (域名rtment + 域名) LT 99
val crn4 = crn1 OR (crn2 AND crn3)
val crn5 = 域名() OR (crn2 AND crn3)

如果我们在 employee 变量中有一个 employee 对象,则可以使用以下代码测试 employee 标准:

expect(crn4(employee)).equals(true)  // Valid employee
expect(crn5(employee)).equals(false) // Invalid employee

在第一行,员工符合标准;在第二行,它没有。如果需要(在调试或记录期间),条件的内容可以在文本中可视化;附上例子:

expect(域名ring())
    .toEqual("""Employee: name EQ "Lucy"""")
expect(域名ring())
    .toEqual("""Employee: id GT 1""")
expect(域名ring())
    .toEqual("""Employee: 域名 LT 99""")
expect(域名ring())
    .toEqual("""Employee: (name EQ "Lucy") OR (id GT 1) AND (域名 LT 99)""")
expect(域名ring())
    .toEqual("""Employee: (NOT (name EQ "Lucy")) OR (id GT 1) AND (域名 LT 99)""")

其他有趣的事情

属性描述符不仅可以用于对SQL 查询建模,还可以参与读取和写入对象的值。最简单的方法是使用PropertyAccessor接口扩展实体接口。如果我们有一个员工对象,可以使用代码来读取它:

val id: Int = employee[域名]
val name: String = employee[域名]
val contractDay: LocalDate = employee[域名ractDay]
val department: Department = employee[域名rtment]
val superior: Employee? = employee[域名rior]
val departmentName: String = employee[域名rtment + 域名]

变量数据类型的显式声明仅用于展示目的,但在实践中,它们是多余的,可以删除。将变量写入对象类似:

employee[域名] = id
employee[域名] = name
employee[域名ractDay] = contractDay
employee[域名rtment] = department
employee[域名rior] = superior
employee[域名rtment + 域名] = departmentName

请注意,读取和写入值是在不覆盖 NULLABLE 值的情况下完成的。另一个有趣的特性是支持使用复合属性描述符读取和写入值。只是为了论证,我向你保证,对于对象的正常使用,使用接口声明的标准实体API会更方便。

上面的示例将其属性复制到变量并返回。如果我们想克隆一个对象,我们可以使用以下结构(浅拷贝):

val target: Employee = 域名s().clone(source)

在数据复制期间不调用反射方法,这是所使用的类体系结构所允许的。更多功能使用示例可以在 GitHub 上的项目测试中找到。

为什么?

为什么创建这个项目?一开始,这只是为了学习 Kotlin 的基础知识。逐渐创建了一堆源代码形式的杂乱无章的笔记,我遇到语言资源只是时间问题,这也将导致简化的 Ujorm 框架 API。在 Kotlin 中找到现成的 ORM 库让我很高兴。但是,在两个流行的中,我找不到更适合我的。我错过了一个与另一个的有趣功能,反之亦然。在某些地方,我发现 API 不够直观,无法使用;在其他情况下,我遇到了并发症与数据库表递归。一个常见的障碍是(根据我的口味)手动构建实体元模型时错误率增加。在这里,可以肯定地反驳说实体可以从数据库表中生成。最后,我把原来的笔记整理成了一个项目,清理了代码,加上了这篇文章。也许这就是所有相关的。

结论

我喜欢与现成的 ORM 框架的核心集成;最快的可能是与 Ujorm 的集成。但是,我知道与任何集成相关的风险,并且我不能排除这个项目最终不会在 ORM 领域找到任何实际用途。该原型在 Apache Commons 2 许可下免费提供。感谢您的建设性意见。


标签:Kotlin
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。