0

別のドメイン オブジェクトへの null 許容参照を持つ既存のドメイン オブジェクトに JSON ペイロードをバインドしようとすると、インバウンド JSON が参照に対して null を示している場合、バインドは失敗します。これにより、永続化の試行が次のエラーで失敗します。

| Error 2011-12-21 10:21:24,202 ["http-bio-8080"-exec-3] ERROR hibernate.AssertionFailure  - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
Message: null id in server.Uploader entry (don't flush the Session after an exception occurs)
    Line | Method
->>  105 | update    in server.SessionController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1110 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    603 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run       in java.lang.Thread
| Error 2011-12-21 10:21:24,206 ["http-bio-8080"-exec-3] ERROR errors.GrailsExceptionResolver  - AssertionFailure occurred when processing request: [POST] /Server/session/2
null id in server.Uploader entry (don't flush the Session after an exception occurs). Stacktrace follows:
Message: null id in server.Uploader entry (don't flush the Session after an exception occurs)
    Line | Method
->>  105 | update    in server.SessionController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1110 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    603 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run       in java.lang.Thread

問題のドメイン オブジェクトは次のとおりです。

class Session {
    DateTime dateCreated
    DateTime lastUpdated
    String ip
    String state

    static hasOne = [uploader:Uploader]
    static constraints = {
        ip blank: false
        state blank: false
        uploader nullable: true, unique: true
    }
}

class Uploader {
    DateTime dateCreated
    DateTime lastUpdated
    Session session
    String firstName
    String lastName
    String organization
    String phoneNumber
    String emailAddress

    static constraints = {
        firstName blank: false
        lastName blank: false
        organization blank: false
    }
}

エラーをスローするコントローラーコードは次のとおりです。

def update() {
    def sessionInstance = Session.get(params.id)
    if (!sessionInstance) {
        flash.message = message(code: 'default.not.found.message', args: [message(code: 'session.label', default: 'Session'), params.id])
        redirect(action: "list")
        return
    }

    if (params.version) {
        def version = params.version.toLong()
        if (sessionInstance.version > version) {
            sessionInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
                    [message(code: 'session.label', default: 'Session')] as Object[],
                    "Another user has updated this Session while you were editing")
            render(view: "edit", model: [sessionInstance: sessionInstance])
            return
        }
    }

    sessionInstance.properties = params

    if (!sessionInstance.save(flush: true)) {      // <---- Error thrown here
        render(view: "edit", model: [sessionInstance: sessionInstance])
        return
    }

    flash.message = message(code: 'default.updated.message', args: [message(code: 'session.label', default: 'Session'), sessionInstance.id])
    redirect(action: "show", id: sessionInstance.id)
}

これは、問題を引き起こす JSON ペイロードです。

{
"id":2,
"dateCreated":"2011-12-21T09:33:33-06:00",
"ip":"127.0.0.1",
"lastUpdated":"2011-12-21T09:33:33-06:00",
"state":"closed",
"uploader":null
}

しかし、この JSON ペイロードは問題なく動作します。

{
"class": "server.Session",
"id":2,
"dateCreated":"2011-12-21T09:33:33-06:00",
"ip":"127.0.0.1",
"lastUpdated":"2011-12-21T09:33:33-06:00",
"state":"closed",
"uploader":null
}

何が起こるかというとuploader、セッションのプロパティが に設定されてい"server.Uploader : null"ます。これは の後.dump()sessionInstanceオブジェクトsessionInstance.properties = paramsです:

<server.Session@713c6968 dateCreated=2011-12-21T09:33:33.000-06:00 lastUpdated=2011-12-21T09:33:33.000-06:00 ip=127.0.0.1 state=closed errors=grails.validation.ValidationErrors: 0 errors id=2 version=1 uploader=server.Uploader : null>

プロパティが JSON ペイロードにある場合はすべて正常に機能し"class": "server.Session"ますが、JSON ペイロードがないとすべてが機能しなくなります。データバインディングはこれを処理できると思います.残りのプロパティはうまくマッピングされていますが、参照にはひどく失敗しているようです.

最後に、これに遭遇した理由は、REST API に対して Griffon クライアントを構築しようとしているからです。HTTPBuilder は、元の JSON 応答を に変換するときに を取り除いているようです"class": "server.Session"net.sf.json.JSONObject応答にはプロパティが含まれていますが、ダンプには含まれてnet.sf.json.JSONObjectおらず、後続の JSON 要求ペイロードも含まれていません。

  1. これは、JSON オブジェクトの「クラス」プロパティがない場合の Grails のデータ バインド方法のバグですか?
  2. これは、HTTPBuilder が JSON 応答を解析する方法のバグですか?
  3. HTTPBuilder が net.sf.json.JSONObject から JSON を出力する方法のバグですか?
  4. 私は完全に間違ったことをしているだけですか?

アップデート

私はいくつかの追加情報を見つけました:

  1. 一意の制約を削除しても動作は変わりません
  2. uploaderフィールドがnullではなく、受信 JSON オブジェクトがそれを に設定しようとしている場合null、プロパティは変更されません (エラーは発生しません)。ただし、"class": "server.Session"プロパティが受信 JSON ペイロードにある場合はnull、期待どおりに変更されます。
4

1 に答える 1

0

IIRC Json-lib の JSONObject は、"class" や "metaClass" (POGO の場合) などの特別なプロパティを自動的に取り除きます。JsonConfig インスタンスを使用してこの動作を構成できますが、HTTPBuilder で適切なタイミングでそのようなインスタンスを挿入できるかどうかはわかりません。

別の方法 (ハックのようなもの) は、Json-lib が削除しない別の名前 (clazz など) でクラス プロパティ値を送信することです。「clazz」を「class」にマップするには、Grails 側でフィルターを登録する必要があります。

于 2011-12-21T18:12:52.113 に答える