2013-02-27 11 views
19

Spring 3.2で特別な部分更新機能を実装しようとしています。私たちはバックエンドにSpringを使用しており、単純なJavascriptフロントエンドを持っています。 という私たちの要求に直接的な解決策を見つけることができませんでした。update()関数は値の任意の数を取り、それに応じて永続性モデルを更新する必要があります。Spring Partial Updateオブジェクトのデータバインディング

ユーザーがフィールドを編集して確認すると、IDと変更されたフィールドがjsonとしてコントローラに渡されるように、すべてのフィールドのインライン編集が行われます。コントローラは、クライアントから任意の数のフィールドを取り込み(1〜n)、それらのフィールドのみを更新できる必要があります。

例えば、ID == 1を持つユーザーが自分のdisplayNameを編集するとき、サーバーにポストされたデータは、次のようになります。下記のとおり

{"id":"1", "displayName":"jim"} 

現在、我々はUserControllerで中に不完全な解決策を持っている:

@RequestMapping(value = "/{id}", method = RequestMethod.POST) 
public @ResponseBody ResponseEntity<User> update(@RequestBody User updateUser) { 
    dbUser = userRepository.findOne(updateUser.getId()); 
    customObjectMerger(updateUser, dbUser); 
    userRepository.saveAndFlush(updateUuser); 
    ... 
} 

ここでのコードは動作しますが、いくつかの問題があります。@RequestBodyは新しいupdateUserを作成し、iddisplayNameで埋めます。 CustomObjectMergerは、updateUserに含まれる唯一のフィールドを更新して、このupdateUserを対応するdbUserとマージします。

問題点は、updateUserのいくつかのフィールドに、デフォルト値とその他の自動生成フィールド値が入力されていることです。このフィールド値は、マージ時にdbUserにある有効なデータを上書きします。私たちのupdateがこれらのフィールドも同様に設定できるようにするため、これらのフィールドを無視すべきであることを明示的に宣言するオプションはありません。

私は、Springが自動的にupdate()関数に明示的に送信された情報のみをdbUserに(何らかのデフォルト/自動フィールド値をリセットせずに)マージするようにしています。これを行う簡単な方法はありますか?

更新:私はすでにほとんど私が求めているが、それほどではない次のオプションを検討しました。それは@RequestParamように更新データを受け取り、(私の知る限り)JSON文字列をしないという問題がある。

//load the existing user into the model for injecting into the update function 
@ModelAttribute("user") 
public User addUser(@RequestParam(required=false) Long id){ 
    if (id != null) return userRepository.findOne(id); 
    return null; 
} 
.... 
//method declaration for using @MethodAttribute to pre-populate the template object 
@RequestMapping(value = "/{id}", method = RequestMethod.POST) 
public @ResponseBody ResponseEntity<User> update(@ModelAttribute("user") User updateUser){ 
.... 
} 

私が考えられてきた再書き込み、JSONをより適切に動作するように私のcustomObjectMerger()をカウントし、それを取るました考慮するフィールドは、HttpServletRequestから入ってくるフィールドだけです。最初の場所でcustomObjectMerger()を使用する必要があっても、春がのJSON機能を除いたほぼ正確にを提供しているときにハックを感じます。誰かがこれを行うためにSpringを取得する方法を知っていれば、私は大いに感謝しています!

+0

@SamEsla - 特にネストされたオブジェクトが存在する状況で、より良い解決策を見つけましたか?私は同じ問題を抱えています。この記事を読む前に、あなたと似たようなことをしました..時間があるなら、見てください:http://stackoverflow.com/questions/16473727/spring-3-ajax-post-request-with -requestbody-and-modelattribute-and-sessionatt – arcseldon

答えて

18

この同じ問題が発生しました。私の現在の解決策は次のようになります。私はまだ多くのテストを行っていませんが、最初の検査ではかなりうまくいくように見えます。

@Autowired ObjectMapper objectMapper; 
@Autowired UserRepository userRepository; 

@RequestMapping(value = "/{id}", method = RequestMethod.POST) 
public @ResponseBody ResponseEntity<User> update(@PathVariable Long id, HttpServletRequest request) throws IOException 
{ 
    User user = userRepository.findOne(id); 
    User updatedUser = objectMapper.readerForUpdating(user).readValue(request.getReader()); 
    userRepository.saveAndFlush(updatedUser); 
    return new ResponseEntity<>(updatedUser, HttpStatus.ACCEPTED); 
} 

ObjectMapperは、org.codehaus.jackson.map.ObjectMapper型のBeanです。これは誰かに役立ちます

希望、

編集:

は、子オブジェクトの問題に実行しました。子オブジェクトが部分的に更新するプロパティを受け取った場合、新しいオブジェクトが作成され、そのプロパティが更新され、設定されます。これにより、そのオブジェクトの他のすべてのプロパティが消去されます。私がきれいな解決策を見つけたら、私は更新します。

+0

タイラーありがとう、これは便利なソリューションのようです。私は、JSONをPOSTデータから永続化されたオブジェクトに直接/自動バインディングできるようにするSpringの機能があるかどうか不思議です。 – SamEsla

+0

ありがとうございます。この状況を解決する唯一の方法は手動で処理することだと私は考えました。以前はJackson ObjectMapperを知らなかった... –

+0

@Tyler - 特に入れ子になったオブジェクトがある状況では、より良い解決策を見つけたことがありますか?私は同じ問題を抱えています。この記事を読む前に、OPに似た何かをしました。時間がある場合は次を参照してください:http://stackoverflow.com/questions/16473727/spring-3-ajax-post-request-with-requestbody-and-modelattribute-and-sessionatt – arcseldon

0

主な問題は、あなたの次のコードである:上記の関数で

@RequestMapping(value = "/{id}", method = RequestMethod.POST) 
public @ResponseBody ResponseEntity<User> update(@RequestBody User updateUser) { 
    dbUser = userRepository.findOne(updateUser.getId()); 
    customObjectMerger(updateUser, dbUser); 
    userRepository.saveAndFlush(updateUuser); 
    ... 
} 

、あなたのプライベート関数&クラス(userRepository、customObjectMerger、...)の一部を呼び出しますが、何の説明どのようにそれを与えませんどのように機能しているのかを知ることができます。だから私は唯一の推測することができます。

CustomObjectMergerは updateUserに含まれたフィールドのみを更新し、データベースから対応する dbUserので、このupdateUserをマージします。

ここでは、CustomObjectMergerで何が起こったのかわかりません(これはあなたの機能であり、表示していません)。しかし、私が推測することができます:updateUserのすべてのプロパティをデータベースのオブジェクトにコピーします。これは絶対に間違った方法です。Springオブジェクトをマップすると、すべてのデータがで埋められます。特定のプロパティを更新するだけです。

1)サーバへの変わらないプロパティを含むすべてのプロパティを()送信:

は、あなたのケースでは2つのオプションがあります。これは少しより多くの帯域幅を要するかもしれないが、あなたはまだ

2あなたの方法を保つ)あなたはユーザーオブジェクト(例えば、ID = -1、年齢= -1のデフォルト値として、いくつかの特別な値を設定する必要があります... )。次に、customObjectMergerでは、-1でない値を設定するだけです。

上記の2つの解決策が満足できないと感じる場合は、jsonリクエストを自分で解析し、S​​pringオブジェクトマッピングメカニズムを気にする必要はありません。ときどきそれはちょっと混乱します。

+0

こんにちはホアン、あなたの答えをありがとう。 '@ RequestBody'は私のペイロードを' User'オブジェクトの新しいインスタンスに変換します。個人的な方法の内臓は、手元の問題にとって重要ではない。 '@ ModelAttribute'が私が探しているのと同じ機能を果たすためにどのように働くのかを知るために、Martin Feyの答えと私の更新を見てください。しかし、要求に来る' RequestParams'で動作します。このメソッドを使用すると、Springは新しい情報を 'RequestParams'から自動的にデータベースから取得した既存のオブジェクトにバインドします。 JSONペイロードでこれを行う方法については、Spring Docsを精査してきました。 – SamEsla

+0

@SamEsla:私はFeyの方法とあなたの質問を理解しています。私の意見では、Feyのやり方は賢明ですが、それはあまり良いことではありません。以前のModelAttributeを使って作業していて、特に彼がコメントしているように、コントローラのDAOオブジェクトを使って作業しているときに、私自身はいくつかの問題を経験しています。 –

+0

@SamEsla:ああ、私はあなたの質問を誤解しました。私はRequestBodyを書いたが、どういうわけか私はあなたがResponseBodyを意味すると思う。私の目がどれほど悪いのか@ _ @私はその無関係な部分を削除する答えを更新しました。 –

1

@ModelAttributeを使用して、あなたがしたいことを達成しています。

  • @ modelattributeでアノテーションされたメソッドを作成します。このメソッドは、リポジトリのパス変数に基づいてユーザーをロードします。

  • は、ここでのparam @modelattribute

と点@RequestMappingメソッドを作成@modelattribute方法は、モデルの初期化であるということです。次に、@requestmappingメソッドで宣言しているので、springはリクエストをこのモデルとマージします。

これは、部分的な更新機能を提供します。

いくつか、またはさらには? ;)私たちは、コントローラに直接私たちのDAOを使用すると、専用のサービス層で、このマージを行いませんので、これはとにかく悪い習慣であることを主張するだろう。しかし、現在我々はこの問題のために問題に遭遇していない。

+0

マーティンありがとう、私はすでにこれを試しました。このメソッドの問題点は、フィールドを更新するために '@ RequestParam'だけを使うように思えることです。私たちはあなたが言及したこの正確な機能を複製する方法を望んでいますが、JSONを入力として扱っています。私はSpringにこれのための組み込みの機能があると確信していますが、私はまだそれに踏み込んでいません。 – SamEsla

+0

これは同様に動作するはずです。私たちは現在ポストデータでそれを使用していますが、私はjsonオブジェクトでそれをしようとします。結局のところ、これは非常に似ています。モデルアトリビュートに一致するもの(requestparam、json props)はすべてマージする必要があります。私はipadのatmの後で私は明日の短い例をしようとします。 –

+1

私の答えは3週間以上遅れました..しかし、結局私はちょっと調べてみる時間がありました。 HoàngLongが正しいです。 json requestbodysをより洗練された方法で処理する方法はありません。我々はリクエストボディーの合併を逃しているように見えます:)春のリクエストか? –

0

部分的な更新は、customObjectMergerで自分で行ったことを行うために作られた@SessionAttributes機能を使用することで解決できます。ここに私の答えで

見て、特に編集、あなたが始めるために:

https://stackoverflow.com/a/14702971/272180

1

私はコールpersisteまたはマージしたり、更新前のエンティティとビューオブジェクトをマージAPIを構築します。

最初のバージョンだと思いますが、これは始まりです。

ちょうどその使用あなたのPOJO`Sフィールドに注釈UIAttributeを使用します。

MergerProcessor.merge(pojoUi, pojoDb);

をそれは、ネイティブ属性とコレクションで動作します。

のgit:https://github.com/nfrpaiva/ui-merge

1

次の方法を使用することができます。

このシナリオでは、エンティティが部分的に更新されるため、PATCHメソッドが適切です。

コントローラメソッドでは、リクエストボディを文字列として取ります。

この文字列をJSONObjectに変換します。次に、キーを反復し、一致する変数を入力データで更新します。

import org.json.JSONObject; 

@RequestMapping(value = "/{id}", method = RequestMethod.PATCH) 
public ResponseEntity<?> updateUserPartially(@RequestBody String rawJson, @PathVariable long id){ 

    dbUser = userRepository.findOne(id); 

    JSONObject json = new JSONObject(rawJson); 

    Iterator<String> it = json.keySet().iterator(); 
    while(it.hasNext()){ 
     String key = it.next(); 
     switch(key){ 
      case "displayName": 
       dbUser.setDisplayName(json.get(key)); 
       break; 
      case "....": 
       .... 
     } 
    } 
    userRepository.save(dbUser); 
    ... 
} 

このアプローチの欠点は、入力値を手動で検証する必要があることです。

+0

こんにちは!応答していただきありがとうございます。後日のPatchは自然な解決策のようですが、バージョン3.2で新しいSpring Frameworkの機能として数ヶ月前に実装されて以来、私たちは誰も考えていないと思います。 – SamEsla

関連する問題