简体   繁体   English

在AsyncUncaughtExceptionHandler中获取Hibernate会话

[英]Getting Hibernate Session inside AsyncUncaughtExceptionHandler

I want to create an exception log in the database when an @Async operation fails with an exception. @Async操作因异常而失败时,我想在数据库中创建异常日志。

You can see the implementation for AsyncExecutorConfiguration and AsyncExceptionHandler classes below. 您可以在下面看到AsyncExecutorConfigurationAsyncExceptionHandler类的实现。

Inside AsyncExceptionHandler class, when I call a service that tries to access the database, I am getting: org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread 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 function itself already has a valid session. @Async函数本身已经有一个有效的会话。 What should I do to have a valid session in AsyncExceptionHandler class too? 我该怎么做才能在AsyncExceptionHandler类中有一个有效的会话?

-- -

UPDATE UPDATE

Here is the simplified implementations for NotificationServiceImpl and LogDaoImpl.class where we get the error. 以下是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);
    }

Per the Hibernate exception; 根据Hibernate异常; if you're not using Spring Data, you'll have to make sure the notification service explicitly invokes the database calls on the Hibernate session. 如果您不使用Spring Data,则必须确保通知服务明确调用Hibernate会话上的数据库调用。

On another note, in my experience, the main use cases for the UncaughtExceptionHandler (in general) are used for: 另一方面,根据我的经验, UncaughtExceptionHandler (通常)的主要用例用于:

  1. A simple last-resort to handle RuntimeException s that may be unknown to the programmer that for some reason cannot (or are not) caught in code 一个处理RuntimeException的简单的最后手段,程序员可能不知道由于某种原因不能(或没有)捕获代码
  2. A way to catch exceptions in code that the programmer has no control over (eg if you're invoking Async directly on some third party library, etc.) 一种捕获程序员无法控制的代码异常的方法(例如,如果您直接在某些第三方库上调用Async,等等)

The commonality between the two is that this Handler is used for something unexpected. 两者之间的共性是这个Handler用于意外的事情。 In fact, Spring itself accounts for the "unexpectedness" in your own code and Spring Async already sets a default one for you that will log to the console (code here ), letting you not have to worry about rogue exceptions killing threads and not knowing why. 事实上,Spring本身在你自己的代码中占据了“意外”,Spring Async 已经 为你设置了一个默认的登录控制台这里的代码),让你不必担心流氓异常会杀死线程而不知道为什么。 (Note: The message in the source code says it's catching an "unexpected" exception. Of course exceptions are unexpected, but these are one's that you really didn't know could happen. Spring Async will log it for you.) (注意:源代码中的消息表明它正在捕获“意外”异常。当然异常是意外的,但这些是您真正不知道的可能发生的事情.Spring Async会为您记录它。)

That being the case, in your example, since you're doing Spring Database operations and should know exactly what's happening inside of #doSomething , I would just go with removing the AUEH a try-catch (and/or -finally ) and handle the exception inside of #doSomething : 既然如此,在您的示例中,由于您正在进行Spring数据库操作并且应该确切地知道#doSomething内部发生了#doSomething ,我只需要将AUEH移除一个try-catch (和/或-finally )并处理#doSomething异常:

@Service
public class MyServiceImpl implements MyService {

    // Self autowired class to take advantage of proxied methods in the same class
    // See https://stackoverflow.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(...) {
        ...
    }

}

This will help you: 这将有助于您:

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

You can use the applicationContext in your handler to lookup the notificationService . 您可以在处理applicationContext中使用applicationContext来查找notificationService I had the same issue when I used @Autowired for the handler, which in turn injected my LogService . 当我使用@Autowired处理程序时,我遇到了同样的问题,后者又注入了我的LogService After looking at the logs I saw that the TransactionSynchronizationManager is clearing transaction synchronization after the rollback of the exception and nothing else except the no transaction for ...... error. 在查看日志之后,我看到TransactionSynchronizationManager在回滚异常后清除了事务同步,除了no transaction for ......错误的no transaction for ......之外没有别的no transaction for ......错误。

After using the applicationContext for looking up the logService bean and changing my handler, I saw the desired result in the logs. 在使用applicationContext查找logService bean并更改我的处理程序之后,我在日志中看到了所需的结果。

  1. begin 开始
  2. Initializing transaction synchronization 初始化事务同步
  3. Getting transaction for [....AsyncService.doAsync] 获取[.... AsyncService.doAsync]的事务
  4. Exception 例外
  5. rolling back 滚回来
  6. Clearing transaction synchronization 清除事务同步

  7. begin 开始

  8. Initializing transaction synchronization 初始化事务同步
  9. Getting transaction for [.....LogService.save] 获取[..... LogService.save]的交易

Change your config to include the interface ApplicationContextAware which will give you a convenience method to access the applicationContext . 更改您的配置以包含ApplicationContextAware接口,它将为您提供访问applicationContext的便捷方法。 Set it as a instance variable. 将其设置为实例变量。

See my configuration class below. 请参阅下面的配置类。

@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;
    }
}

I have removed the @Component from the handler and use it as a POJO. 我已从处理程序中删除@Component并将其用作POJO。 Every time getAsyncUncaughtExceptionHandler is called with an exception, a new handler instance is created with the applicationContext as a dependency. 每次使用异常调用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);
    }
}

The save method on logService requires a new transaction every time it is called. logService上的save方法每次调用时都需要一个新事务。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM