私たちはTomcat7で実行されているJPA2、Hibernate、Spring 3、JSF2に基づくJava Webプロジェクトに取り組んでいます。データベースとしてOracle11gを使用しています。
現在、UIへのユーザーフレンドリーなメッセージとしてデータベース制約違反を設定する方法について議論を行っています。多かれ少なかれ2つの方法がありますが、どちらも実際には満足のいくものではありません。誰かがアドバイスをしてもらえますか?
アプローチ1-プログラムで検証し、特定の例外をスローします
CountryService.Javaでは、各一意の制約が検証され、対応する例外がスローされます。例外は、バッキングBeanで個別に処理されます。
利点:理解と保守が簡単です。特定のユーザーメッセージが可能です。
デメリット:ニースメッセージを表示するためのコードがたくさんあります。基本的に、すべての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);
}
}
ビューのバッキング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の制約名に依存しているため、休止状態を介してスキーマを生成することはできません。メッセージを入力コンポーネントにバインドするために必要なメカニズム(強調表示など)。
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;
}
}
アプローチ1は、並行シナリオでは機能しません。 -チェックした後、データベースレコードを追加する前に、他の誰かが新しいデータベースレコードを挿入するという変更が常にあります。 (シリアル化可能な分離レベルを使用する場合を除きますが、これはほとんどありません)
したがって、DB制約違反の例外を処理する必要があります。ただし、固有の違反を示すデータベース例外をキャッチし、アプローチ1で提案したように、より意味のあるものをスローすることをお勧めします。
これもオプションであり、完全に保存できない場合にのみ詳細な例外のチェックを行うため、コストが低くなる可能性があります。
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;
}
ボイラープレートを回避するために、DataIntegrityViolationException
でExceptionInfoHandler
を処理し、根本原因メッセージでDB制約の発生を見つけて、マップを介してi18nメッセージに変換します。ここのコードを参照してください: https://stackoverflow.com/a/42422568/54847