0

我有这两个域类:

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

你知道我可能做错了什么吗?

提前感谢您的回答。

4

1 回答 1

1

I believe what's happening is that, as you are iterating over the User.organizations collection and removing each association between User/Organization, Hibernate is trying to update that same User.organizations collection (to reflect your deletion) - which prevents the association deletion, which causes your constraint violation.

I've never seen a solution for this issue that uses beforeDelete (unfortunate since it seems like the logical place for that logic), but have seen the following work-arounds:

  1. Place the logic to remove the associations in controller/service layer before the delete (again too bad beforeDelete doesn't work :( in this situation). To avoid concurrently modifying User.organizations, you can make a copy of the collection and iterate over the copy, or query for the associated Organizations, here's a sample query:

    select o from Organization o join o.members AS m where m.id=:id
  2. Make the association between User/Organization explicit, like:

    class UserOrganization {
      User user
      Organization org
    }
    
    class User {
      ...
      Set<Organization> getOrganizations() {
        UserOrganization.findAllByUser(this).collect{ it.org } as Set
      }
    }
    

If you look at the SpringSecurityCore plugin User/Authority code, there's a good example of how to manage associations explicitly.

于 2012-10-14T08:28:56.333 回答