Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/jquery-ui/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 达托米语;更新;在爪哇_Java_Datomic - Fatal编程技术网

Java 达托米语;更新;在爪哇

Java 达托米语;更新;在爪哇,java,datomic,Java,Datomic,我用Java做我的项目(后端)。我不想改用Clojure(至少现在还不想) 然而,Datomic看起来很有趣,并且它声明它有一个JavaAPI,但是我仍然有几个问题没有解决,最重要的是这个问题 举个例子,假设我们有一个客户实体,其业务属性为name、email和phone。因此,在Java中,我们有如下内容: public class Customer { private Long id; private String name; private String email; pr

我用Java做我的项目(后端)。我不想改用Clojure(至少现在还不想)

然而,Datomic看起来很有趣,并且它声明它有一个JavaAPI,但是我仍然有几个问题没有解决,最重要的是这个问题

举个例子,假设我们有一个客户实体,其业务属性为name、email和phone。因此,在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模式声明了相应的属性:customer/name、:customer/email、:customer/phone等

有一个“编辑客户”表单,为要更改的用户公开3个业务属性。假设我更改了姓名和电子邮件,并保存了表单

现在,我应该怎么做才能将更改保存到Datomic?如何构建事务

Datomic提供的示例过于简单化,示例非常接近,但根本没有帮助。我用谷歌搜索了一下,但没有用

答案应该是:

  • 包含真正的Java(非Clojure)代码,直至调用connection.transact
  • 可重用/不需要对其他实体进行复制和粘贴
  • 仅更新已更改的属性(?)-我理解我应该只处理值实际已更改的属性(正确吗?)
  • 正确解决多个用户的并发编辑,即用户不应覆盖彼此的工作。这通常通过乐观锁定来解决。那么,如何在Java中使用Datomic实现乐观锁定呢?还是有其他策略
  • (最后,一个旁注——不是问题本身的一部分。为什么像“编辑实体”这样的核心用例没有在Datomic Java文档中解释,也没有一个官方示例显示如何以最佳方式实现这一点?这种感觉类似于“Datomic Java API”在我看来,Java和Clojure在不同的范例上工作,所以简单地将Clojure API 1:1移植到Java并不能构成Java API。
    难道我不应该给客户添加一点注释(比如@Id和@Version),然后打电话给connection.persist(客户);然后处理好它吗?我知道,可怕的ORM龙又抬起了它丑陋的头。但是,嘿,也许现在我将学习如何以一种更优雅的方式来处理它。)

    回答您的一些问题:

  • 您不必只处理已更改的字段,Datomic将在事务运行时为您删除未更改的属性
  • Datomic不提供类映射层,可能永远也不会提供。我不知道社区中有哪一个已经开发出来了,我也不感到惊讶,因为这个社区倾向于为了通用性而支持面向数据(而不是基于类)的体系结构。因此,您将找不到从Datomic数据到POJO的通用转换工具,例如ORMs提供的
  • 这并不意味着Java在这里是二等公民——但Datomic会迫使您(如果您要求创建者,为了您自己的利益)使用列表和地图等数据结构,而不是POJO来传递信息。这在Clojure中确实比在Java中更惯用
  • 我个人强烈建议在您的业务逻辑中使用实体(即
    datomic.Entity
    类的实例)而不是POJO——至少在编写映射代码之前尝试一下,看看这是否是一个问题。您将失去一些静态保证,并且可能会丢失很多样板文件。尽管如此,下面的实现仍然使用POJO
  • 我已经在下面给了你我的实现。基本上,您可以将客户对象转换为事务映射,并使用
    :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;
        }
    }
    

    嗨,瓦伦丁,谢谢你的回答。我有两个问题:1)我不明白为什么要在更新中创建新的tempid。我在没有它的情况下进行了更新。2) 我无法在版本不匹配的情况下抛出db.fn/cas。我已经在这里更新了我的示例项目:CustomerServiceTest.update_Conflict()测试失败。1)由于upsert行为,tempids不会受损,这就是我一直使用它们的原因。2) 您的测试不起作用,因为在您的
    update()
    方法中,以前的版本是根据数据库调用计算的,而不是从您作为参数传递的客户(以及您伪造的版本)计算的。2)对,我现在得出了相同的结论,但发现您已经在这里指出了这一点。因此,关于您自己的代码,最新的知识转换应该来自newCustData,而不是oldCust。我接受您的回答,因为您帮助我实现了乐观锁定。由此产生的支持基本创建、获取、更新和列表的通用Datomic存储库就在这里:我遇到的所有问题都以某种方式得到了解决。谢谢