2015-06-07 33 views
1

我正在與對象化。datastore - 使用查詢和交易

在我的應用程序中,我有員工實體和任務實體。每個任務都是爲給定的員工創建的。數據存儲正在爲任務實體生成標識。

但是我有一個限制,如果該任務與另一個已經存在的任務重疊,我無法爲員工保存任務。

我不知道如何實現。我甚至不知道如何開始。我應該使用交易嗎?

@Entity 
class Task { 

    @Id 
    private Long id; 

    @Index 
    private long startDate; // since epoch 

    @Index 
    private long endDate; 

    @Index 
    private Ref<User> createdFor; 

    public Task(String id, long startDate, long endDate, User assignedTo) { 

     this.id = null; 
     this.startDate = startDate; 
     this.endDate = endDate; 
     this.createdFor = Ref.create(assignedTo); 

    } 

} 

@Entity 
class Employee { 

    @Id 
    private String id; 

    private String name; 

    public Employee(String id, String name) { 

     this.id = id; 
     this.name = name; 

    } 

} 

回答

2

你不能用你所設置的實體做到這一點,因爲你詢問的任務,並插入新任務的時間之間,你不能保證別人不會已經插入一個新的任務與你插入的任務衝突。即使您使用交易,任何併發添加的衝突任務也不會成爲交易實體組的一部分,因此存在違反約束條件的可能性。

您可以修改您的體系結構,而不是每個任務都具有爲其創建的員工的引用,每個員工都包含爲該員工創建的任務集合?通過這種方式,當您查詢員工的衝突任務時,員工將在您的事務的實體組中被加時間戳,並且如果其他人在完成新任務之前將新任務加入其中,則會拋出併發修改異常,您會然後重試。但是,是的,將您的查詢和您的投入放在同一個交易中。

這裏閱讀有關的事務,實體組和樂觀併發:https://code.google.com/p/objectify-appengine/wiki/Concepts#Transactions

至於確保您的任務不重疊,你只需要檢查是否兩端日起新任務的開始是日起同一員工的任何以前任務的範圍。您還需要檢查您是否設置了一個新的任務,該任務在前一個任務的日期範圍之前開始並結束。我建議爲每個測試使用composite.and過濾器,然後將這三個複合過濾器合併到composite.or過濾器中,這將是您最終應用的過濾器。可能會有更簡單的方法,但這是我的方法:

請注意,這些過濾器不適用於我建議的新體系結構。也許我會刪除它們。 ////////限於分配給同一員工的任務 Filter sameEmployeeTask = new FilterPredicate(「createdFor」,FilterOperator.EQUAL,thisCreatedFor);

/////////Check if new startDate is in range of the prior task 
Filter newTaskStartBeforePriorTaskEnd = 
    new FilterPredicate("endDate", FilterOperator.GREATER_THAN, thisStartDate); 

Filter newTaskStartAfterPriorTaskStart = 
    new FilterPredicate("startDate", FilterOperator.LESS_THAN, thisStartDate); 

Filter newTaskStartInPriorTaskRange = 
    CompositeFilterOperator.and(sameEmployeeTask, newTaskStartBeforePriorTaskEnd, newTaskStartAfterPriorTaskStart); 

/////////Check if new endDate is in range of the prior task 
Filter newTaskEndBeforePriorTaskEnd = 
    new FilterPredicate("endDate", FilterOperator.GREATER_THAN, thisEndDate); 

Filter newTaskEndAfterPriorTaskStart = 
    new FilterPredicate("startDate", FilterOperator.LESS_THAN, thisEndDate); 

Filter newTaskEndInPriorTaskRange = 
    CompositeFilterOperator.and(sameEmployeeTask, newTaskEndBeforePriorTaskEnd, newTaskEndAfterPriorTaskStart); 

/////////Check if this Task overlaps the prior one on both sides 
Filter newTaskStartBeforePriorTaskStart = 
    new FilterPredicate("startDate", FilterOperator.GREATER_THAN_OR_EQUAL, thisStartDate); 

Filter newTaskEndAfterPriorTaskEnd = 
    new FilterPredicate("endDate", FilterOperator.LESS_THAN_OR_EQUAL, thisEndDate); 

Filter PriorTaskRangeWithinNewTaskStartEnd = CompositeFilterOperator.and(sameEmployeeTask ,newTaskStartBeforePriorTaskStart, newTaskEndAfterPriorTaskEnd); 

/////////Combine them to test if any of the three returns one or more tasks 
Filter newTaskOverlapPriorTask = CompositeFilterOperator.or(newTaskStartInPriorTaskRange,newTaskEndInPriorTaskRange,PriorTaskRangeWithinNewTaskStartEnd); 

/////////Proceed 
Query q = new Query("Task").setFilter(newTaskOverlapPriorTask); 

PreparedQuery pq = datastore.prepare(q); 

如果您不返回任何結果,那麼您沒有任何重疊,因此請繼續並保存新任務。

+0

確定,但不應該是一個事務中做什麼? – justatester

+0

對,你不能像你設置你的實體一樣。查看我答案頂部的新編輯。 – Jibbyj

+0

@justatester Jibbyj是對的......不幸的是,您目前的設置方式,您無法真正做到。我會考慮改變你的種類的設計,以確保未來可以規避這種情況。 – Patrice

0

好的,我希望我能多一點幫助。我試圖編輯您的問題並將您的實體更改爲正確的架構。我已經爲您的員工添加了一個嵌入式任務集合和一個attemptAdd方法。我已經爲您的任務和員工添加了一個detectOverlap方法。有了這些,你可以使用類似下面的交易。您需要處理由於任務衝突而導致任務未被添加的情況,以及由於ConcurrentModificationException而導致添加失敗的情況。但是你可以從這些問題中提出另一個問題,同時你應該有一個開始。

摘自:https://code.google.com/p/objectify-appengine/wiki/Transactions

Task myTask = new Task(startDate,endDate,description); 

public boolean assignTaskToEmployee(EmployeeId, myTask) { 
    ofy().transact(new VoidWork() { 
     public void vrun() {    
      Employee assignee = ofy().load().key(EmployeeId).now()); 
      boolean taskAdded = assignee.attemptAdd(myTask); 
      ofy().save().entity(assignee); 
      return taskAdded; 
     } 
    } 
}