我们正在开发基于JPA 2,Hibernate,Spring 3和在Tomcat 7中运行的JSF 2的Java Web项目。我们使用Oracle 11g作为数据库。
我们目前正在讨论将数据库约束违规填充为UI的用户友好消息的方法。或多或少我们看到两种方式,两种方式都不令人满意。有人可以提供一些建议吗?
方法1 - 以编程方式验证并抛出特定异常
在CountryService.java中,将验证每个Unique约束并抛出相应的异常。异常在辅助bean中单独处理。
优势:易于理解和维护。可能的特定用户消息。
缺点:很多代码只是为了拥有好消息。基本上所有DB约束都会在应用程序中重新编写。很多查询 - 不必要的db load。
@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);
}
}
在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."));
}
}
}
方法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;
}
}
答案 0 :(得分:12)
方法1在并发场景中不起作用! - 在您选中之后但在添加数据库记录之前,总会有其他人插入新数据库记录的更改。 (除了你使用可序列化的隔离级别,但这是非常不可能的)
因此,您必须处理数据库约束违规异常。但我建议捕获表示唯一违规的数据库异常,并像方法1中建议的那样抛出更多含义。
答案 1 :(得分: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;
}
答案 2 :(得分:0)
为避免样板我在DataIntegrityViolationException
中处理ExceptionInfoHandler
,在根本原因消息中查找DB约束,并通过map将其转换为i18n消息。请参阅此处的代码:https://stackoverflow.com/a/42422568/548473