Java MongoDB对象版本控制

我需要对存储在面向文档的数据库(MongoDB)中的(简单)Java对象图进行版本控制。 对于关系数据库和Hibernate,我发现了Envers ,并对可能性感到非常惊讶。 是否有类似的东西可以用于Spring Data Documents?

我发现这篇文章概述了我对存储对象版本的想法(以及更多…),我当前的实现类似,因为它将对象的副本存储在带有时间戳的单独历史记录集合中,但我想改善这一点以节省存储空间。 因此,我认为我需要在对象树上实现“diff”操作,并且需要用于重构旧对象的“合并”操作。 有没有图书馆帮助这个?

编辑 :任何MongoDB和版本的经验高度赞赏! 我看到很可能没有Spring Data解决方案。

我们正在使用基础实体(我们设置Id,创建+最后更改日期,……)。 在此基础上,我们使用了一个通用的持久化方法,它看起来像这样:

 @Override public  ObjectId persist(E entity) { delta(entity); mongoDataStore.save(entity); return entity.getId(); } 

delta方法看起来像这样(我会尽量使它尽可能通用):

 protected  void delta(E newEntity) { // If the entity is null or has no ID, it hasn't been persisted before, // so there's no delta to calculate if ((newEntity == null) || (newEntity.getId() == null)) { return; } // Get the original entity @SuppressWarnings("unchecked") E oldEntity = (E) mongoDataStore.get(newEntity.getClass(), newEntity.getId()); // Ensure that the old entity isn't null if (oldEntity == null) { LOG.error("Tried to compare and persist null objects - this is not allowed"); return; } // Get the current user and ensure it is not null String email = ...; // Calculate the difference // We need to fetch the fields from the parent entity as well as they // are not automatically fetched Field[] fields = ArrayUtils.addAll(newEntity.getClass().getDeclaredFields(), BaseEntity.class.getDeclaredFields()); Object oldField = null; Object newField = null; StringBuilder delta = new StringBuilder(); for (Field field : fields) { field.setAccessible(true); // We need to access private fields try { oldField = field.get(oldEntity); newField = field.get(newEntity); } catch (IllegalArgumentException e) { LOG.error("Bad argument given"); e.printStackTrace(); } catch (IllegalAccessException e) { LOG.error("Could not access the argument"); e.printStackTrace(); } if ((oldField != newField) && (((oldField != null) && !oldField.equals(newField)) || ((newField != null) && !newField .equals(oldField)))) { delta.append(field.getName()).append(": [").append(oldField).append("] -> [") .append(newField).append("] "); } } // Persist the difference if (delta.length() == 0) { LOG.warn("The delta is empty - this should not happen"); } else { DeltaEntity deltaEntity = new DeltaEntity(oldEntity.getClass().toString(), oldEntity.getId(), oldEntity.getUuid(), email, delta.toString()); mongoDataStore.save(deltaEntity); } return; } 

我们的delta实体看起来像那样(没有getters + setter,toString,hashCode和equals):

 @Entity(value = "delta", noClassnameStored = true) public final class DeltaEntity extends BaseEntity { private static final long serialVersionUID = -2770175650780701908L; private String entityClass; // Do not call this className as Morphia will // try to work some magic on this automatically private ObjectId entityId; private String entityUuid; private String userEmail; private String delta; public DeltaEntity() { super(); } public DeltaEntity(final String entityClass, final ObjectId entityId, final String entityUuid, final String userEmail, final String delta) { this(); this.entityClass = entityClass; this.entityId = entityId; this.entityUuid = entityUuid; this.userEmail = userEmail; this.delta = delta; } 

希望这可以帮助您入门:-)

这就是我最终为MongoDB实体实现版本控制的方式。 感谢StackOverflow社区的帮助!

  • 为单独的历史记录集合中的每个实体保留更改日志。
  • 为避免保存大量数据,历史记录集不会存储完整的实例,只会存储第一个版本和版本之间的差异。 (您甚至可以省略第一个版本,并从实体主集合中的当前版本“向后”重建版本。)
  • Java Object Diff用于生成对象差异。
  • 为了能够正确使用集合,需要实现实体的equals方法,以便它测试数据库主键而不是子属性。 (否则,JavaObjectDiff将无法识别集合元素中的属性更改。)

以下是我用于版本控制的实体(删除了getter / setter等):

 // This entity is stored once (1:1) per entity that is to be versioned // in an own collection public class MongoDiffHistoryEntry { /* history id */ private String id; /* reference to original entity */ private String objectId; /* copy of original entity (first version) */ private Object originalObject; /* differences collection */ private List differences; /* delete flag */ private boolean deleted; } // changeset for a single version public class MongoDiffHistoryChange { private Date historyDate; private List items; } // a single property change public class MongoDiffHistoryChangeItem { /* path to changed property (PropertyPath) */ private String path; /* change state (NEW, CHANGED, REMOVED etc.) */ private Node.State state; /* original value (empty for NEW) */ private Object base; /* new value (empty for REMOVED) */ private Object modified; } 

这是saveChangeHistory操作:

 private void saveChangeHistory(Object working, Object base) { assert working != null && base != null; assert working.getClass().equals(base.getClass()); String baseId = ObjectUtil.getPrimaryKeyValue(base).toString(); String workingId = ObjectUtil.getPrimaryKeyValue(working).toString(); assert baseId != null && workingId != null && baseId.equals(workingId); MongoDiffHistoryEntry entry = getObjectHistory(base.getClass(), baseId); if (entry == null) { //throw new RuntimeException("history not found: " + base.getClass().getName() + "#" + baseId); logger.warn("history lost - create new base history record: {}#{}", base.getClass().getName(), baseId); saveNewHistory(base); saveHistory(working, base); return; } final MongoDiffHistoryChange change = new MongoDiffHistoryChange(); change.setHistoryDate(new Date()); change.setItems(new ArrayList()); ObjectDiffer differ = ObjectDifferFactory.getInstance(); Node root = differ.compare(working, base); root.visit(new MongoDiffHistoryChangeVisitor(change, working, base)); if (entry.getDifferences() == null) entry.setDifferences(new ArrayList()); entry.getDifferences().add(change); mongoTemplate.save(entry, getHistoryCollectionName(working.getClass())); } 

这就是它在MongoDB中的样子:

 { "_id" : ObjectId("5040a9e73c75ad7e3590e538"), "_class" : "MongoDiffHistoryEntry", "objectId" : "5034c7a83c75c52dddcbd554", "originalObject" : { BLABLABLA, including sections collection etc. }, "differences" : [{ "historyDate" : ISODate("2012-08-31T12:11:19.667Z"), "items" : [{ "path" : "/sections[LetterSection@116a3de]", "state" : "ADDED", "modified" : { "_class" : "LetterSection", "_id" : ObjectId("5034c7a83c75c52dddcbd556"), "letterId" : "5034c7a83c75c52dddcbd554", "sectionIndex" : 2, "stringContent" : "BLABLA", "contentMimetype" : "text/plain", "sectionConfiguration" : "BLUBB" } }, { "path" : "/sections[LetterSection@19546ee]", "state" : "REMOVED", "base" : { "_class" : "LetterSection", "_id" : ObjectId("5034c7a83c75c52dddcbd556"), "letterId" : "5034c7a83c75c52dddcbd554", "sectionIndex" : 2, "stringContent" : "BLABLABLA", "contentMimetype" : "text/plain", "sectionConfiguration" : "BLUBB" } }] }, { "historyDate" : ISODate("2012-08-31T13:15:32.574Z"), "items" : [{ "path" : "/sections[LetterSection@44a38a]/stringContent", "state" : "CHANGED", "base" : "blub5", "modified" : "blub6" }] }, }], "deleted" : false } 

编辑:这是访客代码:

 public class MongoDiffHistoryChangeVisitor implements Visitor { private MongoDiffHistoryChange change; private Object working; private Object base; public MongoDiffHistoryChangeVisitor(MongoDiffHistoryChange change, Object working, Object base) { this.change = change; this.working = working; this.base = base; } public void accept(Node node, Visit visit) { if (node.isRootNode() && !node.hasChanges() || node.hasChanges() && node.getChildren().isEmpty()) { MongoDiffHistoryChangeItem diffItem = new MongoDiffHistoryChangeItem(); diffItem.setPath(node.getPropertyPath().toString()); diffItem.setState(node.getState()); if (node.getState() != State.UNTOUCHED) { diffItem.setBase(node.canonicalGet(base)); diffItem.setModified(node.canonicalGet(working)); } if (change.getItems() == null) change.setItems(new ArrayList()); change.getItems().add(diffItem); } } } 

看起来Javers是这项工作的合适工具,请参阅http://javers.org/documentation/features/#javers-repository

Javers在概念上是一个用于域对象版本控制的VCS,由JSON和MongoDB提供支持