[spring-projects/spring-boot]更容易配置在 Boot 默认后运行的 JOOQ Execution Listener Provider

2024-04-23 197 views
7

你好,

我使用 spring boot 2.0.4 来配置 JOOQ DSLContext,因为它使用 JOOQAutoConfiguration 类。我的问题是,提供的 spring dao 异常翻译与已创建的应用程序错误处理不太适合。我需要一个自定义 ExecuteListener 将 JOOQ 异常转换为应用程序自定义异常。

我尝试将默认侦听器提供程序公开为 bean:

@Bean
public DefaultExecuteListenerProvider applicationExceptionTranslatorExecuteListenerProvider() {
    return new DefaultExecuteListenerProvider(new ApplicationExceptionTranslator());
}

但 JOOQ 配置没有选择提供程序。

在搜索其他问题时,我在 #13329 中找到了有效的解决方案。

@Bean
public static BeanPostProcessor jooqConfigurationPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            return bean;
        }

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName)
                throws BeansException {
            if (bean instanceof DefaultConfiguration) {
                ((DefaultConfiguration) bean).set(new JOOQExceptionTranslator());
            }
            return bean;
        }

    };
}

我需要知道这是否是正确的方法,或者是否有其他方法可以为 ExecuteListenerProvider 提供自定义 ExecuteListener?为什么将侦听器提供程序公开为 bean 不起作用,我缺少什么?

谢谢

回答

4

我需要知道这是否是正确的方法,或者是否有其他方法可以为 ExecuteListenerProvider 提供自定义 ExecuteListener?

使用 aBeanPostProcessor应该不是必要的。自动配置应该选取ExecuteListenerProvider您定义的任何 bean,并配置 jOOQ 以在默认 bean 之外使用它们。

但 JOOQ 配置没有选择提供程序。为什么将侦听器提供程序公开为 bean 不起作用,我缺少什么?

这是不可能的,因为“不接听提供商”并不能告诉我们太多信息。您如何确定 jOOQ 未接听您的提供商?了解您正在看到什么行为以及您期望看到什么行为将很有用。展示这一点的最佳方法是使用重现问题的最小样本。可以请您提供一份吗?您可能还需要考虑您的提供商和默认提供商的排序。您可能希望使用@Order它的@Bean方法,以便在默认值之前调用它。

8

感谢您的回复。

不可能说“不接听提供商”

你是对的。我没有用正确的词语来描述发生的事情。侦听器处于活动状态,但由于某种原因,异常传播无法按我的预期工作。

我现在无法提供项目示例,但我会尝试用代码示例来展示正在发生的事情:

这是我的执行侦听器:

@Slf4j
public class ApplicationExceptionTranslator extends DefaultExecuteListener {

    @Override
    public void exception(ExecuteContext ctx) {
        System.out.println("DEFAULT EXECUTION LISTENER ..............................");

        if (ctx.sqlException() != null) {
                        // PRINT SOME STAFF
            log.warn("SQL: {}", ctx.sql());
            log.warn("RUNTIME EXC MESSAGE: {}", ctx.exception().getMessage());
            log.warn("RUNTIME EXC CANNONICAL NAME: {}", ctx.exception().getClass().getCanonicalName());

            SQLDialect dialect = ctx.dialect();
            SQLExceptionTranslator translator = (dialect != null)
                    ? new SQLErrorCodeSQLExceptionTranslator(dialect.thirdParty().springDbName())
                    : new SQLStateSQLExceptionTranslator();

            DataAccessException daoEx = translator.translate("jOOQ", ctx.sql(), ctx.sqlException());

            ApplicationRepositoryException appRepositoryException = // create EXCEPTION from ctx and daoEx
            ctx.exception(appRepositoryException);
        }
    }
}

这是我的其余异常处理程序,其中包含应用程序异常和 spring dao 异常的处理程序(出于调试目的添加了 spring 异常):

@ControllerAdvice
@Slf4j
public class ApplicationRestResponseExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ApplicationRepositoryException.class)
    protected ResponseEntity<ApplicationRestError> handleApplicationRepositoryException(ApplicationRepositoryException ex, HttpServletRequest req) {
        HttpStatus status = HttpStatus.CONFLICT;
        ApplicationRestError body = MessageBuilder.buildApplicationRestError(ex, req, status);
        return new ResponseEntity<>(body, status);
    }

    @ExceptionHandler(DataAccessException.class)
    protected ResponseEntity<ApplicationRestError> handleSpringDataAccessException(DataAccessException ex, HttpServletRequest req) {
        HttpStatus status = HttpStatus.CONFLICT;
        ApplicationRestError body = MessageBuilder.buildApplicationRestError(ex, req, status);
        return new ResponseEntity<>(body, status);
    }
}

我尝试设置 bean 的顺序:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public DefaultExecuteListenerProvider applicationExceptionTranslatorExecuteListenerProvider() {
    return new DefaultExecuteListenerProvider(new ApplicationExceptionTranslator());
}

我禁用了 BeanPostProcessor 的配置。

上述配置的结果是其余异常处理程序捕获 DataAccessException.class 而不是 ApplicationRepositoryException.class。

当我启用 BeanPostProcessor(如我的第一篇文章中所示)时,我的其余异常处理程序捕获 ApplicationRepositoryException.class。这对我来说是预期的行为,我只想处理应用程序异常。

另一件事:我在这两种情况下都看到日志语句。这确认监听器已加载。

谢谢

9

不幸的是,如果没有重现问题的小样本,就很难准确诊断发生了什么。希望您能够尽快提供一个,因为我们无法花费时间将一些代码片段拼凑在一起。不重现您实际看到的问题的风险太高,不值得。

6

你好,

我已经创建了github 项目示例, 其中包含 README 和有关如何重现意外行为的注释。

再次感谢你的反馈。

1

感谢您提供样品。我已经重现了这个问题。

原因是引导程序JooqExceptionTranslator在您之后运行ApplicationExceptionTranslator并用自己的异常覆盖其翻译的异常。发生这种情况是因为上下文的 SQL 异常是非空的,并且非空 SQL 异常是执行异常转换的触发器。不幸的是,异常翻译器似乎不可能清空上下文的 SQL 异常以阻止JooqExceptionTranslator执行任何操作。

看来应该对 Boot 的DefaultExecuteListenerProviderforJooqExceptionTranslator进行排序,以便其他提供程序可以追随它(优先级较低)。这将允许他们提供的侦听器用自己的侦听器覆盖 Boot 的翻译异常。这确实是以在只有最后一次翻译有价值时多次执行异常翻译为代价的。如果有一种方法可以阻止进一步的异常转换,那将会很有趣,但这似乎是不可能的,因为调用sqlException(null)ExecuteContext因 NPE 而失败。也许我错过了一些东西,但有办法做到这一点,@lukaseder?

8

我不完全理解 Calling 背后的想法sqlException(null)。您到底想防止什么?

2

希望是防止发生任何后续异常翻译。我想知道是否可以通过清空 SQL 异常来完成此操作,以便后续翻译不会发生,因为不再有任何内容可翻译。

9

嗯,这听起来太“聪明”了。此外,你并不是真的对覆盖 感兴趣ExecuteContext.sqlException(),而是ExecuteContext.exception(),这是 jOOQ 抛出的那个......所以我很好奇 jOOQ 中是否真的有什么需要改变的?

2

已经exception被成功覆盖。问题是后续的翻译器包含这样的逻辑:

SQLException exception = context.sqlException();
while (exception != null) {
    handle(context, translator, exception);
    exception = exception.getNextException();
}

handle拨打电话至ExecuteContext.exception(RuntimeException).

此时,SQL异常已经被翻译并且ExecuteException.exception(RuntimeException)已经被调用。我们正在寻找一种方法来向后续翻译人员表明这一点,以便他们不会覆盖已经覆盖的异常。

我们可以依赖翻译器的排序,但这有点浪费,因为翻译将被执行多次但只使用一次。它在某些情况下能够覆盖翻译人员的行为而在其他情况下回退方面也不太灵活。

听起来上面是 jOOQ 提供的,我只是想确保我没有错过 API 的功能。我们可以将翻译器放在中间,以便用户的翻译器根据他们的需要位于其之前或之后。在当前的安排中,他们只能在默认翻译之前进行。

9

我明白了,非常感谢您的解释。好吧,虽然我仍然不完全建议null在这里传递并滥用null这种“特殊价值”,但我想null无论用户出于何种原因仍然接受作为一种价值也没有什么坏处。我为此创建了一个问题:https://github.com/jOOQ/jOOQ/issues/7909。它将向后移植到 3.11 和 3.10

作为解决方法,您可以使用虚拟SQLException单例实例作为转换器之间的“信号”,而不是此null值。它应该具有相同的效果:

private static final SQLException STOP_TRANSLATING = new SQLException();

// Later:
if (ctx.sqlException() != STOP_TRANSLATING)
  translate();

希望这可以帮助

2

确实如此。谢谢。

4

我认为我们仍然应该给我们的jooqExceptionTranslatorExecuteListenerProviderbean 一个 0 的顺序,以便其他提供者可以轻松地追随它。我不确定我们应该在哪个版本中进行更改。

0

你好,

干得好,大家。下周我将尝试使用 Spring Boot 2.1。

多谢

3

你好,

我已经使用 spring boot 2.1 和 JOOQ 3.11.7 升级了项目依赖项。 (演示项目已更新)通过添加Ordered.LOWEST_PRECEDENCE到自定义ExecuteListenerProviderbean 工厂方法,自定义应用程序异常由异常处理程序处理ControllerAdvice,这是预期的行为。如果我理解正确@wilkinsona,这就是我需要做的来获取自定义异常传播。停止多个异常翻译将在低级别完成( spring jooq 集成),或者我应该在应用程序级别执行某些操作?

谢谢

2

@aleksandar78 对于您的特定安排,您无需再做任何事情。或者,ExecuteContext.sqlException(null)如果您不想依赖订购,您现在可以致电。