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

[Refactor/#235] jooq 관련 수정 #237

Merged
merged 22 commits into from
Jul 30, 2024
Merged

Conversation

belljun3395
Copy link
Collaborator

@belljun3395 belljun3395 commented Jul 23, 2024

🎫 연관 이슈

resolved: #235

💁‍♂️ PR 내용

  • jooq 관련 수정

🙏 작업

작업 관련 내용 기록 하였는데 블로그로 대체합니다.

https://belljundev.tistory.com/24

🙈 PR 참고 사항

📸 스크린샷

스크린샷 2024-07-23 오후 12 56 20

설정 변경해도 요청은 잘 동작합니다.

스크린샷 2024-07-23 오후 4 48 27

이벤트로 변경하니까 첫 번째 슬로우 쿼리에는 request url을 MDC에서 가져올 수 있는데 이후 쿼리는 불가능하네요.!


🤖 테스트 체크리스트

  • 체크 미완료
  • 체크 완료

@belljun3395 belljun3395 requested a review from hun-ca as a code owner July 23, 2024 07:21
@github-actions github-actions bot added config 설정 파일과 관련된 내용을 다룰 때 사용됩니다 refactor 기존 기능에 대해 개선할 때 사용됩니다. labels Jul 23, 2024
@belljun3395 belljun3395 changed the title Refactor/#235 belljun3395 [WIP][Refactor/#235] jooq 관련 수정 Jul 23, 2024
@belljun3395 belljun3395 changed the title [WIP][Refactor/#235] jooq 관련 수정 [Refactor/#235] jooq 관련 수정 Jul 23, 2024
Copy link
Member

@hun-ca hun-ca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서비스 고도화를 위해 힘써주셔서 감사합니다~ 아래 커맨트 확인 부탁드려요!

Comment on lines 9 to 18
class PerformanceListener(
private val applicationEventPublisher: ApplicationEventPublisher,
) : ExecuteListener {
private val log = KotlinLogging.logger {}

private var watch: StopWatch? = null
override fun executeStart(ctx: ExecuteContext) {
super.executeStart(ctx)
watch = StopWatch()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PerformanceListener 이거 싱글톤으로 동작하나요? 그럼 watch 변수에 뭔가를 할당하면 객체 상태를 갖게돼서 코드수정 필요합니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PerformanceListener 이거 싱글톤으로 동작하나요?
네! JooqConfig의 configuration 빈 생성시에만 사용되어 결국 싱글톤으로 생성됩니다.

    public StopWatch() {
        this.start = System.nanoTime();
        this.split = start;
    }

StopWatch 객체를 생성하는 순간 start에 현재 시간을 저장하고 executeStart와 executeEnd에서 watch를 공유해야 쿼리 속도를 측정할 수 있어서 위와 같이 구현했습니다.

어떻게 수정하면 좋을지 아직 감이 안잡히는데 어떻게 하면 좋을까요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PerformanceListener 이게 싱글톤이면 watch 변수는 모든 요청에서 공유하기 때문에 시간 측정이 이상하게 될거 같아요. ThreadLocal로 받거나 executeStart 메소드 인자로 넘어오거나 하는 등의 방법으로 변경 필요할거 같습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스크린샷 2024-07-26 오후 10 36 17

요거는 jOOQ에서 제공하는 StopWatchListener 처럼 바꾸는게 좋을 것 같은데 어떤가요?
StopWatchListener를 상속해서 구현하지 못하는 것은 stopwatch 가 private 이어서!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스크린샷 2024-07-26 오후 10 41 55

사진 처럼 요청마다 새로운 객체를 생성해서 처리해요!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뭔가 이상함. SQLErrorCodeSQLExceptionTranslator 객체가 하나인데 저 watch 변수를 요청마다 만드는거면 요청이 여러개 동시에 왔을떄 생각해봤을 떄, 첫 번쨰 요청 때 watch 만들고 첫 번쨰 요청 끝나기 전에 두번째 요청이 watch 변수 바꾸면?.. SQLErrorCodeSQLExceptionTranslator이게 싱글톤이 아닌지 확인 필요해보임

Comment on lines +20 to +21
RepoAlterArgs(
requestURL = MDC.get("Request-URL") ?: "",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MDC.get("Request-URL") 여기서 MDC가 가지는 컨텍스트 데이터가 요청을 수행하는 그 쓰레드 하나에 종속될까요? 그렇다면 @async로 동작하는 해당 메소드에서 현재 요청에 대한 컨텍스트를 어떻게 가져올 수 있는지 궁금하네요...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 구현에서는 여러 쿼리에서 슬로우 쿼리가 발생하는 경우 첫 이벤트를 제외한 다른 처리에서는 MDC 컨텍스트가 공유되지 않는 문제가 있었는데 (PR 사진 참고)

https://blog.gangnamunni.com/post/mdc-context-task-decorator/
위의 블로그를 참고해서 6885ad4 에서 추가 구현했습니다.

class ClonedTaskDecorator : TaskDecorator {
    override fun decorate(runnable: Runnable): Runnable {
        val contextMap = MDC.getCopyOfContextMap()
        return Runnable {
            if (contextMap != null) {
                MDC.setContextMap(contextMap)
            }
            runnable.run()
        }
    }
}
    @Bean(DISCORD_HOOK_EVENT_POOL)
    fun discordHookThreadPool() = ThreadPoolTaskExecutor().apply {
        val properties = disCordThreadPoolProperties()
        corePoolSize = properties.getCorePoolSize()
        maxPoolSize = properties.getMaxPoolSize()
        queueCapacity = properties.getQueueCapacity()
        setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown())
        setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds())
        setThreadNamePrefix("discordHookThreadPool-")
        setRejectedExecutionHandler { r, _ ->
            log.warn { "Discord Hook Event Task Rejected: $r" }
        }
        setTaskDecorator(ClonedTaskDecorator()) // <- decorator 설정
        initialize()
    }

위의 추가된 두 코드로 슬로우 쿼리 발생시의 MDC 컨텍스를 비동기로 디스코드 훅을 보내는 스레드 풀에서도 동일한 MDC 를 공유할 수 있습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네넵 확인했습니다

Comment on lines 9 to 10
private val log = KotlinLogging.logger {}
override fun executeEnd(ctx: ExecuteContext) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변수랑 메소드 붙어있는거보니까 요거 린트 안맞춰진거 같어요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6bc7005 에서 반영했습니다.

Comment on lines 5 to 12
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
import org.springframework.jdbc.support.SQLExceptionTranslator

class ExceptionTranslator : ExecuteListener {

override fun exception(context: ExecuteContext) {
val dialect = context.configuration().dialect()
val translator: SQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator(dialect.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLErrorCodeSQLExceptionTranslator 요거 매번 생성하면서 사용하는거 맞나요? 싱글톤 기반의 빈으로 되어있을거 같은 느낌이 약간 들어서요. dialect도 우리지금 MySQL이니까 모든 쿼리 동일할거구..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 지금 slow query에 대해 예외가 발생하는건 아닌데 왜 SQLErrorCodeSQLExceptionTranslator 가 사용되나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    @Bean
    fun configuration(): DefaultConfiguration {
        val jooqConfiguration = DefaultConfiguration()
        jooqConfiguration.set(connectionProvider())
        jooqConfiguration.set(DefaultExecuteListenerProvider(exceptionTransformer()))
        jooqConfiguration.set(NativeSQLLogger(), PerformanceListener(applicationEventPublisher))
        jooqConfiguration.set(SQLDialect.MYSQL)
        return jooqConfiguration
    }

요 코드에서 사용되니 싱글톤으로 생성될 것이라 생각합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 지금 slow query에 대해 예외가 발생하는건 아닌데 왜 SQLErrorCodeSQLExceptionTranslator 가 사용되나요?

슬로우 쿼리를 위해서 SQLErrorCodeSQLExceptionTranslator를 추가한 것은 아니고 벤더마다 다를 수 있는 SQL 에러를 스프링에서 제공하는 에러로 통일해서 처리하기 위해서 추가했습니다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매번 exception이 호출될 떄마다 SQLErrorCodeSQLExceptionTranslator를 생성하는건 좋지 않아보입니다. SQLErrorCodeSQLExceptionTranslator 객체는 싱글톤 기반 빈으로 두고 매 호출마다 공유하도록 하면 좋을거 같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스크린샷 2024-07-26 오후 10 28 10

ExceptionTranslator 가 AbstractQuery에서 사진처럼 동작하더라고요!
저는 ExceptionTranslator가 catch에서 예외를 잡아서 예외를 SQLErrorCodeSQLExceptionTranslator로 변환만하는 것이라 ExceptionTranslator도 괜찮다고 보이는데 어떠신가요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오케이. 네이밍은 그렇다 치고 매번 exception이 호출될 떄마다 SQLErrorCodeSQLExceptionTranslator를 생성하는거에 대해서 생각해봐여 할듯. 말그대로 MySQL로 짜여진 SQL 번역기 인데, 이게 현재 실행된 쿼리마다 존재하는건 이상해보여요. SQLErrorCodeSQLExceptionTranslator 객체는 싱글톤이 되어야 하는게 맞아보임

Copy link
Collaborator Author

@belljun3395 belljun3395 Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

val dialect = context.configuration().dialect()

jooQ가 여러 데이터 베이스에 연결해서 사용할 수 있다고 아는데
그래서 context에 dialect 정보가 있는거 같아요
아마 그래서 런타임에 context의 dialect를 조회해서 SQLErrorCodeSQLExceptionTranslator를 만드는게 아닌가 합니다.!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우린 MySQL로 고정 사용하기 때문에 SQLErrorCodeSQLExceptionTranslator를 MySQL 문법으로 미리 하나 생성해두고 사용하는건 안되려나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0b8044f 에서 반영했습니다.


override fun executeEnd(ctx: ExecuteContext) {
super.executeEnd(ctx)
if (watch!!.split() > 5000000000L) { // 5 seconds
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5000000000L 값은 따로 어디 빼두는게 관리 편의성 차원에서 좋아보입니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7156b1c 에서 반영했습니다.

Comment on lines +20 to +50
fun announceRepoAlter(args: RepoAlterArgs) {
val embedsList = ArrayList<Embed>()
args.let {
embedsList.add(
Embed(
title = "Exception",
description = "Slow Query Detected"
)
)
it.requestURL.let { requestURL ->
embedsList.add(
Embed(
title = "Request URL",
description = requestURL
)
)
}
it.query?.let { query ->
embedsList.add(
Embed(
title = "Slow Query Detected",
description = query
)
)
}
}
args.let {
DiscordBodyProperty(
content = "😭 DB 이상 발생",
embeds = embedsList
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 자체는 slow query에 종속적인 코드인데 모듈은 API 모듈에 포함되어서 약간 모듈이 안나눠진 느낌입니다. 디스코드 웹훅을 쏘는 역할을 API에 다 두고 그 컨텐츠는 api-repo에서 만들어서 던지던지 하는건 어떨까요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 자체는 slow query에 종속적인 코드인데 모듈은 API 모듈에 포함되어서 약간 모듈이 안나눠진 느낌입니다.

저도 그렇게 생각해요!

디스코드 웹훅을 쏘는 역할을 API에 다 두고 그 컨텐츠는 api-repo에서 만들어서 던지던지 하는건 어떨까요

요 말을 아직 이해를 잘 못했습니다..!


제가 구현하면서 생각한 방법은 client를 별도의 모듈로 분리하는 거였어요
그럼 슬로우 쿼리 발생 이벤트를 api-repo 안에서 바로 처리할 수 있으니까요.
그리고 나중에 다른 모듈에서도 client를 사용해야하는 경우가 생길 수 있는데 그런 중복 코드도 줄일 수 있을 것 같고요!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클라이언트 모듈보단 common 성으로 들어가야 나중에 서비스 분리할 때에도 편리할거 같어요. 근데 일단은 모듈 생성보단, API 모듈에 둬도 될거 같은게 디코로 send하는 역할만 API 모듈에 두고 그 컨텐츠가 뭐가 됐든 send만 할 수 있게 구현하는거에요. 그담 다른 모듈에서 컨텐츠를 만들고

Copy link
Collaborator Author

@belljun3395 belljun3395 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 감사합니다!

Comment on lines 5 to 12
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
import org.springframework.jdbc.support.SQLExceptionTranslator

class ExceptionTranslator : ExecuteListener {

override fun exception(context: ExecuteContext) {
val dialect = context.configuration().dialect()
val translator: SQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator(dialect.name)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    @Bean
    fun configuration(): DefaultConfiguration {
        val jooqConfiguration = DefaultConfiguration()
        jooqConfiguration.set(connectionProvider())
        jooqConfiguration.set(DefaultExecuteListenerProvider(exceptionTransformer()))
        jooqConfiguration.set(NativeSQLLogger(), PerformanceListener(applicationEventPublisher))
        jooqConfiguration.set(SQLDialect.MYSQL)
        return jooqConfiguration
    }

요 코드에서 사용되니 싱글톤으로 생성될 것이라 생각합니다.

Comment on lines 5 to 12
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
import org.springframework.jdbc.support.SQLExceptionTranslator

class ExceptionTranslator : ExecuteListener {

override fun exception(context: ExecuteContext) {
val dialect = context.configuration().dialect()
val translator: SQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator(dialect.name)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 지금 slow query에 대해 예외가 발생하는건 아닌데 왜 SQLErrorCodeSQLExceptionTranslator 가 사용되나요?

슬로우 쿼리를 위해서 SQLErrorCodeSQLExceptionTranslator를 추가한 것은 아니고 벤더마다 다를 수 있는 SQL 에러를 스프링에서 제공하는 에러로 통일해서 처리하기 위해서 추가했습니다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.html

Comment on lines 9 to 18
class PerformanceListener(
private val applicationEventPublisher: ApplicationEventPublisher,
) : ExecuteListener {
private val log = KotlinLogging.logger {}

private var watch: StopWatch? = null
override fun executeStart(ctx: ExecuteContext) {
super.executeStart(ctx)
watch = StopWatch()
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PerformanceListener 이거 싱글톤으로 동작하나요?
네! JooqConfig의 configuration 빈 생성시에만 사용되어 결국 싱글톤으로 생성됩니다.

    public StopWatch() {
        this.start = System.nanoTime();
        this.split = start;
    }

StopWatch 객체를 생성하는 순간 start에 현재 시간을 저장하고 executeStart와 executeEnd에서 watch를 공유해야 쿼리 속도를 측정할 수 있어서 위와 같이 구현했습니다.

어떻게 수정하면 좋을지 아직 감이 안잡히는데 어떻게 하면 좋을까요?

Comment on lines +20 to +50
fun announceRepoAlter(args: RepoAlterArgs) {
val embedsList = ArrayList<Embed>()
args.let {
embedsList.add(
Embed(
title = "Exception",
description = "Slow Query Detected"
)
)
it.requestURL.let { requestURL ->
embedsList.add(
Embed(
title = "Request URL",
description = requestURL
)
)
}
it.query?.let { query ->
embedsList.add(
Embed(
title = "Slow Query Detected",
description = query
)
)
}
}
args.let {
DiscordBodyProperty(
content = "😭 DB 이상 발생",
embeds = embedsList
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 자체는 slow query에 종속적인 코드인데 모듈은 API 모듈에 포함되어서 약간 모듈이 안나눠진 느낌입니다.

저도 그렇게 생각해요!

디스코드 웹훅을 쏘는 역할을 API에 다 두고 그 컨텐츠는 api-repo에서 만들어서 던지던지 하는건 어떨까요

요 말을 아직 이해를 잘 못했습니다..!


제가 구현하면서 생각한 방법은 client를 별도의 모듈로 분리하는 거였어요
그럼 슬로우 쿼리 발생 이벤트를 api-repo 안에서 바로 처리할 수 있으니까요.
그리고 나중에 다른 모듈에서도 client를 사용해야하는 경우가 생길 수 있는데 그런 중복 코드도 줄일 수 있을 것 같고요!

Comment on lines +20 to +21
RepoAlterArgs(
requestURL = MDC.get("Request-URL") ?: "",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 구현에서는 여러 쿼리에서 슬로우 쿼리가 발생하는 경우 첫 이벤트를 제외한 다른 처리에서는 MDC 컨텍스트가 공유되지 않는 문제가 있었는데 (PR 사진 참고)

https://blog.gangnamunni.com/post/mdc-context-task-decorator/
위의 블로그를 참고해서 6885ad4 에서 추가 구현했습니다.

class ClonedTaskDecorator : TaskDecorator {
    override fun decorate(runnable: Runnable): Runnable {
        val contextMap = MDC.getCopyOfContextMap()
        return Runnable {
            if (contextMap != null) {
                MDC.setContextMap(contextMap)
            }
            runnable.run()
        }
    }
}
    @Bean(DISCORD_HOOK_EVENT_POOL)
    fun discordHookThreadPool() = ThreadPoolTaskExecutor().apply {
        val properties = disCordThreadPoolProperties()
        corePoolSize = properties.getCorePoolSize()
        maxPoolSize = properties.getMaxPoolSize()
        queueCapacity = properties.getQueueCapacity()
        setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown())
        setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds())
        setThreadNamePrefix("discordHookThreadPool-")
        setRejectedExecutionHandler { r, _ ->
            log.warn { "Discord Hook Event Task Rejected: $r" }
        }
        setTaskDecorator(ClonedTaskDecorator()) // <- decorator 설정
        initialize()
    }

위의 추가된 두 코드로 슬로우 쿼리 발생시의 MDC 컨텍스를 비동기로 디스코드 훅을 보내는 스레드 풀에서도 동일한 MDC 를 공유할 수 있습니다.

Comment on lines 9 to 10
private val log = KotlinLogging.logger {}
override fun executeEnd(ctx: ExecuteContext) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6bc7005 에서 반영했습니다.


override fun executeEnd(ctx: ExecuteContext) {
super.executeEnd(ctx)
if (watch!!.split() > 5000000000L) { // 5 seconds
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7156b1c 에서 반영했습니다.

@belljun3395 belljun3395 requested a review from hun-ca July 28, 2024 14:23
Comment on lines +30 to +31
val translator =
SQLErrorCodeSQLExceptionTranslator(SQLDialect.MYSQL.name)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translator를 외부에서 정의해서 넣는 방식으로 수정했습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 translator 가져오는 코드는 어디있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 블록에서 SQLErrorCodeSQLExceptionTranslator에 dialect를 바로 넣어서 생성했어요

jooqConfiguration.set(connectionProvider())
val translator =
SQLErrorCodeSQLExceptionTranslator(SQLDialect.MYSQL.name)
jooqConfiguration.set(ExceptionTranslator(translator), NativeSQLLogger(), PerformanceListener(applicationEventPublisher))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가로 기존 구현에서는 ExceptionTranslator가 재대로 동작하고 있지 않는 것을 확인해서 수정하였습니다.

Comment on lines +7 to +16
class ExceptionTranslator(
private val translator: SQLExceptionTranslator,
) : ExecuteListener {

override fun exception(context: ExecuteContext) {
context.exception(
translator
.translate("Access database using Jooq", context.sql(), context.sqlException()!!)
)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳 이렇게 바뀐거네 싱글톤으로

Copy link
Member

@hun-ca hun-ca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생많으십니다

@belljun3395 belljun3395 merged commit ceae194 into main Jul 30, 2024
6 checks passed
@belljun3395 belljun3395 added the ⭐major⭐ 주요 이슈(추후 회고 및 복귀 시 참고용) label Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
config 설정 파일과 관련된 내용을 다룰 때 사용됩니다 ⭐major⭐ 주요 이슈(추후 회고 및 복귀 시 참고용) refactor 기존 기능에 대해 개선할 때 사용됩니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JooQ 설정 수정 및 추가
2 participants