Получение Hibernate Session внутри AsyncUncaughtExceptionHandler - PullRequest
0 голосов
/ 14 декабря 2018

Я хочу создать журнал исключений в базе данных, когда операция @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);
    }

Ответы [ 3 ]

0 голосов
/ 19 декабря 2018

Это вам поможет:

@Override
public void createLog(Log log) {
try {
    session = sessionFactory.getCurrentSession();
} catch (HibernateException e) {
    session = sessionFactory.openSession();
}
    session.saveOrUpdate(log);
}
0 голосов
/ 21 декабря 2018

Вы можете использовать applicationContext в вашем обработчике для поиска notificationService.У меня была та же проблема, когда я использовал @Autowired для обработчика, который, в свою очередь, ввел мой LogService.Посмотрев журналы, я увидел, что TransactionSynchronizationManager очищает синхронизацию транзакций после отката исключения и ничего кроме ошибки no transaction for .......

После использования applicationContext для поиска logService bean и меняя свой обработчик, я увидел желаемый результат в логах.

  1. begin
  2. Инициализация синхронизации транзакций
  3. Получение транзакции для [.... AsyncService.doAsync]
  4. Исключение
  5. откат
  6. Очистка синхронизации транзакций

  7. начало

  8. Инициализация синхронизации транзакций
  9. Получение транзакции для [..... 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);
    }
}

Метод save в logService требует новой транзакции при каждом вызове.

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Log log)
0 голосов
/ 18 декабря 2018

за исключение Hibernate;если вы не используете Spring Data, вам нужно убедиться, что служба уведомлений явно вызывает вызовы базы данных в сеансе Hibernate.

С другой стороны, по моему опыту, основные сценарии использования для UncaughtExceptionHandler (в общем) используются для:

  1. Простое последнее средство для обработки RuntimeException с, которое может быть неизвестно программисту, которое по какой-то причине не может (или не является) пойманным в коде
  2. Способ перехвата исключений в коде, который программист не может контролировать (например, если вы вызываете Async непосредственно из какой-либо сторонней библиотеки и т. Д.)

Общность междуВо-вторых, это Handler используется для чего-то неожиданного.Фактически, сама Spring учитывает «неожиданность» в вашем собственном коде, а Spring Async уже устанавливает для вас значение по умолчанию, которое будет входить в консоль (код здесь ), позволяя вам не беспокоиться о мошеннических исключениях, убивающих темы и не знающих, почему.(Примечание: сообщение в исходном коде говорит, что оно перехватывает «неожиданное» исключение. Конечно, исключения являются неожиданными, но это то, о чем вы действительно не знали, что может произойти. Spring Async зарегистрирует его дляyou.)

В данном случае, в данном случае, поскольку вы выполняете операции с базой данных Spring и должны точно знать, что происходит внутри #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(...) {
        ...
    }

}
...