gpt4 book ai didi

grails - 无法在 Grails 3 中捕获数据库异常

转载 作者:行者123 更新时间:2023-12-02 15:17:50 26 4
gpt4 key购买 nike

在将应用程序从 Grails 2.5 迁移到 Grails 3.1 后,我遇到了一个奇怪的问题 - 数据库异常永远不会冒泡到 Controller 。

典型场景如下所示: Controller 的删除操作尝试删除条目,但数据库级约束阻止删除。这会导致异常(见下文) - 在 Grails 2.5 中,如果未被捕获,则会导致“错误 500 内部服务器错误”。现在,我们为这些自定义映射,因此即使发生 Uncaught Error ,也会以用户友好的方式处理,并且用户知道发生了什么事。

在 Grails 3 中,这停止了工作。异常被记录,但永远不会冒泡到 Controller 。所以在这种特殊情况下,我们的 Controller 只是继续并返回“204”HTTP 状态代码,就好像什么都没发生一样,最终用户甚至不知道出了什么问题。

我很困惑,我认为这一定是我想念的愚蠢的东西,但无法弄清楚。我已经测试了这个 Grails 2.5.5、3.1.16、3.2.12 和 3.3.2,但所有测试的 3.x 版本的行为方式几乎相同。还使用 Hibernate 4 和 5 进行了测试,并尝试了 H2 和 PostgreSQL,但无论如何我都得到了相同的行为。

最简单的重现方法是手动将 FK 关系(不执行删除操作)添加到 hibernate 生成的数据库模式并尝试通过 Controller 删除(下面的示例 #1),但某些多对-也可能会出现问题许多关系或其他场景,例如持久化新条目(并在数据库级别失败)。

更新 #1,2018 年 4 月 25 日:要使用 PostgreSQL 作为数据库和 Grails 3.3.x 创建一个最小的可重现测试用例,只需通过 grails create-app testapp 创建一个空白 grails 应用程序。 .然后调整 application.yml 为您的 PostgreSQL 实例配置数据源,并使用 dbCreate: validate模式,因为下面提供了架构。

添加一个最小域对象:

class Book {
String name
}

还有一个用于测试对象删除的 Controller ,即 BookController:
class BookController {
@Transactional
def delete(Long id) {
Book.get(id).delete()
render "Book $id deleted"
}
}

下面的模式创建了 2 个带有 FK 和一些条目的表。 id 1 的书会被删除失败,id 2 和 3 的书会被成功删除。在所有 3 种情况下, Controller 都会返回 200 OK。您可以通过 curl 调用使用默认 UrlMappings 进行测试,即 curl http://localhost:8080/book/delete/1 .

数据库架构如下:
CREATE TABLE book (
id bigint NOT NULL,
version bigint NOT NULL,
name character varying(255) NOT NULL,
CONSTRAINT book_pkey PRIMARY KEY (id)
);

CREATE TABLE chapter (
id bigint NOT NULL,
book_id bigint NOT NULL,
name character varying(255),
CONSTRAINT chapter_pkey PRIMARY KEY (id),
CONSTRAINT fk_book FOREIGN KEY (book_id) REFERENCES book(id)
);

INSERT INTO book VALUES (1, 0, 'Book 1');
INSERT INTO book VALUES (2, 0, 'Book 2');
INSERT INTO book VALUES (3, 0, 'Book 3');
INSERT INTO chapter VALUES (1, 1, 'Book 3 Chapter');

CREATE SEQUENCE hibernate_sequence START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
SELECT pg_catalog.setval('hibernate_sequence', 4, true);

为了完整起见,这里是堆栈跟踪:
2018-04-25 16:23:22.462 ERROR --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: update or delete on table "book" violates foreign key constraint "fk_book" on table "chapter"
Detail: Key (id)=(1) is still referenced from table "chapter".
2018-04-25 16:23:22.473 ERROR --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl : HHH000346: Error during managed flush [could not execute statement]
2018-04-25 16:23:22.720 ERROR --- [nio-8080-exec-1] o.g.web.errors.GrailsExceptionResolver : PSQLException occurred when processing request: [GET] /test/delete/1
ERROR: update or delete on table "book" violates foreign key constraint "fk_book" on table "chapter"
Detail: Key (id)=(1) is still referenced from table "chapter".. Stacktrace follows:

java.lang.reflect.InvocationTargetException: null
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [fk_book]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:241)
at org.springframework.orm.hibernate5.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:755)
at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:590)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:765)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:734)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:150)
at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:91)
... 14 common frames omitted
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:112)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3311)
at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3548)
at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:98)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:586)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:460)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:491)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:582)
... 18 common frames omitted
Caused by: org.postgresql.util.PSQLException: ERROR: update or delete on table "book" violates foreign key constraint "fk_book" on table "chapter"
Detail: Key (id)=(1) is still referenced from table "chapter".
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2433)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2178)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:306)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:155)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:132)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
... 36 common frames omitted

更新 #2,2018 年 4 月 25 日:存在 Hibernate 不知道的数据库表这一事实是上述示例的重点。这是一种强制数据库级异常的简单方法,该异常只会被记录,但不会一直到 Controller ,因此在 HTTP 级别上看起来好像没有发生错误。

更新 #3,2018 年 4 月 25 日:下面是另一个不依赖于任何手动数据库模式“hacks”的 Grails 3.3 测试用例,在同一域(用户)之间具有多对多关系。它由 2 个域类组成:
class User {
String name
static hasMany = [relationships: Relationship]
static mappedBy = [relationships: 'user']
static mapping = { table 'table_user' }
}

class Relationship implements Serializable {
User user
User related
static belongsTo = [ user: User, related: User ]
static mapping = {
table 'table_relationship'
id composite: ['user', 'related']
version false
}
}

和一个最小的 Controller :
class TestController {
@Transactional
def delete(Long id) {
User.get(id).delete()
render "User $id deleted"
}
}

这可以用 PostgreSQL 或 H2 重现,只需使用 create-drop 模式和以下 BootStrap 代码创建 2 个用户条目:
def init = { servletContext ->
def user1 = new User(name: 'User 1').save()
new User(name: 'User 2', relationships: [new Relationship(related: user1)]).save()
}

尝试删除用户 2,然后删除用户 1 可以正常工作。尝试首先删除用户 1 将失败并出现异常,但 Controller 仍将返回“200 OK”。试试 curl http://localhost/test/delete/1 .

为方便起见,您可以在此处下载带有可运行 Grails 3.3.5 应用程序的压缩存档: https://www.dropbox.com/s/pycwuxm7r0wyxem/grails3_exception_issue_testapp.zip?dl=0

最佳答案

如果您要使用 @Transactional你应该使用 grails.gorm.transactions.Transactional而不是 grails.transaction.Transactional .

除此之外,问题似乎并不在于异常没有到达 Controller ,而是真正发生的事情是直到您调用 render 之后才提交事务。并且在事务提交之前不会引发异常。调用.delete(flush: true)将显示不同的行为,但无论如何将事务边界放在 Controller 中是一个坏主意。一个更好的计划是将您的数据库交互放在事务服务中。

我将您的代码放入项目中 https://github.com/jeffbrown/many33 . https://github.com/jeffbrown/many33/commit/f8804c8793b399994c34043c1d340e4bf0d462cd 的提交显示了组织此代码的更好方法并产生较少令人惊讶的行为。

我希望这会有所帮助。

关于grails - 无法在 Grails 3 中捕获数据库异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49978221/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com