2015-12-26 18 views
1

我在Java中執行我的項目(後端)。我不想改用Clojure(還沒有)。Java中的Datomic「update」

Datomic然而看起來很有趣它聲明它有一個Java API,但我仍然有一些開放的問題,最重要的是這個。

爲了舉例說明,假設我們有一個具有業務屬性名稱,電子郵件和電話的客戶實體。因此,在Java中,我們有這樣的事:

public class Customer { 
    private Long id; 
    private String name; 
    private String email; 
    private String phone; 
    private Long version; // ? - see 4. below 
    // getters, setter, toString, hashCode, equals, business logic, etc. 
} 

的Datomic架構聲明相應的屬性:客戶/名稱,:客戶/電子郵件:客戶/電話等

有一個 「編輯客戶」表格暴露了用戶改變的3個業務屬性。假設我更改名稱和電子郵件並保存表單。

現在,我應該怎麼做才能將更改保存到Datomic中?我如何構建交易?

Datomic提供的示例過於簡單,CompareAndSwap示例最接近但根本沒有幫助。我做了我的谷歌搜索,但無濟於事。

答案應該:

  1. 包括實的Java(不Clojure的)的代碼多達調用connection.transact。
  2. 可重複使用/不需要複製&粘貼其他實體。
  3. 只更新已更改的屬性(?) - 我知道我應該只處理值實際已更改的屬性(正確?)。
  4. 正確解析多個用戶的併發編輯,即用戶不應覆蓋彼此的工作。這通常通過樂觀鎖定來解決。那麼如何在Java中對Datomic進行樂觀鎖定呢?還是有其他的策略?

(最後,一個側面提示 - 不是問題的一部分,Datomic Java文檔中沒有解釋如何編輯實體這樣的核心用例,也沒有官方示例顯示如何以最好的方式來解決這個問題呢?這種感覺並不是真的支持「Datomic Java API」。在我看來,Java和Clojure在不同的範例中工作,所以簡單地將Clojure API 1:1移植到Java並不構成Java 。API尚未 我不應該能夠註釋客戶有點(如@Id@Version),然後就打電話connection.persist(客戶);並用它做我知道,可怕的ORM龍提高它的頭再次難看。但是,嘿,也許現在我會學習如何在一個更優雅的方式做到這一點)

回答

0

爲了回答大家的一些問題:

  1. 你不辦理只有字段已經改變了,Datomic會在事務運行時刪除沒有改變的屬性。
  2. Datomic不提供類映射圖層,可能永遠不會。我不知道在社區中已經開發出了一個,也沒有讓我感到意外,因爲這個社區傾向於爲通用性而傾向於面向數據(而不是基於類)的體系結構。因此,您不會找到實用程序將其從Datomic數據一般轉換爲POJO,例如由ORM提供。
  3. 這並不意味着Java在這裏是二等公民 - 但Datomic會給你施加壓力(如果你問創作者,你自己的利益)使用列表和地圖等數據結構代替POJO來傳遞信息。這在Clojure中的確比在Java中更習慣。
  4. 我個人強烈建議您使用實體(例如datomic.Entity類的實例)而不是POJO作爲您的業務邏輯 - 至少試一試,看看在編寫映射代碼之前是否存在這個問題。你失去了一些靜態的保證 - 可能是很多的樣板。儘管如此,下面的實現確實使用POJO。

我在下面給出了我的實現。基本上,您可以將您的Customer對象轉換爲事務映射,並使用事務函數:db.fn/cas獲取您想要更新的併發保證。

如果你是一位經驗豐富的Java開發人員,這可能對你看起來不太優雅 - 我知道這種感覺。同樣,這並不意味着您無法從Java獲得Datomic的好處。您是否可以遵守面向數據的API取決於您,這個問題並非針對Datomic--儘管Datomic傾向於將您推向數據導向,例如通過實體。

import datomic.*; 

import java.util.List; 
import java.util.Map; 

public class DatomicUpdateExample { 

    // converts an Entity to a Customer POJO 
    static Customer customerFromEntity(Entity e){ 
     if(e == null || (e.get(":customer/id") == null)){ 
      throw new IllegalArgumentException("What you gave me is not a Customer entity."); 
     } 
     Customer cust = new Customer(); 
     cust.setId((Long) e.get(":customer/id")); 
     cust.setName((String) e.get(":customer/name")); 
     cust.setEmail((String) e.get(":customer/email")); 
     cust.setPhone((String) e.get(":customer/phone")); 
     cust.setVersion((Long) e.get(":model/version")); 
     return cust; 
    } 

    // finds a Customer by 
    static Customer findCustomer(Database db, Object lookupRef){ 
     return customerFromEntity(db.entity(lookupRef)); 
    } 

    static List txUpdateCustomer(Database db, Customer newCustData){ 
     long custId = newCustData.getId(); 
     Object custLookupRef = Util.list(":customer/id", custId); 
     Customer oldCust = findCustomer(db, custLookupRef); // find old customer by id, using a lookup ref on the :customer.id field. 
     long lastKnownVersion = oldCust.getVersion(); 
     long newVersion = lastKnownVersion + 1; 
     return Util.list(// transaction data is a list 
       Util.map(// using a map is convenient for updates 
         ":db/id", Peer.tempid(":db.part/user"), 
         ":customer/id", newCustData.getId(), // because :customer/id is a db.unique/identity attribute, this will map will result in an update 
         ":customer/email", newCustData.getEmail(), 
         ":customer/name", newCustData.getName(), 
         ":customer/phone", newCustData.getPhone() 
       ), 
       // 'Compare And Swap': this clause will prevent the update from happening if other updates have occurred by the time the transaction is executed. 
       Util.list(":db.fn/cas", custLookupRef, ":model/version", lastKnownVersion, newVersion) 
     ); 
    } 

    static void updateCustomer(Connection conn, Customer newCustData){ 
     try { 
      Map txResult = conn.transact(txUpdateCustomer(conn.db(), newCustData)).get(); 
     } catch (InterruptedException e) { 
      // TODO deal with it 
      e.printStackTrace(); 
     } catch (Exception e) { 
      // if the CAS failed, this is where you'll know 
      e.printStackTrace(); 
     } 
    } 
} 

class Customer { 
    private Long id; 
    private String name; 
    private String email; 
    private String phone; 
    private Long version; 

    public Long getId() { 
     return id; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public String getEmail() { 
     return email; 
    } 

    public void setEmail(String email) { 
     this.email = email; 
    } 

    public String getPhone() { 
     return phone; 
    } 

    public void setPhone(String phone) { 
     this.phone = phone; 
    } 

    public Long getVersion() { 
     return version; 
    } 

    public void setVersion(Long version) { 
     this.version = version; 
    } 
} 
+0

嗨瓦倫丁,謝謝你的回答。我有兩個問題:1)我不明白爲什麼你在更新中創建一個新的tempid。沒有它我就更新了。 2)在版本不匹配的情況下,我無法進行db.fn/cas投擲。我在這裏更新了我的示例項目:https://github.com/ivos/datomic-java-sample CustomerServiceTest.update_Conflict()測試失敗。 – ivom

+0

1)由於upsert的行爲,tempids不會受到傷害,這就是爲什麼我總是把它們放在一邊。 2)你的測試不起作用,因爲在你的'update()'方法中,以前的版本是從數據庫調用中計算出來的,而不是從你作爲參數傳遞的客戶端(以及你僞造的版本)中計算出來的。 –

+0

2)對,我現在得出了同樣的結論,只是發現你已經指出了這裏。所以,關於你自己的代碼,lastKnownVersion應該來自newCustData,而不是來自oldCust。 – ivom