1

我在 CDI bean 中有一个方法,它是事务性的,出错时它会在数据库中创建一个带有异常消息的条目。该方法可以被 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());
    }
}

创建错误方法:

@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

任何想法 ?

4

1 回答 1

0

我建议您考虑以下方法:

1)实现重试逻辑。捕获异常,分析它。如果它表示意外重复(如您所述),则不要将其视为错误,只需重复方法调用即可。现在您的代码将以不同的方式工作:它会注意到一条记录已经存在并且不会创建重复项。

2)使用隔离级别SERIALIZABLE。然后在单个事务中,您将“看到”一致的行为:如果选择操作没有找到特定记录,那么直到该事务结束,没有其他事务将插入这样的记录,并且不会有与重复相关的异常。但代价是每次这样的交易都会锁定整个表。这会从本质上降低应用程序的性能。

于 2019-10-22T19:32:26.690 回答