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
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
2.1k
withContextってスレッド切り替え以外にも使えるって知ってた?
t45k
3
3.1k
DB呼び出し回数を減らしてコア機能を高速化した話
t45k
0
350
コードレビュー改善のためにJenkinsとIntelliJ IDEAのプラグインを自作してみた話
t45k
0
170
Other Decks in Programming
See All in Programming
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
180
When benchmarks go bad - what I learned from measuring performance wrong
hollycummins
0
220
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
380
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
750
複雑なUIコンポーネントとの ⻑い付き合い⽅:波及するバグからテスト戦略を経て、安全な再設計へ
mixplace
0
430
ChromeでローカルLLM
ryu1013
0
140
dotNET_vulnclicker_intro.pdf
deka_morita
0
140
Agents on Rails - Rails at Scale 2026
irinanazarova
0
570
AWSに止められる覚悟してますか?
morizo_1984
2
540
現場と育てるRailsモデリング 〜Spreadsheetから始めたモデル設計〜
siroemk
2
630
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
35
17k
phperだってパンチカードでコーディングしたい
o0h
PRO
0
350
Featured
See All Featured
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
800
Typedesign – Prime Four
hannesfritz
42
3.2k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.2k
The SEO identity crisis: Don't let AI make you average
varn
0
590
brightonSEO & MeasureFest 2025 - Winning Strategies for Black Friday CRO & PPC - Christian Goodrich
cargoodrich
3
870
How to train your dragon (web standard)
notwaldorf
96
6.9k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
360
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
540
The Invisible Side of Design
smashingmag
301
52k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
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