Share
Embed
Transcript
61 slides
-
Task / Tasuku Nakagawa 20237݄͔Βݱ৬ େࡕڌۈ GitHub: T45K X (چTwitter):
getupmax ࢿྉ: https://speakerdeck.com/t45k/r2dbcwodao-ru-suruhua -
• 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ͱ -
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 -
@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ʹ มՄೳ -
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) } -
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 -
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) } } όέπϦϨʔ όέπϦϨʔ -
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ΛΓସ͑ -
@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͢Δʁ ελοΫ͢Δ ʢݪҼෆ໌ʣ -
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࣮ߦ -
@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)