将唯一违规例外传播到UI的最佳做法

fis*_*tte 17 jsf spring hibernate jpa constraints

我们正在开发基于JPA 2,Hibernate,Spring 3和在Tomcat 7中运行的JSF 2的Java Web项目.我们使用Oracle 11g作为数据库.

我们目前正在讨论将数据库约束违规填充为用户友好消息的方法.或多或少我们看到两种方式,两种方式都不令人满意.有人可以提供一些建议吗?

方法1 - 以编程方式验证并抛出特定异常

在CountryService.java中,将验证每个Unique约束并抛出相应的异常.异常在辅助bean中单独处理.

优点:易于理解和维护.特定用户消息可能.

缺点:很多代码只是为了拥有好消息.基本上所有DB约束都会在应用程序中重新编写.很多查询 - 不必要的db加载.

@Service("countryService")
public class CountryServiceImpl implements CountryService {

    @Inject
    private CountryRepository countryRepository;

    @Override
    public Country saveCountry(Country country) throws NameUniqueViolationException,  IsoCodeUniqueViolationException, UrlUniqueViolationException {
        if (!isUniqueNameInDatabase(country)) {
            throw new NameUniqueViolationException();
        }
        if (!isUniqueUrl(country)) {
            throw new UrlUniqueViolationException();
        }
        if (!isUniqueIsoCodeInDatabase(country)) {
            throw new IsoCodeUniqueViolationException();
        }
        return countryRepository.save(country);
    }
}
Run Code Online (Sandbox Code Playgroud)

在View的Backing Bean中,您可以处理异常:

@Component
@Scope(value = "view")
public class CountryBean {

    private Country country;

    @Inject
    private CountryService countryService;

    public void saveCountryAction() {
        try {
            countryService.saveCountry(country);
        } catch (NameUniqueViolationException e) {
            FacesContext.getCurrentInstance().addMessage("name", new FacesMessage("A country with the same name already exists."));
        } catch (IsoCodeUniqueViolationException e) {
            FacesContext.getCurrentInstance().addMessage("isocode", new FacesMessage("A country with the same isocode already exists."));
        } catch (UrlUniqueViolationException e) {
            FacesContext.getCurrentInstance().addMessage("url", new FacesMessage("A country with the same url already exists."));
        } catch (DataIntegrityViolationException e) {
             // update: in case of concurrent modfications. should not happen often
             FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("The country could not be saved."));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

方法2 - 让数据库检测约束违规

优点:没有锅炉板代码.对db没有不必要的查询.没有重复的数据约束逻辑.

缺点:依赖于DB中的约束名称,因此无法通过hibernate生成Schema.将消息绑定到输入组件所需的机制(例如,用于突出显示).

public class DataIntegrityViolationExceptionsAdvice {
    public void afterThrowing(DataIntegrityViolationException ex) throws DataIntegrityViolationException {

        // extract the affected database constraint name:
        String constraintName = null;
        if ((ex.getCause() != null) && (ex.getCause() instanceof ConstraintViolationException)) {
            constraintName = ((ConstraintViolationException) ex.getCause()).getConstraintName();
        }

        // create a detailed message from the constraint name if possible
        String message = ConstraintMsgKeyMappingResolver.map(constraintName);
        if (message != null) {
            throw new DetailedConstraintViolationException(message, ex);
        }
        throw ex;
    }
}
Run Code Online (Sandbox Code Playgroud)

Ral*_*lph 12

方法1在并发方案中不起作用! - 在您选中之后但在添加数据库记录之前,总会有其他人插入新数据库记录的更改.(除了你使用可序列化的隔离级别,但这是不太可能的)

因此,您必须处理数据库约束违例异常.但我建议捕获指示唯一违规的数据库异常,并像方法1中建议的那样抛出更多含义.

  • 我知道hibernate会忽略UniqueConstraint上的name属性.生成模式时不会使用它. (2认同)

ken*_*ken 9

这也可能是一个选项,并且可能成本更低,因为如果您无法直接保存,您只会检查详细的异常:

try {
    return countryRepository.save(country);
}
catch (DataIntegrityViolationException ex) {
    if (!isUniqueNameInDatabase(country)) {
        throw new NameUniqueViolationException();
    }
    if (!isUniqueUrl(country)) {
        throw new UrlUniqueViolationException();
    }
    if (!isUniqueIsoCodeInDatabase(country)) {
        throw new IsoCodeUniqueViolationException();
    }
    throw ex;
}
Run Code Online (Sandbox Code Playgroud)

  • 很好,但请记住,如果 save 位于用 @Transactional 注释的方法内,您将无法捕获异常 (3认同)
  • 谢谢,比方法1更好!我看到的唯一缺点是,在调用`countryRepository.save(country)`之后手动需要刷新. (2认同)