Spring Data Mongo:使用MappingMongoConverter更新文档中的null值

5
使用Spring Data Mongo,我需要更新Mongo中的文档。
我的实体定义如下:
@Document(collection = "Orders")
public class Order{

    @Id
    private Long id;
    private String clientContainerReference;
    private String status;
    private Bigdecimal amount;
    private BigDecimal remainingQuantity;
    ...
}

首先,将此文档插入Mongo时,其remainingQuantity为100。接下来,订单被更新为null的remainingQuantity。在更新(upsert)之后,remainingQuantity始终设置为100。

这是由于该类造成的:

org.springframework.data.mongodb.core.convert.MappingMongoConverter

在writeInternal方法中,对文档的每个属性都进行了空值检查。如果属性为空,则在生成的DBObject中排除该属性。
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
        public void doWithPersistentProperty(MongoPersistentProperty prop) {

            if (prop.equals(idProperty)) {
                return;
            }

            Object propertyObj = wrapper.getProperty(prop);

            if (null != propertyObj) {

                if (!conversions.isSimpleType(propertyObj.getClass())) {
                    writePropertyInternal(propertyObj, dbo, prop);
                } else {
                    writeSimpleInternal(propertyObj, dbo, prop);
                }
            }
        }
    });

我明白这样做是更高效的,因为生成的DBObject较小,同时更新请求对于Mongo来说更容易处理。但如何更新真正的null值呢?

更具体地说,在我的情况下,所有文档的所有字段都可能为null。所以我不想编写自定义转换器,并且一个接一个地将每个Java字段映射到DBObject字段。

针对我的用例,我创建了一个名为“NullAwareMappingMongoConverter”的类,它重写了MappingMongoConverter,以便转换器在值为null时进行写入。

entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
        public void doWithPersistentProperty(MongoPersistentProperty prop) {

            if (prop.equals(idProperty)) {
                return;
            }


            Object propertyObj = wrapper.getProperty(prop);

            if (null != propertyObj) {

                if (!conversions.isSimpleType(propertyObj.getClass())) {
                    writePropertyInternal(propertyObj, dbo, prop);
                } else {
                    writeSimpleInternal(propertyObj, dbo, prop);
                }
            }
            else{
                writeSimpleInternal(propertyObj, dbo, prop);
            }
        }
    });

这是一个非常丑陋的解决方案,因为Spring Data Mongo的MappingMongoConverter具有包可见性。Spring是否提供一种告诉我们:不要忽略带注释或其他东西的这些属性上的null值的方法?
谢谢。
下面是用于更新实体的代码。
 public T setNotificationDateAndSave(T entity) {
    Assert.notNull(entity, "Entity must not be null!");
    BasicDBObject dbObject = new BasicDBObject();
    mongoTemplate.getConverter().write(entity, dbObject);

    DateTime expirationDate = getDeprecatedStatus().contains(getStatus(entity)) ?
            new DateTime().plusSeconds(EXPIRE_AFTER_SECONDS) : null;

    dbObject.put(EXPIRATION_DATE_COLUMN, mongoTemplate.getConverter()
            .convertToMongoType(expirationDate, ClassTypeInformation.from(DateTime.class)));


    NotificationDateUpdate update = new NotificationDateUpdate(dbObject, NOTIFICATION_DATE_COLUMN);
    Query q = Query.query(Criteria.where("_id").is(entity.getId()).andOperator(Criteria.where(REGISTER_DATE_COLUMN).lte(entity.getRegisterDate())));
    try{
        mongoTemplate.upsert(q, update, parameterClass);
    }catch (DuplicateKeyException e) {
        logger.info(format("could not save notification : a more recent notification exists for collection %s and entity id : %s", parameterClass.getName(), entity.getId()));
    }
    return entity;
}

你实际执行的更新文档的代码是什么? - Oliver Drotbohm
我把更新代码放在了我的问题末尾。 - zouroto
你为什么要手动操作转换器?NotificationDateUpdate是什么? - Oliver Drotbohm
实际上,我们正在面临相同的问题 - 我们文档中有一些可选字段可能会变为空值(即应该从数据库文档中删除)。目前还没有办法解决这个问题,唯一的解决方法是使用一些不好看的解决方案,比如重写 MappingMongoConverter 类 :( - Mikhail Berastau
顺便问一下,zouroto,你有没有考虑过完全摆脱mongoTemplate,直接使用mongo客户端(使用Jackson的ObjectMapper将pojos转换)的选项? - Mikhail Berastau
1个回答

2

有同样的问题,但我不想处理转换器,感觉对我的需求太复杂了。我只是自动设置了对象的所有空字段。

稍微调整一下你的代码,就会像这样:

DBObject dbObject = new BasicDBObject();
mongoTemplate.getConverter().write(entity, dbObject);
Update update = Update.fromDBObject(dbObject);
for (Field f : entity.class.getDeclaredFields()) {
  try {
     f.setAccessible(true);
     if (f.get(entity) == null)
        update.unset(f.getName());
  } catch (IllegalAccessException e) {
     e.printStackTrace();
  }
}
ops.upsert(query,update);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接