25

Spring3.2で特別な部分更新機能を実装しようとしています。バックエンドにSpringを使用しており、単純なJavascriptフロントエンドがあります。要件に対する簡単な解決策を見つけることができませんでした。つまり、update()関数は任意の数のfield:valuesを受け取り、それに応じて永続性モデルを更新する必要があります。

すべてのフィールドをインライン編集しているため、ユーザーがフィールドを編集して確認すると、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し、idとを入力しdisplayNameます。これをデータベースの対応するものCustomObjectMergerとマージし、に含まれるフィールドのみを更新します。updateUserdbUserupdateUser

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

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

更新:私はすでに、私が求めていることをほぼ実行する次のオプションを検討しましたが、完全ではありません。問題は、更新データをas@RequestParamで受け取り、(AFAIK)は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()、から入ってくるフィールドのみをカウントして考慮に入れることを検討しましたHttpServletRequestcustomObjectMerger()しかし、そもそもを使用しなければならないことでさえ、JSON機能の欠如を除いて、春が私が探しているものをほぼ正確に提供するとき、ハッキーに感じます。Springにこれを実行させる方法を誰かが知っているなら、私はそれを大いに感謝します!

4

8 に答える 8

25

私はちょうどこれと同じ問題に遭遇しました。私の現在の解決策は次のようになります。私はまだ多くのテストを行っていませんが、最初の検査ではかなりうまく機能しているようです。

@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です。

これが誰かに役立つことを願っています、

編集:

子オブジェクトで問題が発生しました。子オブジェクトが部分的に更新するプロパティを受け取ると、新しいオブジェクトが作成され、そのプロパティが更新されて設定されます。これにより、そのオブジェクトの他のすべてのプロパティが消去されます。クリーンなソリューションに出会ったら更新します。

于 2013-02-28T20:56:32.067 に答える
4

@ModelAttributeを使用して、実行したいことを実現しています。

  • リポジトリを介してパス変数に基づいてユーザーをロードする@modelattributeという注釈の付いたメソッドを作成します。

  • パラメータ@modelattributeを使用してメソッド@Requestmappingを作成します

ここでのポイントは、@modelattributeメソッドがモデルの初期化子であるということです。次に、@ requestmappingメソッドで宣言するため、Springはリクエストをこのモデルとマージします。

これにより、部分的な更新機能が提供されます。

いくつか、あるいはたくさん?;)DAOをコントローラーで直接使用し、専用のサービスレイヤーでこのマージを行わないため、これはとにかく悪い習慣であると主張します。しかし、現在、このアプローチのために問題に遭遇することはありませんでした。

于 2013-02-28T06:35:21.537 に答える
3

永続化、マージ、または更新を呼び出す前に、ビューオブジェクトをエンティティとマージするAPIを構築します。

初版ですが、スタートだと思います。

POJO`SフィールドでアノテーションUIAttributeを使用してから、次を使用します。

MergerProcessor.merge(pojoUi, pojoDb);

ネイティブの属性とコレクションで動作します。

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

于 2015-11-28T05:05:44.147 に答える
3

以下のアプローチを使用できます。

このシナリオでは、エンティティが部分的に更新されるため、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);
    ...
}

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

于 2016-09-03T22:07:57.063 に答える
2

私はカスタマイズされたダーティなソリューションでjava.lang.reflectパッケージを採用しています。私のソリューションは3年間問題なく機能しました。

私のメソッドは2つの引数を取ります。objectFromRequestとobjectFromDatabaseはどちらもObject型です。

コードは単純に次のことを行います。

if(objectFromRequest.getMyValue() == null){
   objectFromDatabase.setMyValue(objectFromDatabase.getMyValue); //change nothing
} else {
   objectFromDatabase.setMyValue(objectFromRequest.getMyValue); //set the new value
}

リクエストからのフィールドの「null」値は、「変更しないでください!」を意味します。

-名前が「Id」で終わる参照列の値が1の場合、「nullに設定」を意味します。

さまざまなシナリオに合わせて、多くのカスタム変更を追加することもできます。

public static void partialUpdateFields(Object objectFromRequest, Object objectFromDatabase) {
    try {
        Method[] methods = objectFromRequest.getClass().getDeclaredMethods();

        for (Method method : methods) {
            Object newValue = null;
            Object oldValue = null;
            Method setter = null;
            Class valueClass = null;
            String methodName = method.getName();
            if (methodName.startsWith("get") || methodName.startsWith("is")) {
                newValue = method.invoke(objectFromRequest, null);
                oldValue = method.invoke(objectFromDatabase, null);

                if (newValue != null) {
                    valueClass = newValue.getClass();
                } else if (oldValue != null) {
                    valueClass = oldValue.getClass();
                } else {
                    continue;
                }
                if (valueClass == Timestamp.class) {
                    valueClass = Date.class;
                }

                if (methodName.startsWith("get")) {
                    setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("get", "set"),
                            valueClass);
                } else {
                    setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("is", "set"),
                            valueClass);
                }

                if (newValue == null) {
                    newValue = oldValue;
                }

                if (methodName.endsWith("Id")
                        && (valueClass == Number.class || valueClass == Integer.class || valueClass == Long.class)
                        && newValue.equals(-1)) {
                    setter.invoke(objectFromDatabase, new Object[] { null });
                } else if (methodName.endsWith("Date") && valueClass == Date.class
                        && ((Date) newValue).getTime() == 0l) {
                    setter.invoke(objectFromDatabase, new Object[] { null });
                } 
                else {
                    setter.invoke(objectFromDatabase, newValue);
                }
            }

        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

私のDAOクラスでは、simcardToUpdateはhttpリクエストから取得されます。

simcardUpdated = (Simcard) session.get(Simcard.class, simcardToUpdate.getId());

MyUtil.partialUpdateFields(simcardToUpdate, simcardUpdated);

updatedEntities = Integer.parseInt(session.save(simcardUpdated).toString());
于 2019-04-30T10:17:32.053 に答える
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がオブジェクトをマップすると、すべてのデータがいっぱいになるため、これは絶対に間違った方法です。そして、あなたはいくつかの特定のプロパティを更新したいだけです。

あなたの場合には2つのオプションがあります:

1)すべてのプロパティ(変更されていないプロパティを含む)をサーバーに送信します。これにはもう少し帯域幅がかかる可能性がありますが、それでもあなたは道を歩み続けます

2)Userオブジェクトのデフォルト値としていくつかの特別な値を設定する必要があります(たとえば、id = -1、age = -1 ...)。次に、customObjectMergerで、-1以外の値を設定します。

上記の2つの解決策が満たされていない場合は、jsonリクエストを自分で解析することを検討してください。また、Springオブジェクトのマッピングメカニズムを気にしないでください。時々それはただたくさん混乱します。

于 2013-02-28T04:29:31.920 に答える
0

部分的な更新は@SessionAttributes、で自分で行ったことを実行するために作成された機能を使用して解決できますcustomObjectMerger

ここで私の答え、特に編集を見て、始めましょう:

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

于 2015-09-30T07:12:54.740 に答える
0

私はこれをJavaマップといくつかのリフレクションマジックで行いました:

public static Entidade setFieldsByMap(Map<String, Object> dados, Entidade entidade) {
        dados.entrySet().stream().
                filter(e -> e.getValue() != null).
                forEach(e -> {
                    try {
                        Method setter = entidade.getClass().
                                getMethod("set"+ Strings.capitalize(e.getKey()),
                                        Class.forName(e.getValue().getClass().getTypeName()));
                        setter.invoke(entidade, e.getValue());
                    } catch (Exception ex) { // a lot of exceptions
                        throw new WebServiceRuntimeException("ws.reflection.error", ex);
                    }
                });
        return entidade;
    }

そしてエントリポイント:

    @Transactional
    @PatchMapping("/{id}")
    public ResponseEntity<EntityOutput> partialUpdate(@PathVariable String entity,
            @PathVariable Long id, @RequestBody Map<String, Object> data) {
        // ...
        return new ResponseEntity<>(obj, HttpStatus.OK);
    }
于 2019-05-30T20:34:04.683 に答える