Метод @Transactional вставляет значение для исключения и многопоточный wildlfy CDI - PullRequest
1 голос
/ 22 октября 2019

У меня есть метод в компоненте CDI, который является транзакционным, при ошибке он создает запись в базе данных с сообщением об исключении. Этот метод может вызываться RESTendpoint и многопоточным способом.

У нас есть ограничение SQL, чтобы избежать дублирования в базе данных

    @Transactional
public RegistrationRuleStatus performCheck(RegistrationRule rule, User user) {

    try {
        //check if rule is dependant of other rules and if all proved, perform check
        List<RegistrationRule> rules = rule.getRuleParentDependencies();
        boolean parentDependenciesAreProved = true;

        if (!CollectionUtils.isEmpty(rules)) {
            parentDependenciesAreProved = ruleDao.areParentDependenciesProved(rule,user.getId());
        }

        if (parentDependenciesAreProved) {
            Object service = CDI.current().select(Object.class, new NamedAnnotation(rule.getProvider().name())).get();
            Method method = service.getClass().getMethod(rule.getProviderType().getMethod(), Long.class, RegistrationRule.class);

            return (RegistrationRuleStatus) method.invoke(service, user.getId(), rule);

        } else {
            RegistrationRuleStatus status = statusDao.getStatusByUserAndRule(user, rule);
            if (status == null) {
                status = new RegistrationRuleStatus(user, rule, RegistrationActionStatus.START, new Date());
                statusDao.create(status);
            }

            return status;
        }
    } catch (Exception e) {
        LOGGER.error("could not perform check {} for provider {}", rule.getProviderType().name(), rule.getProvider().name(), e.getCause()!=null?e.getCause():e);

        return statusDao.createErrorStatus(user,rule,e.getCause()!=null?e.getCause().getMessage():e.getMessage());
    }
}

create Error method:

@Transactional
public RegistrationRuleStatus createErrorStatus(User user, RegistrationRule rule, String message) {
     RegistrationRuleStatus status = getStatusByUserAndRule(user, rule);
     if (status == null) {
         status = new RegistrationRuleStatus(user, rule, RegistrationActionStatus.ERROR, new Date());
         status.setErrorCode(CommonPropertyResolver.getMicroServiceErrorCode());
         status.setErrorMessage(message);
         create(status);
     }else {
         status.setStatus(RegistrationActionStatus.ERROR);
         status.setStatusDate(new Date());
         status.setErrorCode(CommonPropertyResolver.getMicroServiceErrorCode());
         status.setErrorMessage(message);
         update(status);
     }
     return status;
}

проблема в том, что метод вызывается дважды одновременно, и записана ошибка DuplicateException, но мы не хотим этого. Сначала мы проверяем, существует ли объект, но я думаю, что он вызывается в одно и то же время.

JAVA8 / wildlfy / CDI / JPA / eclipselink

Есть идеи?

1 Ответ

0 голосов
/ 22 октября 2019

Я бы предложил вам рассмотреть следующие подходы:

1) Реализовать логику повторных попыток. Поймай исключение, проанализируй его. Если он указывает на неожиданный дубликат (как вы описали), то не рассматривайте его как ошибку и просто повторите вызов метода. Теперь ваш код будет работать по-другому: он заметит, что запись уже существует и не создаст дубликат.

2) Используйте уровень изоляции SERIALIZABLE. Тогда в рамках одной транзакции вы «увидите» непротиворечивое поведение: если операция выбора не нашла конкретную запись, то до конца этой транзакции никакая другая транзакция не будет вставлять такую ​​запись, и не будет исключений, связанных с дубликатами. Но цена в том, что вся таблица будет заблокирована для каждой такой транзакции. Это может существенно снизить производительность приложения.

...