【问题标题】:Make SpringData update only changed field of POJO使 SpringData 仅更新 POJO 的更改字段
【发布时间】:2015-12-14 16:36:31
【问题描述】:

我在 MongoDB 上使用 Spring Data。我能够保存 POJO,更新它们。它工作正常。但现在我想在 db 中刷新只更改了 POJO 的字段。

例如我有用户实体。我创建用户,然后不时更新 lastActiveDate。

@Document
class User {
    @Id
    BigInteger ID;

    String email;

    String name;

    Date lastActiveDate;
}

User user = new User();
user.setName("User");
user.setEmail("example@example.com");
repository.save(user);

User toUpdUser = repository.findOne(userId);
toUpdUser.setLastActiveDate(new Date);
repository.save(toUpdUser );

在第二次保存中,我只想更新 lastActiveDate 字段而不是整个用户,因为文档更新在大型实体上可能很慢。我也想知道变更集(至少一组更新的字段)。

目前我还没有找到 API 来做到这一点。唯一的可能是在设置器中手动处理它(存储更改的字段集并手动存储),但它看起来很难看且不支持。另一种选择是在 bean 上使用 AOP 来实现相同的结果,但 IFAIK Spring Data 不会将 POJO 视为 Spring bean 并使用构造函数,因此在使用 AOP 加载 POJO 后将只是 POJO。

更新: 我正在寻找没有显式 mongo api(或 mongo-like api)调用的方法。我有数十个字段的实体,客户几乎可以更改其中的任何一个。我只想存储更改的字段并能够获取更改集以执行一些检查。实体存储和字段更新之间的差异太大 - 5ms vs 0.2ms

当然,我可以创建自己的 POJO 映射器实现并支持 CRUD 跟踪器,但它已经存在,为什么不尝试一下。而且我可以使用另一个框架,而不仅仅是 SpringData。

【问题讨论】:

    标签: java spring mongodb spring-data spring-data-mongodb


    【解决方案1】:

    Spring-data 不支持逐个字段的比较,以便确定哪些字段已更新并且仅将这些字段发送到数据库。虽然它确实只支持持久的非空字段,但我发现自己在实现PATCHPUT Web 服务时利用了这一点。使用非空字段创建一个 $set 对象,您将保持文档的其他部分不变。

    您需要使用MongoConverters 的一些未记录的功能,这是PATCH-webservice 的示例spring-mvc,它还在文档的“events”数组中维护事件历史记录:

    @RestController
    public class MyWebservice {
    
        @Autowired
        private MongoConverter mongoConverter;
    
        @RequestMapping(value = "/order/{id}", method = PATCH, produces = APPLICATION_JSON_UTF8_VALUE, consumes = APPLICATION_JSON_UTF8_VALUE)
        public Order updateOrder(@PathVariable("id") String id, @RequestBody Order order) throws JsonProcessingException {
            order.setId(id);
            DBObject update = getDbObject(order);
            mongoTemplate.updateFirst(query(where("id").is(id)), Update.fromDBObject(new BasicDBObject("$set", update)).push("events", order), Order.class);
            return mongoTemplate.findOne(query(where("id").is(id)), Order.class);
        }
    
        private DBObject getDbObject(Object o) {
            BasicDBObject basicDBObject = new BasicDBObject();
            mongoConverter.write(o, basicDBObject);
            return basicDBObject;
        }
    }
    

    MongoConverter 只会将非空值写入BasicDBObject

    【讨论】:

    • 这很有趣。不是我所期望的,但很有趣。我在业务层工作,通常从数据库(完整实体)加载实体,如果需要修改它们并存储它们。我可以尝试在加载和存储之前编写转换对象并比较它们,但它看起来非常难看。
    【解决方案2】:

    您可以使用 Jackson API 来确定更改的内容并保留 POJO。下面是代码

    public class TestJacksonUpdate {
    
    class Person implements Serializable {
        private static final long serialVersionUID = -7207591780123645266L;
        public String code = "1000";
        public String firstNm = "John";
        public String lastNm;
        public Integer age;
        public String comments = "Old Comments";
    
        @Override
        public String toString() {
            return "Person [code=" + code + ", firstNm=" + firstNm + ", lastNm=" + lastNm + ", age=" + age
                    + ", comments=" + comments + "]";
        }
    }
    
    public static void main(String[] args) throws JsonProcessingException, IOException {
        TestJacksonUpdate o = new TestJacksonUpdate();
    
        String input = "{\"code\":\"10000\",\"lastNm\":\"Smith\",\"comments\":\"Jackson Update WOW\"}";
        Person persist = o.new Person();
    
        System.out.println("persist: " + persist);
    
        ObjectMapper mapper = new ObjectMapper();
        Person finalPerson = mapper.readerForUpdating(persist).readValue(input);
    
        System.out.println("Final: " + finalPerson);
        // repository.save(finalPerson);    } }
    

    【讨论】:

      【解决方案3】:

      您正在寻找MongoTemplate.updateFirst()

      在此处查看相关答案:Spring Data Mongodb: Updating documents

      【讨论】:

      • 谢谢,但我正在寻找没有显式 mongo api 调用的方法。我有数十个字段的实体,客户几乎可以更改其中的任何一个。我只想存储更改的字段并能够获取更改集以执行一些检查。实体存储和字段更新之间的差异太大 - 5ms vs 0.2ms。
      • @ainlolcat 我认为您无法避免对 mongo api 的显式调用 - 祝您好运,如果您发现了什么,请告诉我们。
      【解决方案4】:

      要更新对象上的单个字段,请使用reference documentation 中所述的Update API。然后可以使用MongoTemplate 的用法将其合并到存储库中,使用该机制实现此处所述的各个查询方法。

      【讨论】:

      • 我知道如何手动更新字段。我什至为性能测试编写了简单的 CRUD 跟踪器,它可以更新文档内任何深度的任何字段。但是我必须在每个集合操作中调用它来记录更改。这很烦人。我只是想让框架这样做。当然,如果框架不能那样工作,我将实现查询生成、正常的 CRUD 跟踪器、编写 AOP 以使外部客户满意。我正在寻找类似stackoverflow.com/questions/19656882/…
      • 您可能想重新考虑否决票。仅仅因为你不喜欢这个答案,并不意味着它是不正确的。我基本上是在说明@gogstad 的答案更详细地描述了什么,但建议将其封装到存储库方法中。
      猜你喜欢
      • 2016-10-26
      • 2022-01-17
      • 2019-06-24
      • 2019-11-10
      • 2020-07-11
      • 2019-06-03
      • 2020-09-25
      • 2021-11-30
      • 2013-08-22
      相关资源
      最近更新 更多