GORM - 在beforeDelete事件中修改对象不起作用

时间:2012-10-10 14:36:44

标签: hibernate grails gorm

我有这两个域类:

class User {
    String username
    String password

    static hasMany = [organizations: Organization]
    static belongsTo = Organization

    static constraints = {
        username blank: false, unique: true
        password blank: false
    }

    static mapping = {
        password column: '`password`'
        organizations fetch: 'join'
    }

    def beforeDelete() {
        User.withNewSession {
            this.organizations.each {
                it.removeFromMembers(this)
            }
            this.save()
        }
    }
}

class Organization {
    String name;

    static hasMany = [members: User]
}

我想要实现的是我非常清楚 - 我希望能够删除用户但在此之前,我必须从他被分配到的所有组织中删除用户。 GORM显然不支持这一点,所以我尝试在类User上编写beforeDelete事件,该事件将在删除之前从所有组织中删除用户。问题是上面的代码不起作用,这是它抛出的异常:

| Error 2012-10-10 16:27:56,898 [http-bio-8080-exec-2] ERROR errors.GrailsExceptionResolver  - NonUniqueObjectException occurred when processing request: [POST] /theses-management/user/index - parameters:
id: 1
_action_delete: Delete
a different object with the same identifier value was already associated with the session: [com.redhat.theses.auth.User#1]. Stacktrace follows:
Message: a different object with the same identifier value was already associated with the session: [com.redhat.theses.auth.User#1]
    Line | Method
->>   36 | doCall             in com.redhat.theses.auth.User$_beforeDelete_closure1$$ENlS7bOi
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     32 | beforeDelete       in com.redhat.theses.auth.User$$ENlS7bOi
|     46 | onApplicationEvent in org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
|     93 | delete             in com.redhat.theses.auth.UserController$$ENlS7klM
|    195 | doFilter . . . . . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter           in grails.plugin.cache.web.filter.AbstractFilter
|   1110 | runWorker . . . .  in java.util.concurrent.ThreadPoolExecutor
|    603 | run                in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run . . . . . . .  in java.lang.Thread

所以我厌倦了这样改变beforeDelete事件:

def beforeDelete() {
    User.withNewSession {
        this.organizations.each {
            it.removeFromMembers(this)
        }
        this.merge()
        this.save()
    }
}

但是同样的例外被抛出......

另一种尝试是:

def beforeDelete() {
    User.withNewSession {
        this.organizations.each {
            it.removeFromMembers(this)
        }
        def user = this.merge()
        user.save()
    }
}

结果是这个例外:

| Error 2012-10-10 16:33:22,669 [http-bio-8080-exec-7] ERROR util.JDBCExceptionReporter  - Referential integrity constraint violation: "FK6BC87A0D3E1F37F9: PUBLIC.ORGANIZATION_MEMBERS FOREIGN KEY(USER_ID) REFERENCES PUBLIC.USER(ID)"; SQL statement:
delete from user where id=? and version=? [23503-164]
| Error 2012-10-10 16:33:22,673 [http-bio-8080-exec-7] ERROR events.PatchedDefaultFlushEventListener  - Could not synchronize database state with session
Message: could not delete: [com.redhat.theses.auth.User#1]
    Line | Method
->>   93 | delete    in com.redhat.theses.auth.UserController$$ENlS7klM
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1110 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    603 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run       in java.lang.Thread

Caused by JdbcSQLException: Referential integrity constraint violation: "FK6BC87A0D3E1F37F9: PUBLIC.ORGANIZATION_MEMBERS FOREIGN KEY(USER_ID) REFERENCES PUBLIC.USER(ID)"; SQL statement:
delete from user where id=? and version=? [23503-164]
->>  329 | getJdbcSQLException in org.h2.message.DbException
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    169 | get       in     ''
|    146 | get . . . in     ''
|    398 | checkRow  in org.h2.constraint.ConstraintReferential
|    415 | checkRowRefTable in     ''
|    291 | checkRow  in     ''
|    862 | fireConstraints in org.h2.table.Table
|    879 | fireAfterRow in     ''
|     99 | update .  in org.h2.command.dml.Delete
|     73 | update    in org.h2.command.CommandContainer
|    226 | executeUpdate in org.h2.command.Command
|    143 | executeUpdateInternal in org.h2.jdbc.JdbcPreparedStatement
|    129 | executeUpdate in     ''
|    105 | executeUpdate in org.apache.commons.dbcp.DelegatingPreparedStatement
|     93 | delete .  in com.redhat.theses.auth.UserController$$ENlS7klM
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1110 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    603 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run       in java.lang.Thread
| Error 2012-10-10 16:33:22,680 [http-bio-8080-exec-7] ERROR errors.GrailsExceptionResolver  - JdbcSQLException occurred when processing request: [POST] /theses-management/user/index - parameters:
id: 1
_action_delete: Delete
Referential integrity constraint violation: "FK6BC87A0D3E1F37F9: PUBLIC.ORGANIZATION_MEMBERS FOREIGN KEY(USER_ID) REFERENCES PUBLIC.USER(ID)"; SQL statement:
delete from user where id=? and version=? [23503-164]. Stacktrace follows:
Message: Referential integrity constraint violation: "FK6BC87A0D3E1F37F9: PUBLIC.ORGANIZATION_MEMBERS FOREIGN KEY(USER_ID) REFERENCES PUBLIC.USER(ID)"; SQL statement:
delete from user where id=? and version=? [23503-164]
    Line | Method
->>  329 | getJdbcSQLException   in org.h2.message.DbException
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    169 | get                   in     ''
|    146 | get . . . . . . . . . in     ''
|    398 | checkRow              in org.h2.constraint.ConstraintReferential
|    415 | checkRowRefTable . .  in     ''
|    291 | checkRow              in     ''
|    862 | fireConstraints . . . in org.h2.table.Table
|    879 | fireAfterRow          in     ''
|     99 | update . . . . . . .  in org.h2.command.dml.Delete
|     73 | update                in org.h2.command.CommandContainer
|    226 | executeUpdate . . . . in org.h2.command.Command
|    143 | executeUpdateInternal in org.h2.jdbc.JdbcPreparedStatement
|    129 | executeUpdate . . . . in     ''
|    105 | executeUpdate         in org.apache.commons.dbcp.DelegatingPreparedStatement
|     93 | delete . . . . . . .  in com.redhat.theses.auth.UserController$$ENlS7klM
|    195 | doFilter              in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter . . . . . .  in grails.plugin.cache.web.filter.AbstractFilter
|   1110 | runWorker             in java.util.concurrent.ThreadPoolExecutor
|    603 | run . . . . . . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run                   in java.lang.Thread

你知道我可能做错了吗?

提前感谢您的回答。

1 个答案:

答案 0 :(得分:1)

我相信正在发生的事情是,当您迭代User.organizations集合并删除User / Organization之间的每个关联时,Hibernate正在尝试更新同一个User.organizations集合(以反映您的删除) - 这会阻止关联删除,导致您的约束违规。

我从来没有见过使用beforeDelete的这个问题的解决方案(不幸因为它似乎是逻辑的合理位置),但是已经看到了以下解决方法:

  1. 放置逻辑以在删除之前删除控制器/服务层中的关联(在删除之前再次太糟糕了:(在这种情况下)。为了避免同时修改User.organizations,您可以制作副本的集合和迭代副本,或查询相关的组织,这是一个示例查询:

    select o from Organization o join o.members AS m where m.id=:id
  2. 明确用户/组织之间的关联,如:

    class UserOrganization {
      User user
      Organization org
    }
    
    class User {
      ...
      Set<Organization> getOrganizations() {
        UserOrganization.findAllByUser(this).collect{ it.org } as Set
      }
    }
    
  3. 如果你看一下SpringSecurityCore插件用户/权限代码,就会有一个很好的例子来说明如何明确地管理关联。