在 AsyncUncaughtExceptionHandler 中获取休眠会话

Posted

技术标签:

【中文标题】在 AsyncUncaughtExceptionHandler 中获取休眠会话【英文标题】:Getting Hibernate Session inside AsyncUncaughtExceptionHandler 【发布时间】:2019-05-16 01:45:06 【问题描述】:

@Async 操作因异常而失败时,我想在数据库中创建异常日志。

您可以在下面查看 AsyncExecutorConfigurationAsyncExceptionHandler 类的实现。

AsyncExceptionHandler 类中,当我调用一个尝试访问数据库的服务时,我得到:org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread

@Configuration
@EnableAsync
public class AsyncExecutorConfiguration implements AsyncConfigurer 

    @Autowired
    private AsyncExceptionHandler asyncExceptionHandler;

    private static final int CORE_POOL_SIZE = 3;
    private static final int MAX_POOL_SIZE = 3;
    private static final int QUEUE_CAPACITY = 24;
    private static final String THREAD_NAME_PREFIX = "AsynchThread-";

    @Override
    public Executor getAsyncExecutor() 
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.initialize();
        return executor;
    

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() 
        return asyncExceptionHandler;
    



@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler 
    @Autowired
    private NotificationService notificationService;

    @Override
    @Transactional(rollbackFor = Exception.class, readOnly = false)
    public void handleUncaughtException(Throwable ex, Method method, Object... params) 
        AsyncErrorLog log = new AsyncErrorLog(ex);
        notificationService.saveLogAndNotify(log); // throws exception "Could not obtain transaction-synchronized Session for current thread"
    



@Service
public class MyServiceImpl implements MyService 

    @Override
    @Async
    @Transactional(rollbackFor = Exception.class, readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void doSomething(Long id) 
        // I can execute database operations here

    
    ...

@Async 函数本身已经有一个有效的会话。我应该怎么做才能在AsyncExceptionHandler 类中进行有效会话?

--

更新

这是我们得到错误的NotificationServiceImplLogDaoImpl.class 的简化实现。

@Service
public class NotificationServiceImpl implements NotificationService 

    @Autowired
    private LogDao logDao;

    @Override
    @Transactional(rollbackFor = Exception.class, readOnly = false)
    public void saveLogAndNotify(Log log) 
        return logDao.createLog(log);
    


@Repository
public class LogDaoImpl

    @Autowired
    protected SessionFactory sessionFactory;


    @Override
    public void createLog(Log log) 
        sessionFactory.getCurrentSession().saveOrUpdate(log);
    

【问题讨论】:

请分享NotificationServiceImpl @Spara: NotificationServiceImpl 调用 LogDaoImpl 我们得到错误的地方。我分享了那个类的实现。 您是否尝试将@Transactional 添加到LogDaoImpl 类的顶部? 我们在NotificationServiceImpl 的服务级别有@Transactional,除了AsyncUncaughtExceptionHandler 之外,它可以从应用程序的很多地方调用,没有任何问题。 (更新代码以显示服务实现) 是的,我明白了,但是错误意味着您的服务层无法成功启动事务,我试图在我的项目中制作您的情况,但我不能,因为我使用了 spring 数据,请更改两件事告诉我它是否有效:首先请在AsyncExceptionHandler 类中将@Component 更改为@Service,其次请在LogDaoImpl 中添加@Transactional 【参考方案1】:

根据 Hibernate 异常;如果您不使用 Spring Data,则必须确保通知服务显式调用 Hibernate 会话上的数据库调用。

另一方面,根据我的经验,UncaughtExceptionHandler(通常)的主要用例用于:

    处理RuntimeExceptions 的简单最后手段,程序员可能不知道由于某种原因不能(或没有)被代码捕获 一种在程序员无法控制的代码中捕获异常的方法(例如,如果您直接在某个第三方库上调用 Async 等)

两者的共同点是这个Handler 用于一些意想不到的事情。事实上,Spring 本身在您自己的代码和 Spring Async 已经sets a default one for you that will log to the console(代码here)中解决了“意外”,让您不必担心流氓异常杀死线程而不知道为什么。 (注意:源代码中的消息说它正在捕获“意外”异常。当然异常是意外的,但这些是你真的不知道可能发生的异常。Spring Async 将记录它给你。)

既然如此,在您的示例中,由于您正在执行 Spring 数据库操作并且应该确切地知道 #doSomething 内部发生了什么,所以我会删除 AUEH a try-catch (和/或 @987654328 @) 并处理 #doSomething 内部的异常:

@Service
public class MyServiceImpl implements MyService 

    // Self autowired class to take advantage of proxied methods in the same class
    // See https://***.com/questions/51922604/transactional-and-stream-in-spring/51923214#51923214
    private MyService myService;  

    private NotificationService notificationService;  

    @Override
    @Async
    public void doSomething(Long id) 
        // I can execute database operations here
        try 
            myService.doDatabaseOperations(...);
         catch(DatabaseAccessException e) 
            AsyncErrorLog log = new AsyncErrorLog(ex);
            notificationService.saveLogAndNotify(log);
        
        // Other exceptions (from DB operations or the notifications service) can be 
        // handled with other "catches" or to let the SimpleAsyncExHandler log them for you.
        // You can also use standard multithreading exception handling for this
    

    @Transactional(rollbackFor = Exception.class, readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void doDatabaseOperations(...) 
        ...
    


【讨论】:

嗨@Dovmo,您能否详细说明“如果您不使用 Spring Data,则必须确保通知服务在 Hibernate 会话上显式调用数据库调用。”我们没有使用 Spring Data,如何确保通知服务显式调用 Hibernate 会话上的数据库调用? 您的实现与我们现在解决问题的方式非常接近,但我们必须在每个 @Async 实现中重复相同的 try/catch 块。如果我们能在一个地方处理它们就更好了。 我为您设想了两种模块化事物的方法:(1) 有一个 AOP/Intercepter 注释或 (2) 你可以让 doSomething 为该 @Async 调用返回一个 CompletableFuture .您可以让您的服务接受来自CompletableFuture#exceptionally 的异常并调用数据库记录器。其中任何一个有意义或需要解释吗? 是的,它们对我来说很有意义。我们仍在使用 Java7,因此 CompletableFuture 不是一个选项。但是,一旦我将注意力从 AsyncUncaughtExceptionHandler 上移开,我就可以看到删除 try/catch 块重复的机会。非常感谢您的意见。【参考方案2】:

您可以在处理程序中使用applicationContext 来查找notificationService。当我将@Autowired 用于处理程序时,我遇到了同样的问题,而处理程序又注入了我的LogService。查看日志后,我看到TransactionSynchronizationManager 在异常回滚后正在清除事务同步,除了no transaction for ...... 错误之外没有其他任何内容。

使用applicationContext 查找logService bean 并更改我的处理程序后,我在日志中看到了所需的结果。

    开始 初始化事务同步 获取 [....AsyncService.doAsync] 的事务 异常 回滚

    清除事务同步

    开始

    初始化事务同步 为 [.....LogService.save] 获取事务

更改您的配置以包含接口ApplicationContextAware,这将为您提供访问applicationContext 的便捷方法。将其设置为实例变量。

请看下面我的配置类。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer, ApplicationContextAware 

    private static final int CORE_POOL_SIZE = 3;
    private static final int MAX_POOL_SIZE = 3;
    private static final int QUEUE_CAPACITY = 24;
    private static final String THREAD_NAME_PREFIX = "AsynchThread-";

    private ApplicationContext applicationContext;

    @Override
    public Executor getAsyncExecutor() 
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.initialize();
        return executor;
    

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() 
        return new AsyncExceptionHandler(this.applicationContext);
    

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
        this.applicationContext = applicationContext;
    

我已经从处理程序中删除了@Component 并将其用作 POJO。 每次调用 getAsyncUncaughtExceptionHandler 时出现异常,都会创建一个新的处理程序实例,并将 applicationContext 作为依赖项。

public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler 

    private final ApplicationContext applicationContext;

    public AsyncExceptionHandler(ApplicationContext applicationContext) 
        this.applicationContext = applicationContext;
    

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) 
        Log log = new Log();
        log.setEntry(ex.getMessage());
        LogService logService = this.applicationContext.getBean(LogService.class);
        logService.save(log);
    

logService 上的 save 方法每次调用时都需要一个新事务。

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Log log)

【讨论】:

【参考方案3】:

这将对您有所帮助:

@Override
public void createLog(Log log) 
try 
    session = sessionFactory.getCurrentSession();
 catch (HibernateException e) 
    session = sessionFactory.openSession();

    session.saveOrUpdate(log);

【讨论】:

您好 Spara,此解决方案不会抱怨会话,但也不会提交事务。我们可以继续尝试提交事务,但我更喜欢 Spring @Transactional 处理所有这些过程而不是手动干预。 @nilgun 是的,你是对的,这是最后一个解决方案,如果你不知道该怎么做,你可以使用它......

以上是关于在 AsyncUncaughtExceptionHandler 中获取休眠会话的主要内容,如果未能解决你的问题,请参考以下文章

秋的潇洒在啥?在啥在啥?

上传的数据在云端的怎么查看,保存在啥位置?

在 React 应用程序中在哪里转换数据 - 在 Express 中还是在前端使用 React?

存储在 plist 中的数据在模拟器中有效,但在设备中无效

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据