使 SpringData 仅更新 POJO 的更改字段

Posted

技术标签:

【中文标题】使 SpringData 仅更新 POJO 的更改字段【英文标题】:Make SpringData update only changed field of 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。

【问题讨论】:

【参考方案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 以使外部客户满意。我正在寻找类似***.com/questions/19656882/… 您可能想重新考虑否决票。仅仅因为你不喜欢这个答案,并不意味着它是不正确的。我基本上是在说明@gogstad 的答案更详细地描述了什么,但建议将其封装到存储库方法中。

以上是关于使 SpringData 仅更新 POJO 的更改字段的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch掰开揉碎第13篇SpringData操作ES基础篇

Spring Mongo 仅通过新 POJO 的非空字段进行更新

如何使quickxml ObjectMapper与codehaus注释一起使用

springdata-jpa 八种查询方法

如何更改文本动画以使它们仅显示在自己的空间中?

需要进行哪些更改才能使此代码检测多个人脸而不是仅一个人脸