Questo è il modo in cui ho finito per implementare il controllo delle versioni per le entità MongoDB. Grazie alla community di StackOverflow per l'aiuto!
- Un registro delle modifiche viene mantenuto per ciascuna entità in una raccolta cronologica separata.
- Per evitare il salvataggio di molti dati, la raccolta della cronologia non memorizza istanze complete, ma solo la prima versione e le differenze tra le versioni. (Si potrebbe anche omettere la prima versione e ricostruire le versioni "all'indietro" dalla versione corrente nella raccolta principale dell'entità.)
- Java Object Diff viene utilizzato per generare le differenze di oggetto.
- Per poter lavorare correttamente con le raccolte, è necessario implementare il metodo
equals
delle entità in modo che esegua il test per la chiave primaria del database e non per le proprietà secondarie. (Altrimenti, JavaObjectDiff non riconoscerà le modifiche alle proprietà negli elementi della raccolta.)
Ecco le entità che utilizzo per il controllo delle versioni (getter/setter, ecc.rimosso):
// 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<MongoDiffHistoryChange> differences;
/* delete flag */
private boolean deleted;
}
// changeset for a single version
public class MongoDiffHistoryChange {
private Date historyDate;
private List<MongoDiffHistoryChangeItem> 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;
}
Ecco l'operazione 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<MongoDiffHistoryChangeItem>());
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<MongoDiffHistoryChange>());
entry.getDifferences().add(change);
mongoTemplate.save(entry, getHistoryCollectionName(working.getClass()));
}
Questo è come sembra in 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[[email protected]]",
"state" : "ADDED",
"modified" : {
"_class" : "LetterSection",
"_id" : ObjectId("5034c7a83c75c52dddcbd556"),
"letterId" : "5034c7a83c75c52dddcbd554",
"sectionIndex" : 2,
"stringContent" : "BLABLA",
"contentMimetype" : "text/plain",
"sectionConfiguration" : "BLUBB"
}
}, {
"path" : "/sections[[email protected]]",
"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[[email protected]]/stringContent",
"state" : "CHANGED",
"base" : "blub5",
"modified" : "blub6"
}]
},
}],
"deleted" : false
}
EDIT: Ecco il codice visitatore:
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<MongoDiffHistoryChangeItem>());
change.getItems().add(diffItem);
}
}
}
non pieno controllo delle versioni, ma abbiamo implementato un piccolo sistema di controllo - che ha cambiato la registrazione che vecchi valori a quelli nuovi. Stiamo usando il metodo '' prePersist() '' di Morphia (che funzionerà solo per i salvataggi di entità completa, non per gli aggiornamenti specifici). Può fornire alcuni esempi di codice, ma non è niente di sofisticato ... – xeraa
Grazie per il tuo commento! Sarei molto interessato ad ulteriori dettagli che dimostrino la tua soluzione. Solo il monitoraggio dei salvataggi completi è ok: questo è anche il nostro caso d'uso principale. Un punto molto interessante è il modo in cui si confronta la vecchia con la nuova entità, identificando le proprietà modificate. Ho dato un'occhiata ai quadri di comparazione dei grafici qui, ma non ho trovato una soluzione facile e veloce. –