[Day 13] 非同步的操作资料库?谈 suspendedTransactionAsync

前面我们聊到了如何存取资料库,以及遇到 N+1 问题时该如何发现以及解决问题。

今天我们来谈谈 Exposed 框架如何非同步的存取资料。

协程

在 Kotlin 程序语言中,支援透过协程(Coroutine)的方式操作,可以在不消耗大量资源的状况下,达成非同步操作的需求。

可惜的是,由於 Exposed 框架的实作上,有一些记忆体储存的位置,是储存在线程内。如果协程在分配时,切换到不同的线程进行操作,可能会导致无法预期的问题。所以我们不能将同一个 transaction() 切换到不同的协程进行操作。

虽然同一个 transaction() 无法切换到不同协程,不过我们可以透过其他方式,来达成非同步的需求。

transaction() 外处理资料

之前的范例内,我们都是在 transaction() 内处理完资料并印出内容。

transaction {  
 	SchemaUtils.create(Users)  
    User.new {  
 		name = "Alice"  
 	}  
 	User.new {  
 		name = "Bob"  
	}
	User  
    .all()  
    .forEach {  
        println("name: ${it.name}")  
    }
}

如果我们希望将资料传输到 transaction() 以外,我们可以在 transaction() 函数前面,用一个变数接收回传值

val users = transaction {
 	SchemaUtils.create(Users)  
    User.new {  
 		name = "Alice"  
 	}
 	User.all().toList()  
}  
println(users.javaClass.kotlin)  
users.forEach{  
 	println(it.name)  
}

这边我们透过 toList(),将原本的 User.all() 内容转换成 java.util.ArrayList 类别。

执行这段程序之後,我们就可以看到 users 的类别和内容

class java.util.ArrayList
Alice

要注意的是,这段程序码到现在还是同步执行的。如果我们对资料库的存取很慢的话,会影响後面程序的运作。

我们用 java.lang.Thread.sleep 来模拟资料库存取很耗时间时的情况。

val users = transaction {  
 	sleep(3000)  
    SchemaUtils.create(Users)  
    User.new {  
 		name = "Alice"  
 	}  
 	User.all().toList()  
}  
val users2 = transaction {  
 	sleep(3000)  
    SchemaUtils.create(Users)  
    User.new {  
 		name = "Bob"  
 	}  
 	User.all().toList()  
}  
val users3 = transaction {  
 	sleep(3000)  
    SchemaUtils.create(Users)  
    User.new {  
 		name = "Carol"  
 	}  
 	User.all().toList()  
}  
(users+users2+users3).forEach{  
 	println(it.name)  
}

这段程序可以成功的印出内容

Alice
Bob
Carol

但是运作起来要消费的时间很长,因为每段程序都必须要等前面的transaction()程序执行完毕,也就是等三秒之後,才会往下执行。

也就是说,要跑完三次transaction(),执行的时间至少需要花费九秒以上,才能执行完成。

suspendedTransactionAsync()

要让程序能够不被 sleep(3000) 卡住,先执行後面的部分,我们可以利用 suspendedTransactionAsync() 函数改写我们的程序。

首先,将我们的 main() 宣告成 suspend 函数

suspend fun main()

再来,我们将原本的 transaction() 改写成 suspendedTransactionAsync()

val users = suspendedTransactionAsync {  
 	sleep(3000)  
    SchemaUtils.create(Users)  
    User.new {  
 		name = "Alice"  
 	}  
 	User.all().toList()  
}  
  
val users2 = suspendedTransactionAsync {  
 	sleep(3000)  
    SchemaUtils.create(Users)  
    User.new {  
 		name = "Bob"  
	}  
 	User.all().toList()  
}  
  
val users3 = suspendedTransactionAsync {  
 	sleep(3000)  
    SchemaUtils.create(Users)  
    User.new {  
 		name = "Carol"  
 	}  
 	User.all().toList()  
}

这个函数回传的内容,就不是我们之前所取得的 ArrayList 了。我们可以实际印出类别名称看看

println(users.javaClass.kotlin)

会得到

class kotlinx.coroutines.DeferredCoroutine

并且我们执行时会发现到,印出println(users.javaClass.kotlin) 内容的时间变得很快。似乎没有受到 sleep(3000) 的影响。

这是因为 suspendedTransactionAsync() 这个函数并没有直接提供给我们和资料库互动所取出的内容,而是先回传一个 DeferredCoroutine 物件,程序就直接往下执行了。

要和资料库互动,将 DeferredCoroutine 物件变成 ArrayList 物件,我们要透过 await() 函数来改写我们的程序

(users.await() 
+ users2.await() 
+ users3.await()).forEach {  
 	println(it.name)  
}

这样执行後,一样可以取得我们的内容。并且由於这三段没有互相等待对方执行的时间,所以执行时间会比起原先要短,不需要等九秒钟以上才能执行完毕。


<<:  Day03-Nginx 简介

>>:  【Day03-表格】为什麽熊猫(pandas)是用来处理表格的工具?

Day 16:Next 布景客制化 - 让副标题显示於标题内

Hexo 网站设定当中,除了有标题的设定外,还有所谓的「副标题」来辅助主标题外想补充说明的内容。比方...

Day 24 : Jenkins 在Build完通知与好用套件

介绍Jenkins的章节即将进入尾声了。事实上你可能会想Jenkins默认介面这麽老气,怎麽就成为全...

【左京淳的JAVA WEB学习笔记】第十三章 购物车

购物车采用session储存,结构为Map<String,Integer>。Key为is...

【Day 22】 实作 - 如何在 AWS Quicksight Join 不同资料源

大家午安 ~ 刚刚打开介面发文时,看到有 iThome 邦友订阅文章,真的是无比开心 Q 感谢大家的...

【程序】软件测试 Testing 转生成恶役菜鸟工程师避免 Bad End 的 30 件事 - 20

测试 为什麽要写测试 何时该订定测试 和哪些人协作 ...