Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
R2DBCを導入しようとした話
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Tasuku Nakagawa
September 28, 2023
Programming
480
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
R2DBCを導入しようとした話
Tasuku Nakagawa
September 28, 2023
More Decks by Tasuku Nakagawa
See All by Tasuku Nakagawa
デッドコード消せてますか?構文解析とGradleプラグイン開発で始めるコードベース改善
t45k
4
2k
withContextってスレッド切り替え以外にも使えるって知ってた?
t45k
3
3.1k
DB呼び出し回数を減らしてコア機能を高速化した話
t45k
0
350
コードレビュー改善のためにJenkinsとIntelliJ IDEAのプラグインを自作してみた話
t45k
0
170
Other Decks in Programming
See All in Programming
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
600
Turning Architecture into Unit Tests in the AI Era (NSSpain XIV)
steliosf
PRO
1
120
Heart of Swift Concurrency
koher
0
1.2k
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
140
標準パッケージに uuid が追加された 背景から見る Go らしい意思決定 / go_127_uuid_decision
convto
5
9.5k
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
520
Androidだけじゃない、DroidKaigiで広がった私の技術スタック
akkeylab
0
110
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
410
WebRTC映像をAirPlayに対応させる挑戦.pdf
monolithic_adam
0
330
AGENTS.md Is Not Enough:Build Skills, Don't Download Them
lx_t
0
130
AHC070解法紹介
eijirou
0
150
Embedded Swiftで作る自作USBデバイス によるiOSデバイスの自動テスト / iOSDC Japan 2026 glassfiber
glassfiber
0
270
Featured
See All Featured
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.9k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.4k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
440
My Coaching Mixtape
mlcsv
0
340
AI Search: Where Are We & What Can We Do About It?
aleyda
0
8k
Exploring anti-patterns in Rails
aemeredith
4
520
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
570
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
910
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
How Software Deployment tools have changed in the past 20 years
geshan
2
35k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
[SF Ruby Conf 2025] Rails X
palkan
3
1.4k
Transcript
R2DBCΛ ಋೖ͠Α͏ͱͨ͠ 2023/09/29ʢۚʣ αʔόʔαΠυKotlin LTେձ vol.10 גࣜձࣾϚωʔϑΥϫʔυ Task
Task / Tasuku Nakagawa 20237݄͔Βݱ৬ େࡕڌۈ GitHub: T45K X (چTwitter):
getupmax ࢿྉ: https://speakerdeck.com/t45k/r2dbcwodao-ru-suruhua
ϚωʔϑΥϫʔυΫϥυ ࿈݁ձܭ
KotlinʢJVMʣͷݟ͕ͳ͍ঢ়ଶͰ։ൃ։࢝ →͍͔ͭ͘ͷΓ͍ͨ͜ͱΛఘΊͯϦϦʔε Ұ݅ͷܖͰϢʔβ͕Ͳͬͱ૿͑Δ →ૣ͍͏ͪʹεέʔϧ͢ΔΑ͏ʹ͍ͨ͠ ϚωʔϑΥϫʔυΫϥυ ࿈݁ձܭ
DBΞΫηεΛϊϯϒϩοΩϯάͰΔͭ ↕︎ JDBC R2DBC
• ϋϚΓϙΠϯτ • ʢݱঢ়ͷʣղܾࡦ ӡ༻ͷ͠·ͤΜʢ·ͩͰ͖ͳ͍ʣ R2DBCಋೖ
• Kotlin 1.9.0 • Spring Boot 3.0.5 • Spring WebFlux
• jOOQ 3.7.5 • MySQL ελοΫ
ϦϙδτϦ
@Repository class Repository(private val ctx: DSLContext) { fun findById(id: Long):
Record? = ctx.selectFrom(TABLE) .where(ID.eq(id)) .fetchOne() } DSLͰDB͍߹Θ͕ͤͰ͖ΔORM jOOQͱ
jOOQR2DBCʹରԠࡁΈ https://blog.jooq.org/reactive-sql-with-jooq-3-15-and-r2dbc/
dependencies { runtimeOnly("io.asyncer:r2dbc-mysql:1.0.2") runtimeOnly("io.r2dbc:r2dbc-pool:1.0.1.RELEASE") // ͪ͜Βݱࡏ։ൃ͕ࢭ·͍ͬͯΔ // runtimeOnly("dev.miku:r2dbc-mysql:0.8.2.RELEASE") } ґଘ
build.gradle.kts
spring: r2dbc: url: r2dbc://{jdbcͱಉ͡ઃఆ} username: username password: password pool: enabled:
true ઃఆ application.yaml
@Configuration class JooqConfiguration( private val connectionFactory: ConnectionFactory) { @Bean fun
dslContext(): DSLContext = DSL.using(connectionFactory).dsl() } jOOQ Config
jOOQͰR2DBCΛ ͏४උͬͨ
jOOQ 3.17͔Β coroutineΛαϙʔτ https://www.jooq.org/doc/3.17/manual/sql-building/kotlin-sql-building/kotlin-coroutines/
@Repository class Repository(private val ctx: DSLContext) { suspend fun findById(id:
Long): Record? = ctx.selectFrom(TABLE) .where(ID.eq(id)) .awaitFirstOrNull() } jOOQ 3.17ͷมߋ ΫΤϦ͕ PublisherΛฦ͢ Coroutineʹ มՄೳ
ͳ͍
jOOQ 3.17.5࣌Ͱ ࣮͞Ε͍ͯͳ͍ ʢηϚϯςΟοΫόʔδϣχϯά...ʣ
jOOQ 3.17.15 or 3.18ܥΛ ͏ͱղܾ
fetch/fetchOneΛ ଟ༻͍ͯ͠Δ
fetch/fetchOne ϒϩοΩϯά͢Δ શͯΛselectʹ ॻ͖͑Δͷ໘
fetch/fetchOneޓͷ ϝιουΛ४උͯ͠Ұׅஔ suspend inline fun <reified R : TableRecord<*>, T
: TableImpl<R>> DSLContext.nonBlockingFetchOne( tableImpl: T, vararg condition: Condition ): R? = this.select() .from(tableImpl) .where(*condition) .awaitFirstOrNull() ?.map { it.into(R::class.java) }
τϥϯβΫγϣϯ
ݱঢ়ɺR2DBCͰ @Transaction ͑ͳͦ͞͏
R2DBC༻ͷ τϥϯβΫγϣϯཧ
class UseCase( private val tx: TransactionalOperator, private val repo: Repository,
) { suspend fun call() = tx.execute(mono { repo.save() }) } Spring Frameworkۘ Ҿ͕Mono TransactionOperator
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine
લऀjOOQඇରԠ ࣮࣭ޙऀҰ
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine ͜Ε͚ͩͰޮ͔ͳ͍
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { config -> repo.save(config.dsl()) } } ίʔϧόοΫͰDSLContextΛऔಘ ͦΕʹରͯ͠ΫΤϦΛ࣮ߦ͠ͳ͍ͱ͍͚ͳ͍ transactionCoroutineͷ᠘
όέπϦϨʔʁ class UseCase( private val ctx: DSLContext, private val service:
Service, ) { suspend fun call() = ctx.transactionCoroutine { config -> service.do(config.dsl()) } } class Service( private val repo: Repository, ) { suspend fun do(ctx: DSLContext) { repo.save(ctx) } }
όέπϦϨʔʁ class UseCase( private val ctx: DSLContext, private val service:
Service, ) { suspend fun call() = ctx.transactionCoroutine { config -> service.do(config.dsl()) } } class Service( private val repo: Repository, ) { suspend fun do(ctx: DSLContext) { repo.save(ctx) } } όέπϦϨʔ όέπϦϨʔ
ίʔϧόοΫͰݺΕͨ Repository DSLContextΛ Γସ͍͑ͨ
CoroutineContextͰ ࣋ͪճ͢
class TransactionElement(val ctx: DSLContext) : AbstractCoroutineContextElement(TransactionElement) { companion object Key
: CoroutineContext.Key<TransactionElement> } DSLContextΛอ࣋͢Δ CoroutineContext
@Component class TransactionManager(private val ctx: DSLContext) { suspend fun <T>
execute(block: suspend () -> T): T = ctx.transactionCoroutine { config -> withContext(coroutineContext + TransactionElement(config.dsl())) { block() } } } } ϚωʔδϟͰContextΛ߹
@Repository class Repository(private val ctx: DSLContext) { suspend fun getCtx()
= coroutineContext[TransactionElement.Key]?.ctx ?: this.ctx suspend fun save() { getCtx()... // CRUDॲཧ } } RepoͰ͏ctxΛΓସ͑
AOP
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { repo.find() ?: throw RuntimeException() } } AOPͰڞ௨ॲཧΛ͍ͯ͠Δ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { repo.find() ?: throw RuntimeException() } } AOPͰڞ௨ॲཧΛ͍ͯ͠Δ ϦϙδτϦͷϝιου͕ suspendؔʹͳͬͨ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { runBlocking { repo.find() } ?: throw RuntimeException() } } runBlocking͢Δʁ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { runBlocking { repo.find() } ?: throw RuntimeException() } } runBlocking͢Δʁ ελοΫ͢Δ ʢݪҼෆ໌ʣ
Coroutineͷ࣮ߦʹ ׂΓࠐ·͍ͤͨ
suspend fun hoge(value: String){ } public static final Object hoge(
String value, Continuation $completion) { return Unit.INSTANCE; } suspendؔ Java͔Β͜͏ݟ͑Δ
suspend fun hoge(value: String){ } public static final Object hoge(
String value, Continuation $completion) { return Unit.INSTANCE; } suspendؔ Java͔Β͜͏ݟ͑Δ Continuation͕ Coroutineͷ͋Ε͜ΕΛ࣋ͭ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } }
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } } JoinPoint͔Β ContinuationΛύΫΔ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } } ύΫͬͨContinuation্Ͱ DBίʔϧ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } } ͦͷ··JoinPoint࣮ߦ
Spring Framework 6.1͔Β αϙʔτ͞ΕΔ༷😇 https://github.com/spring-projects/spring-framework/issues/22462
ͦͷଞ
• ஈ֊తʹमਖ਼͍͖͍ͯͨ͠ • ݁߹ςετͰɺJDBCͰDBΛݕূ͍ͨ͠ • suspendؔʹͰ͖ͳ͍͕ؔ͋Δ JDBCͱR2DBCͷซ༻
@Configuration class JooqConfiguration( private val connectionFactory: ConnectionFactory) { @Bean fun
dslContext(): DSLContext = DSL.using(connectionFactory).dsl() @Bean("jdbcDSLContext") fun jdbcDslContext( @Value("\${spring.datasource.url}") url: String, @Value("\${spring.datasource.username}") username: String, @Value("\${spring.datasource.password}") password: String, ): DSLContext = DefaultDSLContext( DataSourceBuilder.create().url(url).username(username) .password(password).build(), SQLDialect.MYSQL) } ࣗલͰ༻ҙ͢Δ
@Repository class Repository( private val ctx: DSLContext, @Qualifier("jdbcDSLContext") private val
jdbcCtx: DSLContext, ) { fun findById(id: Long): Record? = ctx… fun blockingFindById(id: Long): Record? = jdbcCtx… } ϦϙδτϦ
ىಈ࣌ʹUnknownHostException NettyͷDNSϦκϧό Caused by: java.net.UnknownHostException: Failed to resolve 'my_db:3306' [A(1)]
after 2 queries at io.netty.resolver.dns.DnsResolveContext.finishResolve (DnsResolveContext.java:1088)
DBͷϗετ໊ʹ”_”ؚ͕·Ε͍͚ͯͳ͍ Docker Compose͍ͬͯΔ߹ҙ NettyͷDNSϦκϧό
• jOOQ৽͍͠όʔδϣϯΛ͏ • CoroutineContextΛ׆༻ͯ͠ τϥϯβΫγϣϯΛཧ͢Δ • AOPͰContinuationΛ͍ճ͢ • JDBCͱซ༻ͯ͠ஈ֊తʹमਖ਼͢Δ ·ͱΊ
• QA • ϦϦʔεޙελοΫ͠ͳ͍͜ͱΛفΔ • ύϑΥʔϚϯεܭଌ͍ͨ͠ ࠓޙ
WE’RE HIRING!! https://hrmos.co/pages/moneyforward/jobs?category=1666322478647443458,1666323214451404802
None