在 AsyncUncaughtExceptionHandler 中获取休眠会话
Posted
技术标签:
【中文标题】在 AsyncUncaughtExceptionHandler 中获取休眠会话【英文标题】:Getting Hibernate Session inside AsyncUncaughtExceptionHandler 【发布时间】:2019-05-16 01:45:06 【问题描述】:当@Async
操作因异常而失败时,我想在数据库中创建异常日志。
您可以在下面查看 AsyncExecutorConfiguration
和 AsyncExceptionHandler
类的实现。
在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
类中进行有效会话?
--
更新
这是我们得到错误的NotificationServiceImpl
和LogDaoImpl.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
(通常)的主要用例用于:
-
处理
RuntimeException
s 的简单最后手段,程序员可能不知道由于某种原因不能(或没有)被代码捕获
一种在程序员无法控制的代码中捕获异常的方法(例如,如果您直接在某个第三方库上调用 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?