Exposed spring integration and code generator for dsl interface

Overview

Infra-ORM

maven central version workflow status license

欢迎使用 Infra-ORM, 这是一个基于 Exposed 的 ORM 框架,可以和 Spring Boot 集成良好,如果你是 Kotlin 开发者,推荐你试试 Exposed, 配合 Infra-ORM 可以给你带来最佳的开发体验。

为什么造这个轮子?

Exposed 提供了 Dao 和 DSL 编程模型,具体编程模型的争论可以看下面的 issue 连接, Infra-ORM 主要解决该讨论中的问题, 因此,你如果是 DAO 编程模型(SQL延迟发送,实体附加状态,这是另一个 JPA ?)爱好者,请无视本项目,使用 Exposed 官方的 DAO 包。

DAO VS DSL 讨论原帖:
https://github.com/JetBrains/Exposed/issues/24

DAO编程模型的问题:

使用 dao 编程模型的框架很多,JAVA 领域最具代表性的要数 JPA。

  1. DAO对象是一个附加”函数“功能的复杂对象,大多框架数实现都带有一个基类,你不能得到一个纯净的 POJO,这个基类可能会对序列化造成不良影响(例如 Kryo 序列化)。
  2. 任何的 DAO 对象使用时都要异常小心,因为他带有状态的,漫不经心的操作很可能会造成数据库修改。
  3. 学习成本高,你需要清楚的理解并发处理机制、数据库语句发送时机、缓存,上下文如何脱离和附加,极端的例子就是 JPA 和 微软的 EntityFramework(EF有关闭更改追踪 和 CodeFirst 模式避免这些问题),复杂的 DAO 机制会让很多人无法彻底掌握这些框架。

由于上面的问题,JPA 也备受争议,理解不充分的情况下贸然使用会出现莫名其妙的 BUG,这是我们趟过的坑!!

综上,我们需要一种轻量化、无状态、强类型的编程方式操作数据库,JAVA 环境由于语言描述能力有限,似乎只有 Mybatis Dynamic + Mybatis Generate 一个勉强及格的答案,这也是 Mybatis 这种古老框架存活这么久的原因吧。

贴一段看看 C# 方向的 EntityFramework 的语法,你就知道 JAVA 领域这些框架有多弱:

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };

随着 KOTLIN 语言的出现,JAVA 领域的 ORM 有了更多风骚的操作,Exposed 更是将 kotlin 语法在 ORM 方向发挥到了极致,附上 Exposed 文档,自己感受一下:

https://github.com/JetBrains/Exposed/wiki/DSL

不幸的是官方似乎对 DAO 模型情有独钟,并不打算在 DSL + 简单对象映射的方向上给出方案,所以有了这个轮子。

Quick Start

1. 添加使用生成器

仅对使用 kotlin dsl 的 gradle.build 支持, groovy 风格的配置文件暂未测试兼容性。
在你的 Table 类所在的项目 gradle.build.kts 中加入如下内容:

plugins {
    id("com.google.devtools.ksp") version Versions.kspVersion
}

dependencies {
    ksp("com.labijie.orm:exposed-generator:${Versions.ormVersion}")
}

ksp {
    arg("key", "value")
}

加入 KSP , 同时引入 Infra-ORM 的代码生成器

ksp {
    arg("key", "value")
}

这一部分是 KSP 配置,生成器支持的配置在这里加入(后面会说),默认不需要任何配置也能工作良好。

2. 编写 Table (Schema)类

object UserTable : Table("my") {
   var id = long("id").autoIncrement()
   var name: Column<String> = varchar("name", 50)
   var status = enumeration("status", TestEnum::class)
   var count = integer("count")

   override val primaryKey: PrimaryKey = PrimaryKey(id, name = "user_Id")
}

此时项目目录结构如下

|   build.gradle.kts
|
\---src
    +---main
    |   +---kotlin
    |   |   \---com
    |   |       \---labijie
    |   |           \---orm
    |   |               \---dummy
    |   |                       UserTable.kt
    |   |
    |   \---resources
    \---test
        +---kotlin
        \---resources

3. 编译项目:

编译项目后,目录结构如下:

|   build.gradle.kts
|
\---src
    +---main
    |   +---kotlin
    |   |   \---com
    |   |       \---labijie
    |   |           \---orm
    |   |               \---dummy
    |   |                   |   UserTable.kt
    |   |                   |
    |   |                   \---pojo
    |   |                       |   User.kt
    |   |                       |
    |   |                       \---dsl
    |   |                               UserDSL.kt
    |   |
    |   \---resources
    \---test
        +---kotlin

可以看到生成了 pojo 目录, 同时生成了 User.kt 文件和 UserDSL.kt 文件:

User.kt

public open class User {
   public var id: Long = 0L

   public var name: String = ""

   public var status: TestEnum = TestEnum.OK

   public var count: Int = 0
}

UserDSL.kt

public object UserDSL {
   public fun parseUserRow(raw: ResultRow): User {
      val plain = User()
      plain.id = raw[id]
      plain.name = raw[name]
      plain.status = raw[status]
      plain.count = raw[count]
      return plain
   }

   public fun applyUser(statement: UpdateBuilder<*>, raw: User): Unit {
      statement[id] = raw.id
      statement[name] = raw.name
      statement[status] = raw.status
      statement[count] = raw.count
   }

   public fun applyUser(statement: UpdateStatement, raw: User): Unit {
      statement[id] = raw.id
      statement[name] = raw.name
      statement[status] = raw.status
      statement[count] = raw.count
   }

   public fun ResultRow.toUser(): User = parseUserRow(this)

   public fun Iterable<ResultRow>.toUserList(): List<User> = this.map(UserDSL::parseUserRow)

   public fun UpdateBuilder<*>.apply(raw: User) = applyUser(this, raw)

   public fun UpdateStatement.apply(raw: User) = applyUser(this, raw)

   public fun UserTable.insert(raw: User): InsertStatement<Number> = UserTable.insert {
      applyUser(it, raw)
   }

   public fun UserTable.batchInsert(list: Iterable<User>): List<ResultRow> {
      val rows = UserTable.batchInsert(list) {
            entry -> applyUser(this, entry)
      }
      return rows
   }

   public fun UserTable.update(
      raw: User,
      limit: Int? = null,
      `where`: SqlExpressionBuilder.() -> Op<Boolean>
   ): Int = UserTable.update(where, limit) {
      applyUser(it, raw)
   }
}
  • User.kt 是实体类, 帮助你用简单对象映射到 Exposed 的 ResultRow
  • UserDSL 是数据操作的扩展方法,帮助你自动完成数据映射,简化 CRUD 操作

生成代码包含了 User 对象作为参数的 update, insert, batchInsert, 和一些完成数据映射的帮助器方法, 但似乎还缺少一些东西, 比如 selectByPrimaryKey, deleteByPrimaryKey, updateByPrimaryKey.

如何获得主键方法(SelectById, UpdateById, DeleteById)

由于 KSP 是编译时完成代码结构分析,此时还未生成字节码,所以不具备反射的能力,KSP 的定位也不会提供”赋值“层级的代码分析, 所以分析不了 UserTable 中的代码:

override val primaryKey: PrimaryKey = PrimaryKey(id, name = "user_Id")

简单说,由于无法分析出主键是由 id 这个属性提供的,要读取主键最直接的方式就是在 id 属性上加入注解,这样可以通过 KSP 的 API 进行分析。
但是,加入注解如果只是完成一个简单的 ID 分析似乎有点大材小用了,不妨换了一个思路,加入基类限定主键的属性名称。

这需要引入一个包(有洁癖的请放心,这个包非常干净,只依赖 exposed-core, 这个包目前只有几个基类):

  1. 引入包
dependencies {
    api("com.labijie.orm:exposed-core:${Versions.ormVersion}")
}
  1. 改造一下 UserTable 的代码
import com.labijie.infra.orm.SimpleLongIdTable

object UserTable : SimpleLongIdTable("my", "id") {
    var name: Column<String> = varchar("name", 50)
    var status = enumeration("status", TestEnum::class)
    var count = integer("count")
}

让 UserTable 继承自 SimpleLongIdTable 即可,这样我们通过 KSP 分析代码时候只要发现这个基类,就知道你的主键是 id 属性。

  1. 重新编译项目,你将发现的 UserDSL 类多了三个扩展方法:
public object UserDSL {
  ....

  public fun UserTable.update(raw: User): Int = UserTable.update(raw) {
    UserTable.id eq id
  }

  public fun UserTable.deleteByPrimaryKey(id: Long): Int = UserTable.deleteWhere {
    UserTable.id eq id
  }

  public fun UserTable.selectByPrimaryKey(id: Long): User? {
    val query = UserTable.select {
      UserTable.id eq id
    }
    return query.firstOrNull()?.toUser()
  }
}

至此,已经有了常用的单表操作方法,同时几个用于数据映射的扩展方法也可以协助更好的使用 Exposed DSL 的强大功能。

生成代码样例可以在本项目的的 dummy-project 模块中找到

如何处理多主键?

这不是一个广泛需求,还会为数据库带来减益,暂不打算为了这个意义不大的需求引入一系列注解污染你的代码,你可以考虑如下方式处理这个额问题:

  1. 如果可能,通过程序转换为单主键,例如 md5 hash 多个键值的方式,这样存储是单主键,但是逻辑上还是多主键。
  2. 手写帮助器方法,因为生成的代码已经完成了映射类型的苦差事,多写几个万年不改的扩展方法并不难。

生成器配置

一般情况下默认配置可以良好工作,但也有一些例外,例如你的 Table 类分散在多个包,这样生成代码也会分散各处,可能你需要集中管理这些生成类, 那么以下配置可以帮到你:

为了降低学习成本,Infra-ORM 提供的配置不多,约定大于配置是不变的真理.

参数名 说明
exg_package 生成代码的包命,如果不配置,默认会在你的 Table 类下面构建子 pojo 包,代码文件将放入其中
exg_out_dir 生成代码的目录,必须是绝对路径,如果不配置,默认生成到你的 Table 类所在的项目 kotlin 代码文件目录

exg_out_dir 要求绝对路径,但是你可以通过 gradle.build 中提供的变量得到项目目录,以达到相对路径的效果

👄 注意:exg_out_dir 要配置到 XXX/src/main/kotlin 这个层级,生成器会自动创建包目录

配置使用示例,在 gradle.build.kts 中添加如下代码:

ksp {
    arg("exg_package", "com.github.my.orm")
    arg("exg_out_dir", project.rootProject.childProjects["other"]!!.projectDir.absolutePath)
}

数据库支持

得益于 Exposed 良好的跨数据数据库特性,Infra-ORM 支持以下数据库:

  • H2
  • MySQL
  • MariaDB
  • Oracle
  • PostgreSQL
  • PostgreSQL
  • SQL Server
  • SQLite
You might also like...
⚒ A multiplatform-friendly common utility module targeting code flexibility

⚒ mechanism A multiplatform-friendly common utility module targeting code flexibility. 🎏 Getting Started You can find information about how to use th

Code samples for the second edition of "Kotlin in Action".

Code samples for Kotlin in Action, Second Edition This project contains the code samples from book "Kotlin in Action, Second Edition" by Roman Elizaro

LiteGo is a Java-based asynchronous concurrency library. It has a smart executor, which can be freely set the maximum number of concurrent at same time , and the number of threads in waiting queue. It can also set waiting policies and overload strategies.

LiteGo:「迷你」的Android异步并发类库 LiteGo是一款基于Java语言的「异步并发类库」,它的核心是一枚「迷你」并发器,它可以自由地设置同一时段的最大「并发」数量,等待「排队」线程数量,还可以设置「排队策略」和「超载策略」。 LiteGo可以直接投入Runnable、Callable

This is an Online Book App in which user can read and add their books on favourites fragment and also give rating on it.
This is an Online Book App in which user can read and add their books on favourites fragment and also give rating on it.

BookHub-AndroidApp BookHub Basic Android App Based on the concept of Fragment, Navigation Drawer, Database (Room), Internet Access, etc. See the app o

An Android helper class to manage database creation and version management using an application's raw asset files

THIS PROJECT IS NO LONGER MAINTAINED Android SQLiteAssetHelper An Android helper class to manage database creation and version management using an app

SquiDB is a SQLite database library for Android and iOS

Most ongoing development is currently taking place on the dev_4.0 branch. Click here to see the latest changes and try out the 4.0 beta. Introducing S

lightweight and minimalist ORM for Java/Android. works with SQLite & MySQL. (not actively maintained)

Description ORMAN is an minimalistic and lightweight ORM framework for Java which can handle your common database usage without writing SQL and strugg

Android library for auto generating SQL schema and Content provider

Android-AnnotatedSQL Android library for auto generating SQL schema and Content Provider by annotations. You will get a full-featured content provider

lightweight and minimalist ORM for Java/Android. works with SQLite & MySQL. (not actively maintained)

Description ORMAN is an minimalistic and lightweight ORM framework for Java which can handle your common database usage without writing SQL and strugg

Releases(1.0.8)
Owner
Red Sparrow
红雀小队
Red Sparrow
Upsert DSL extension for Exposed, Kotlin SQL framework

Exposed Upsert Upsert DSL extension for Exposed, Kotlin SQL framework. Project bases on various solutions provided by community in the official "Expos

Dzikoysk 23 Oct 6, 2022
Upsert DSL extension for Exposed, Kotlin SQL framework

Exposed Upsert Upsert DSL extension for Exposed, Kotlin SQL framework. Project bases on various solutions provided by community in the official "Expos

Reposilite Playground 23 Oct 6, 2022
✨ Nifty Utilities and PostgreSQL Extensions for Exposed

✨ ExposedPowerUtils ✨ Utilities and Extensions for Exposed, because while Exposed is a pretty nice framework, it tries to support a lot of SQL dialect

null 9 Nov 8, 2022
AndroidQuery is an Android ORM for SQLite and ContentProvider which focuses on easy of use and performances thanks to annotation processing and code generation

WARNING: now that Room is out, I no longer maintain that library. If you need a library to easy access to default android ContentProvider, I would may

Frédéric Julian 19 Dec 11, 2021
A blazing fast, powerful, and very simple ORM android database library that writes database code for you.

README DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to gener

Andrew Grosner 4.9k Dec 30, 2022
A blazing fast, powerful, and very simple ORM android database library that writes database code for you.

README DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to gener

Andrew Grosner 4.9k Dec 30, 2022
A simple NoSQL client for Android. Meant as a document store using key/value pairs and some rudimentary querying. Useful for avoiding the hassle of SQL code.

SimpleNoSQL A simple NoSQL client for Android. If you ever wanted to just save some data but didn't really want to worry about where it was going to b

Colin Miller 389 Sep 25, 2022
LiteOrm is a fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line of code efficiently.

#LiteOrm:Android高性能数据库框架 A fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line

马天宇 1.5k Nov 19, 2022
Starter code for Android Kotlin Fundamentals Codelab 6.1 Room

TrackMySleepQuality - Starter Code Starter code for Android Kotlin Fundamentals Codelab 6.1 Room Introduction TrackMySleepQuality is an app for record

YamanAswal 0 Jan 15, 2022
⚒ A multiplatform-friendly common utility module targeting code flexibility

⚒ mechanism A multiplatform-friendly common utility module targeting code flexibility. ?? Getting Started You can find information about how to use th

Hexalite Network 3 Aug 31, 2022