2012-08-24 26 views
11

Tôi cần làm phiên bản trên (đơn giản) đồ thị đối tượng Java được lưu trữ trong một cơ sở dữ liệu hướng tài liệu (MongoDB). Đối với cơ sở dữ liệu quan hệ và Hibernate, tôi phát hiện ra Envers và tôi rất ngạc nhiên về khả năng. Có cái gì đó tương tự có thể được sử dụng với tài liệu dữ liệu mùa xuân?Java MongoDB Object Versioning

Tôi đã tìm thấy this post nêu ra những suy nghĩ mà tôi có (và nhiều hơn nữa ...) về lưu trữ phiên bản đối tượng và triển khai hiện tại của tôi hoạt động tương tự ở chỗ nó lưu trữ bản sao của đối tượng trong một bộ sưu tập lịch sử riêng biệt với dấu thời gian. muốn cải thiện điều này để tiết kiệm dung lượng lưu trữ. Vì vậy, tôi nghĩ rằng tôi cần phải thực hiện cả hai hoạt động "khác biệt" trên cây đối tượng và hoạt động "hợp nhất" để xây dựng lại các đối tượng cũ. Có thư viện nào giúp đỡ điều này không?

Chỉnh sửa: Bất kỳ trải nghiệm nào với MongoDB và phiên bản được đánh giá cao! Tôi thấy hầu hết có lẽ sẽ không có giải pháp Dữ liệu mùa xuân.

+0

Không đầy đủ phiên bản, nhưng chúng tôi đã thực hiện một hệ thống kiểm toán nhỏ - khai thác gỗ đã thay đổi mà giá trị cũ sang những cái mới. Chúng tôi đang sử dụng phương pháp '' prePersist() '' của Morphia (sẽ chỉ làm việc để tiết kiệm thực thể đầy đủ, chứ không phải các bản cập nhật cụ thể). Có thể cung cấp một số mẫu mã, nhưng nó không có gì phức tạp ... – xeraa

+0

Cảm ơn bạn đã bình luận! Tôi sẽ rất quan tâm đến một số chi tiết khác thể hiện giải pháp của bạn. Chỉ theo dõi việc lưu thực thể đầy đủ là hoàn toàn ok: Đây cũng là trường hợp sử dụng chính của chúng tôi. Một điểm rất thú vị là cách bạn so sánh tuổi với thực thể mới, xác định các thuộc tính đã thay đổi. Tôi đã xem xét các khung so sánh đồ thị ở đây, nhưng không tìm thấy giải pháp nhanh chóng và dễ dàng. –

Trả lời

7

Chúng tôi đang sử dụng thực thể cơ sở (nơi chúng tôi đặt Id, tạo + ngày thay đổi cuối cùng, ...). Xây dựng dựa trên này, chúng tôi đang sử dụng một phương pháp bền bỉ chung chung, mà trông giống như sau:

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

Phương pháp tam giác trông như thế này (tôi sẽ cố gắng thực hiện điều này chung chung càng tốt):

protected <E extends BaseEntity> 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; 
} 

thực thể châu thổ của chúng tôi trông như thế (không có getters setters +, toString, hashCode, và bằng):

@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; 
    } 

Hy vọng điều này sẽ giúp bạn bắt đầu :-)

+0

Cảm ơn bạn rất nhiều vì mẫu. Tôi cũng tìm thấy một bài viết về sự khác biệt đối tượng java (http://stackoverflow.com/questions/8001400/is-there-a-java-library-that-can-diff-two-objects) đề cập đến thư viện này: https: // github.com/SQiShER/java-object-diff - có lẽ tôi có thể "thêm gia vị" giải pháp của bạn với thuật toán khác biệt này. Tôi muốn để câu hỏi này mở ra thêm một thời gian nữa, có lẽ có những ý tưởng khác. –

+0

Dự án thú vị, mong được giải pháp của bạn. Một upvote vẫn sẽ được đánh giá cao trong khi chờ đợi ;-) – xeraa

12

Đây là cách tôi đã kết thúc triển khai phiên bản cho các thực thể MongoDB. Nhờ cộng đồng StackOverflow giúp đỡ!

  • Nhật ký thay đổi được lưu giữ cho từng thực thể trong bộ sưu tập lịch sử riêng biệt.
  • Để tránh lưu nhiều dữ liệu, bộ sưu tập lịch sử không lưu trữ các phiên bản hoàn chỉnh, nhưng chỉ có phiên bản đầu tiên và sự khác biệt giữa các phiên bản. (Bạn thậm chí có thể bỏ qua phiên bản đầu tiên và xây dựng lại các phiên bản "ngược" từ phiên bản hiện tại trong bộ sưu tập chính của thực thể.)
  • Java Object Diff được sử dụng để tạo các khác biệt đối tượng.
  • Để có thể làm việc với các bộ sưu tập chính xác, cần thực hiện phương thức equals của các thực thể để nó kiểm tra khóa chính cơ sở dữ liệu chứ không phải thuộc tính phụ. (Nếu không, JavaObjectDiff sẽ không nhận ra các thay đổi về tài sản trong các phần tử thu thập.)

Đây là các thực thể tôi sử dụng để phiên bản (getters/setters, v.v.loại bỏ):

// 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; 
} 

Đây là hoạt động 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())); 
} 

Đây là cách nó trông giống như trong 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: Đây là mã của khách:

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); 
    } 
} 

}