Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataBase 和Query解耦 #41

Closed
jiaojing opened this issue Sep 2, 2019 · 9 comments
Closed

DataBase 和Query解耦 #41

jiaojing opened this issue Sep 2, 2019 · 9 comments
Labels
enhancement New feature or request

Comments

@jiaojing
Copy link

jiaojing commented Sep 2, 2019

query,最后map的时候会调用global的DataBase执行。是否可以类似:slick
// DB.run(DBIOActions) 这种方式。

项目非常棒,💪

@vincentlauvlwj
Copy link
Member

这是可以的,看看这段代码是不是你想要的效果?

val db = Database.connect(url = "jdbc:h2:mem:ktorm;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")

val names = db {
    Employees
        .select(Employees.name)
        .where { Employees.departmentId eq 1 }
        .orderBy(Employees.salary.desc())
        .map { it.getString(1) }
}

assert(names.size == 2)
assert(names[0] == "vince")
assert(names[1] == "marry")

@jiaojing
Copy link
Author

jiaojing commented Sep 3, 2019

嗯,我看到了,可以这样写。但是这个db是通过threadlocal的方式传递给map的。如果不用这个db{}包裹代码块,容易有坑(代码也不会提示不可以不包裹),比如在多线程执行的时候。 另外,把query定义为一个SQL构造器,而不直接map执行。用类似这样的方式进行绑定,个人认为代码风格更好些。

db.from(Employees)
    .where { e -> (e.organizationId ne null) and (e.name eq "John Doe") }
    .groupBy { e -> e.name }
    .having { e -> e.id ne null }
    .orderBy { e -> e.name.asc .. e.id.desc }
    .limit { 10 }
    .offset { 10 }
    .select { e -> e.id .. e.name }

或者类似slick这样:

val setup = DBIO.seq(
  // Create the tables, including primary and foreign keys
  (suppliers.schema ++ coffees.schema).create,

  // Insert some suppliers
  suppliers += (101, "Acme, Inc.",      "99 Market Street", "Groundsville", "CA", "95199"),
  suppliers += ( 49, "Superior Coffee", "1 Party Place",    "Mendocino",    "CA", "95460"),
  suppliers += (150, "The High Ground", "100 Coffee Lane",  "Meadows",      "CA", "93966"),
  // Equivalent SQL code:
  // insert into SUPPLIERS(SUP_ID, SUP_NAME, STREET, CITY, STATE, ZIP) values (?,?,?,?,?,?)

  // Insert some coffees (using JDBC's batch insert feature, if supported by the DB)
  coffees ++= Seq(
    ("Colombian",         101, 7.99, 0, 0),
    ("French_Roast",       49, 8.99, 0, 0),
    ("Espresso",          150, 9.99, 0, 0),
    ("Colombian_Decaf",   101, 8.99, 0, 0),
    ("French_Roast_Decaf", 49, 9.99, 0, 0)
  )
  // Equivalent SQL code:
  // insert into COFFEES(COF_NAME, SUP_ID, PRICE, SALES, TOTAL) values (?,?,?,?,?)
)

val setupFuture = db.run(setup)
  1. data class -> entity
  2. object table -> table schema 定义
  3. query表达式构造
  4. db.run

前3步都是和执行环境无关的,抽离之后,后续切换到coroutine 执行,只需要改进第4部分。

@vincentlauvlwj
Copy link
Member

谢谢你的建议,把 query 表达式的构造与执行环境分离确实有很大的意义,我会考虑如何改进

@jiaojing
Copy link
Author

jiaojing commented Sep 3, 2019

我们刚开始在服务端使用kotlin,评估下来,ktorm是orm这块做的比较好的,也感谢你对项目的付出。

@785172550
Copy link

785172550 commented Oct 20, 2019

@jiaojing 我也在尝试用coroutine来执行sql, 但是感觉需要用异步的DB调用,而不是JDBC这种会阻塞线程的才有效,要不然也只是相当于在一个线程池里面做DB调用(这样同时的并发数,就是线程池数量与数据库连接池数量的两者最小值),我找到了这个数据库driver,
https://github.com/eclipse-vertx/vertx-sql-client
但是现在和ktorm不能结合,因为它不是JDBC协议的,我觉的能将构造sql表达和执行分离,就能用ktorm构造sql和将rowset转换为entity, 然后用vertx-sql-client 来执行数据库调用了,这样ORM框架就彻底和数据库driver层面解耦了

@jiaojing
Copy link
Author

jiaojing commented Jan 8, 2020

我看了一下这个 rm-global-database-object分支的代码,非常赞。关于coroutine这块:
目前数据库的异步的驱动,也就jasync-sql这一个库,执行SQL以后返回的是CompletableFuture。这个很容易就可以转换成suspend函数。
Ktorm要支持的话,目前这个结构可能还需要修改。
目前Ktorm的query最终会在map,count等iterator的方法调用的时候,lazy 初始化一个iterator,这时会执行数据库查询操作。
image
但是底层用jasync-sql的话,database.executeExpression这个函数签名肯定就是suspend的了,就会传染整个调用链路。从这个作为突破口,可能会比较容易支持coroutine。有一个不成熟的建议是:query构造和db彻底解耦。db.run(query),或者asyncdb.run(query).后者方法签名是suspend的。

@vincentlauvlwj
Copy link
Member

是这样的,异步和同步是两个完全不一样的世界,很多库都没办法做到完美地同时支持两者。

我的想法是,与 Database 对应,增加一个 AsyncDatabase,它们分别支持异步和同步。但是这样的话 database.from(..)database.sequenceOf(..) 就要改,增加 AsyncQueryAsyncEntitySequenceAsyncSequenceGrouping 等一系列的 counterpart,这样一套下来,几乎所有的代码我们都要写两遍。

所以,与其这么做,我觉得还不如另外建一个 ktorm-async 的项目,一个支持异步的专门的 Ktorm 版本。反正代码都要重新写一遍,我不如直接把同步的 Ktorm 和异步的 Ktorm 分为两个项目,它们之间顶多就共享 SqlExpressionSqlFormatter 之类的这些代码,也就是你说的 query 构造部分。

你看看这个思路如何?

@jiaojing
Copy link
Author

嗯,也是一种方案。核心的就是SqlExpresion。然后DB执行他。

我去看了一下slick的方式。他是把query的拼接,数据库的其他操作,都和执行环境全部分离出来。 然后DB.run(DBIOAction)-> future 这种方式把所有最终的执行几乎都放到这一个函数里去了。这个函数返回的是future,也就是底层是同步(那就得自己用线程池转成异步的)或者异步的驱动都可以。所有的terminal 函数,最终调用这个run得到一个future。然后future就可以很简单转成suspend了。

不过这个我感觉不着急,kotlin目前还不成熟。我估计得等1.4以后,看看哪个版本kotlin的 multi-platform做好以后,coroutine的使用场景才会出来吧。目前也就Android下能用。服务端基本上都是spring的生态,基本用不到。

@jiaojing
Copy link
Author

jiaojing commented Jul 8, 2021

最近翻了一下代码,其实query本身的构造,是不会suspend的。就是一个普通的函数。database.run(query)这个涉及到网络io,肯定是suspend的。之后获得一个QueryResult,然后再这个之后所有的数据结果的处理,变换按照我的理解,也是一个普通函数,不会suspend。也就是说整个coroutine控制在db的run方法上。这个database根据底层driver情况,提供两套执行的api其实就是suspend runAsync(query),runSync(query).然后使用者自行选择就行。

`kotlin
public suspend fun Database.run(query: Query): QueryResult = QueryResult(this, query)

public suspend fun Query.runBy(database: Database): QueryResult = QueryResult(database, this)

`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants