使 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 不支持逐个字段的比较,以便确定哪些字段已更新并且仅将这些字段发送到数据库。虽然它确实只支持持久的非空字段,但我发现自己在实现PATCH
或PUT
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 的非空字段进行更新