很好地处理数据库约束错误

时间:2014-09-09 06:28:32

标签: java hibernate validation error-handling

然而它应该是简单的....我的任务是在我们的应用程序的域对象中放置一个具有唯一约束的特定字段。这本身并不是一个挑战。我刚做了以下几件事:

public class Location {
    // more fields

    @Column(unique = true)
    @NotNull
    private String locationName;

    // getters and setters
}

到目前为止一切顺利,一切顺利,我在对数据库运行一些测试时遇到错误。现在我继续开始实现Controller代码,该代码应该处理域对象的编辑。

然后一切都崩溃了,因为我从休眠状态回来的是一些堆栈跟踪,这似乎只是为了防止任何理智的处理:

18:30:20,173 ERROR [org.jboss.ejb3.invocation] (http-localhost-127.0.0.1-8443-2) JBAS014134: EJB Invocation failed on component LocationService for method public abstract long company.project.services.IService.update(java.lang.Object) throws novatec.crm.DataLayerLockException: javax.ejb.EJBTransactionRolledbackException: Transaction rolled back
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.handleEndTransactionException(CMTTxInterceptor.java:115) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.endTransaction(CMTTxInterceptor.java:95) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:232) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:304) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:190) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
    at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288) [jboss-invocation-1.1.1.Final.jar:1.1.1.Final]
    at org.jboss.as.ejb3.component.interceptors.CurrentInvocationContextInterceptor.processInvocation(CurrentInvocationContextInterceptor.java:41) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [rt.jar:1.6.0_45]
    ... 125 more
Caused by: javax.transaction.RollbackException: ARJUNA016053: Could not commit transaction.
    at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1177)
    at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:117)
    at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.endTransaction(CMTTxInterceptor.java:92) [jboss-as-ejb3-7.1.1.Final.jar:7.1.1.Final]
... 79 more
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Eindeutiger Index oder Primärschlüssel verletzt: "CONSTRAINT_INDEX_9 ON PUBLIC.LOCATION(LOCATIONNAME)"
Unique index or primary key violation: "CONSTRAINT_INDEX_9 ON PUBLIC.LOCATION(LOCATIONNAME)"; SQL statement:
update Location set housenumber=?, locationName=?, numberSuffix=?, street=?, town=?, version=?, zipcode=? where id=? and version=? [23505-161]
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1361) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1289) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1295) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.ejb.AbstractEntityManagerImpl$CallbackExceptionMapperImpl.mapManagedFlushFailure(AbstractEntityManagerImpl.java:1481) [hibernate-entitymanager-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.transaction.synchronization.internal.SynchronizationCallbackCoordinatorImpl.beforeCompletion(SynchronizationCallbackCoordinatorImpl.java:109) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization.beforeCompletion(RegisteredSynchronization.java:53) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:76)
    at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:273)
    at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:93)
    at com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:164)
    at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1165)
    ... 82 more
Caused by: org.hibernate.exception.ConstraintViolationException: Eindeutiger Index oder Primärschlüssel verletzt: "CONSTRAINT_INDEX_9 ON PUBLIC.LOCATION(LOCATIONNAME)"
Unique index or primary key violation: "CONSTRAINT_INDEX_9 ON PUBLIC.LOCATION(LOCATIONNAME)"; SQL statement:
update Location set housenumber=?, locationName=?, numberSuffix=?, street=?, town=?, version=?, zipcode=? where id=? and version=? [23505-161]
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:128) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:129) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at com.sun.proxy.$Proxy79.executeUpdate(Unknown Source)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3010) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2908) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3237) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:113) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:272) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:264) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:187) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:326) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1081) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:315) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    at org.hibernate.engine.transaction.synchronization.internal.SynchronizationCallbackCoordinatorImpl.beforeCompletion(SynchronizationCallbackCoordinatorImpl.java:104) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    ... 88 more
Caused by: org.h2.jdbc.JdbcSQLException: Eindeutiger Index oder Primärschlüssel verletzt: "CONSTRAINT_INDEX_9 ON PUBLIC.LOCATION(LOCATIONNAME)"
Unique index or primary key violation: "CONSTRAINT_INDEX_9 ON PUBLIC.LOCATION(LOCATIONNAME)"; SQL statement:
update Location set housenumber=?, locationName=?, numberSuffix=?, street=?, town=?, version=?, zipcode=? where id=? and version=? [23505-161]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
    at org.h2.message.DbException.get(DbException.java:169)
    at org.h2.message.DbException.get(DbException.java:146)
    at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:81)
    at org.h2.index.PageBtree.find(PageBtree.java:121)
    at org.h2.index.PageBtreeLeaf.addRow(PageBtreeLeaf.java:146)
    at org.h2.index.PageBtreeLeaf.addRowTry(PageBtreeLeaf.java:100)
    at org.h2.index.PageBtreeIndex.addRow(PageBtreeIndex.java:105)
    at org.h2.index.PageBtreeIndex.add(PageBtreeIndex.java:96)
    at org.h2.table.RegularTable.addRow(RegularTable.java:121)
    at org.h2.table.Table.updateRows(Table.java:439)
    at org.h2.command.dml.Update.update(Update.java:128)
    at org.h2.command.CommandContainer.update(CommandContainer.java:73)
    at org.h2.command.Command.executeUpdate(Command.java:219)
    at org.h2.server.TcpServerThread.process(TcpServerThread.java:302)
    at org.h2.server.TcpServerThread.run(TcpServerThread.java:137)
    at java.lang.Thread.run(Unknown Source)

    at org.h2.engine.SessionRemote.done(SessionRemote.java:538)
    at org.h2.command.CommandRemote.executeUpdate(CommandRemote.java:183)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:143)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:129)
    at org.jboss.jca.adapters.jdbc.WrappedPreparedStatement.executeUpdate(WrappedPreparedStatement.java:493)
    at sun.reflect.GeneratedMethodAccessor391.invoke(Unknown Source) [:1.6.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [rt.jar:1.6.0_45]
    at java.lang.reflect.Method.invoke(Method.java:597) [rt.jar:1.6.0_45]
    at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:122) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
    ... 102 more

我不想与此有任何关系,我希望将我的可怜的用户从这样的例外中拯救出来,当他崩溃到他脸上时,他无法预料到这一点。所以我虽然:嘿,我应该隐藏这个......我做的是以下内容:

try {
    service.update(object);
} catch (EJBTransactionRolledbackException e) {
    Throwable root = e.getCause();
    while (root.getCause() != null) {
        root = root.getCause();
    }
    if (root instanceof java.sql.SQLException) {
        // duplicate location-name
        resources.produceFacesContext().addMessage(
            "code",
            new FacesMessage(FacesMessage.SEVERITY_WARN, "Fehler:",
                    "Der Standortname muss einmalig sein!"));
        // german for: "Error:", "LocationName must be unique"
    }
}

我认为必须是处理此问题的更好方法。不应该有必要恢复这种糟糕的变通方法,只是为了找出究竟是什么破坏了,这样你才能告诉用户他所犯的错误。

当然,我可以尝试针对一个运行防御性SELECT语句的Validator运行域对象。但是在应用程序逻辑中强制执行数据库约束比在遍历异常根本原因时感觉更脏......

感觉我正在忽略一些非常明显的东西。

P.S。如果我没有捕获异常,一切都会中断......没有很好的处理ConstraintViolationException。我想这是因为那里没有定义ConstraintViolations ...

2 个答案:

答案 0 :(得分:2)

解析异常总是是一个坏主意。特别是你在这里做的是因为你假设任何SQL异常都是约束违规的结果,实际上SQLException可以有很多东西。

如果你在一个字段上设置一个数据库唯一约束,然后尝试输入两次,这就是你将得到的,显而易见的解决方案是不要试图在错误发生时捕获它,而是手动检查它是否存在于插入之前的数据库。

答案 1 :(得分:0)

我会使用一个拦截根本原因的拦截器,因此在上层处理异常更容易。

要解开根本原因,您可以使用ExceptionUtils

org.apache.commons.lang3.exception.ExceptionUtils#getRooutCause

业务层拦截器应该捕获所有运行时异常,解包它们并进一步抛出它们。

您可以查看Spring PersistenceExceptionTranslator,它接受​​RuntimeException并将其转换为DataAccessException,从而将您与各种特定于框架的异常类型隔离开来。