Upsert MongoTemplate - простой способ сделать обновление из pojo (какой пользователь отредактировал)?

Вот простой pojo:

public class Description {
    private String code;
    private String name;
    private String norwegian;
    private String english;
}

И, пожалуйста, посмотрите следующий код, чтобы применить upsert к MongoDb через Spring MongoTemplate:

Query query = new Query(Criteria.where("code").is(description.getCode()));
Update update = new Update().set("name", description.getName()).set("norwegian", description.getNorwegian()).set("english", description.getEnglish());
mongoTemplate.upsert(query, update, "descriptions");

Строка для создания объекта Update указывает каждое поле класса Item вручную.

Но если мой объект Item изменится, мой слой Дао сломается.

Итак, есть ли способ избежать этого, чтобы все поля из моего класса Item автоматически применялись к обновлению?

E.g.

Update update = new Update().fromObject(item);

Обратите внимание, что мой pojo не расширяет DBObject.


person vikingsteve    schedule 15.11.2013    source источник


Ответы (12)


Я столкнулся с той же проблемой. В текущей версии Spring Data MongoDB такая вещь недоступна. Вы должны обновить отдельные поля вручную.

Однако это возможно с другим фреймворком: Morphia.

Эта структура имеет оболочку для функций DAO: https://github.com/mongodb/morphia/wiki/DAOSupport

Вы можете использовать DAO API для таких вещей:

SomePojo pojo = daoInstance.findOne("some-field", "some-value");
pojo.setAProperty("changing this property");
daoInstance.save(pojo);
person wvandrunen1982    schedule 05.03.2014

Я нашел довольно хорошее решение для этого вопроса

//make a new description here
Description d = new Description();
d.setCode("no");
d.setName("norwegian");
d.setNorwegian("norwegian");
d.setEnglish("english");

//build query
Query query = new Query(Criteria.where("code").is(description.getCode()));

//build update
DBObject dbDoc = new BasicDBObject();
mongoTemplate.getConverter().write(d, dbDoc); //it is the one spring use for convertions.
Update update = Update.fromDBObject(dbDoc);

//run it!
mongoTemplate.upsert(query, update, "descriptions");

Пожалуйста, обратите внимание, что Update.fromDBObject возвращает объект обновления со всеми полями в dbDoc. Если вы просто хотите обновить ненулевые поля, вам следует запрограммировать новый метод для исключения нулевых полей.

Например, внешний интерфейс публикует документ, как показано ниже:

//make a new description here
Description d = new Description();
d.setCode("no");
d.setEnglish("norwegian");

Нам нужно только обновить поле «язык»:

//return Update object
public static Update fromDBObjectExcludeNullFields(DBObject object) {
    Update update = new Update();       
    for (String key : object.keySet()) {
        Object value = object.get(key);
        if(value!=null){
            update.set(key, value);
        }
    }
    return update;
}

//build udpate
Update update = fromDBObjectExcludeNullFields(dbDoc);
person PaniniGelato    schedule 22.01.2016
comment
@ starkk92, пожалуйста, обратите внимание, что Update.fromDBObject заменит все поля в dbDoc. Если вы просто хотите создать обновление с ненулевыми полями, вам нужно написать его самостоятельно. - person PaniniGelato; 14.04.2016
comment
Хорошо для ответа, но это также устанавливает нулевые объекты. Если вы не хотите, чтобы нулевые объекты обновлялись, вы должны включить ObjectMapper с возможностью игнорировать включения. Это комментарий @gogstad к ответу Вивека Сети. Кстати, спасибо. - person javadev; 01.03.2017

Решение для новой версии spring-data-mongodb 2.X.X.

API эволюционировал, начиная с версии 2.X.X есть:

Update.fromDocument(org.bson.Document object, String... exclude)

вместо (1.Х.Х):

Update.fromDBObject(com.mongodb.DBObject object, String... exclude)

Полное решение:

//make a new description here
Description d = new Description();
d.setCode("no");
d.setName("norwegian");
d.setNorwegian("norwegian");
d.setEnglish("english");
Query query = new Query(Criteria.where("code").is(description.getCode()));

Document doc = new Document(); // org.bson.Document
mongoTemplate.getConverter().write(item, doc);
Update update = Update.fromDocument(doc);

mongoTemplate.upsert(query, update, "descriptions");

Оно работает!

person Dawid Naczke    schedule 09.07.2018
comment
почему это не работает для BulkOperations. Любая идея здесь — это фрагмент моего кода pastebin.com/t0mZDZd8 java. lang.IllegalArgumentException: Недопустимый код имени поля BSON - person madcolonel10; 01.10.2019
comment
@ madcolonel10, ты нашел какое-нибудь решение? У меня такая же проблема. - person chris115379; 12.11.2019
comment
@chris115379 проверьте, помогает ли это pastebin.com/LxEPym3S - person madcolonel10; 13.11.2019

вы можете использовать save : (если не существует = вставить else = upsert)

save (Object objectToSave, String collectionName)

читать: javadoc

person Nimrod007    schedule 07.10.2014

Как и в предыдущих ответах, используйте функции mongoTemplate.getConverter().write() и Update.fromDocument(). Но я обнаружил, что Update.fromDocument() не добавит ключ "$set" и не будет работать напрямую, решение состоит в том, чтобы добавить "$set" самостоятельно, как показано ниже (PS: я использую версию 2.2.1.RELEASE ):

public static Update updateFromObject(Object object, MongoTemplate mongoTemplate) {
    Document doc = new Document();
    mongoTemplate.getConverter().write(object, doc);
    return Update.fromDocument(new Document("$set", doc));
}
person arthurdai    schedule 27.11.2019

Если вы хотите обновить Pojos, вкл. свойства String id; необходимо исключить поле _id в методе fromDBObject Update.fromDBObject(dbDoc,"_id").

В противном случае вы получите исключение:

org.springframework.dao.DuplicateKeyException: { "serverUsed" : "127.0.0.1:27017" , "ok" : 1 , "n" : 0 , "updatedExisting" : false , "err" : "E11000 duplicate key error collection: db.description index: _id_ dup key: { : null }" , "code" : 11000}; nested exception is com.mongodb.MongoException$DuplicateKey: { "serverUsed" : "127.0.0.1:27017" , "ok" : 1 , "n" : 0 , "updatedExisting" : false , "err" : "E11000 duplicate key error collection: db.description index: _id_ dup key: { : null }" , "code" : 11000}

потому что поле _id первого равно нулю

{
    "_id" : null,
...

}

Полный код, основанный на ответе @PaniniGelato, будет

public class Description(){
    public String id;
...
}

Description d = new Description();
d.setCode("no");
d.setName("norwegian");
d.setNorwegian("norwegian");
d.setEnglish("english");

//build query
Query query = new Query(Criteria.where("code").is(description.getCode()));

//build update
DBObject dbDoc = new BasicDBObject();
mongoTemplate.getConverter().write(d, dbDoc); //it is the one spring use for convertions.
Update update = Update.fromDBObject(dbDoc, "_id");

//run it!
mongoTemplate.upsert(query, update, "descriptions");

Тогда upsert работает в случаях вставки и обновления. Поправки и мысли приветствуются ;)

person schmichri    schedule 02.09.2016
comment
предостережение: если вы используете поле с тегом @Version в своей сущности, этот подход будет мешать приращению версии Spring, создавая исключение Недопустимое имя поля BSON $inc. В этом случае необходимо перебрать dbDoc и использовать update.set, как в ответе @PaniniGelatos. - person Mr.Radar; 20.03.2017

Этим я пока и занимаюсь. Не очень элегантный способ сделать это, но он сохраняет драгоценный вызов БД:

import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;

/**
 * Perform an upsert operation to update ALL FIELDS in an object using native mongo driver's methods
 * since mongoTemplate's upsert method doesn't allow it
 * @param upsertQuery
 * @param object
 * @param collectionName
 */
private void performUpsert(Query upsertQuery, Object object, String collectionName){

    ObjectMapper mapper = new ObjectMapper();

    try {
        String jsonStr = mapper.writeValueAsString(object);
        DB db = mongoTemplate.getDb();
        DBCollection collection = db.getCollection(collectionName);
        DBObject query = upsertQuery.getQueryObject();
        DBObject update = new BasicDBObject("$set", JSON.parse(jsonStr));
        collection.update(query, update, true, false);
    } catch (IOException e) {
        LOGGER.error("Unable to persist the metrics in DB. Error while parsing object: {}", e);
    }
}
person Vivek Sethi    schedule 06.08.2015
comment
Вам нужно убедиться, что ObjectMapper не сериализует нули: String jsonStr = objectMapper.copy().setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(object); - person gogstad; 22.04.2016

Здесь необходимо различать два случая:

  1. Обновите элемент, который ранее был извлечен из БД.
  2. Обновите или вставьте (upsert) элемент, созданный вами с помощью кода.

В случае 1) вы можете просто использовать mongoTemplate.save(pojo, "коллекция"), потому что ваш POJO уже будет иметь заполненный ObjectID в поле id.

В случае 2) вы должны объяснить монго, что означает «уже существует» в случае вашей модели домена: по умолчанию метод mongoTemplate.save() обновляет существующий элемент, если он есть с тем же ObjectId. Но с недавно созданным экземпляром POJO у вас нет этого идентификатора. Поэтому метод mongoTemplate.upsert() имеет параметр запроса, который вы можете создать следующим образом:

MyDomainClass pojo = new MyDomainClass(...);

Query query = Query.query(Criteria.where("email").is("[email protected]"));

DBObject dbDoc = new BasicDBObject();
mongoTemplate.getConverter().write(pojo, dbDoc);   //it is the one spring use for convertions.
dbDoc.removeField("_id");    // just to be sure to not create any duplicates
Update update = Update.fromDBObject(dbDoc);

WriteResult writeResult = mongoTemplate.upsert(query, update, UserModel.class);
person Robert    schedule 26.12.2016

Я думаю, что: Описание добавить свойство

@Id
private String id;

затем получите документ по условию запроса, установите идентификатор описания по идентификатору документа. и сохранить

person stoneHah    schedule 14.05.2014

Просто используйте ReflectionDBObject - если вы сделаете Description его расширением, вы должны просто передать поля вашего объекта в Update рефлексивно, автоматически. Примечание выше о пустых полях, включенных в обновление, остается в силе.

person V. F.    schedule 07.06.2016

public void saveOrUpdate(String json) {
    try {
        JSONObject jsonObject = new JSONObject(json);
        DBObject update1 = new BasicDBObject("$set", JSON.parse(json));
        mongoTemplate.getCollection("collectionName").update(new Query(Criteria.where("name").is(jsonObject.getString("name"))).getQueryObject(), update1, true, false);
    } catch (Exception e) {
        throw new GenericServiceException("Error while save/udpate. Error msg: " + e.getMessage(), e);
    }

}

это очень простой способ сохранить строку json в коллекцию с помощью mongodb и spring. Этот метод можно переопределить для использования в качестве JSONObject.

person Awanish Kumar    schedule 22.03.2018

person    schedule
comment
Ответы только на код действительно обескуражены. Чтобы помочь будущим читателям, пожалуйста, объясните, что вы делаете! - person itsmysterybox; 30.10.2018