Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/340.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 SQL条件原子插入_Java_Sql_Postgresql_Jdbc - Fatal编程技术网

Java SQL条件原子插入

Java SQL条件原子插入,java,sql,postgresql,jdbc,Java,Sql,Postgresql,Jdbc,我的数据库中有一个事件表。定期插入新事件如下所示: private static final String INSERT_EVENT_SQL = "INSERT INTO EVENTS" + "(EVENT_ID, AGGREGATE_ID, AGGREGATE_VERSION, EVENT_TYPE, EVENT_PAYLOAD) VALUES" + "(?,?,?,?,?)"; pst = conn.prepareStatement(INSERT_EVENT_SQL); p

我的数据库中有一个事件表。定期插入新事件如下所示:

private static final String INSERT_EVENT_SQL = "INSERT INTO EVENTS"
    + "(EVENT_ID, AGGREGATE_ID, AGGREGATE_VERSION, EVENT_TYPE, EVENT_PAYLOAD) VALUES"
    + "(?,?,?,?,?)";

pst = conn.prepareStatement(INSERT_EVENT_SQL);
pst.setString(1, event.getEventId().toString());
pst.setString(2, event.getAggregateId().toString());
pst.setLong(3, event.getAggregateVersion());
pst.setString(4, event.getEventType());
pst.setString(5, event.getPayload());
我想让这个插入成为有条件的和原子的

必须满足的条件是
event.getAggregateVersion()
等于数据库中存储的当前聚合版本加1

当前聚合版本可以通过以下两种方式之一从数据库条目计算:

  • 查找具有相同聚合\u ID的最新事件并获取其聚合\u版本
  • 具有相同聚合\u ID的所有事件中聚合\u版本的最大值
  • 版本比较和插入应以原子方式进行,以防止同时插入具有相同聚合\u ID和聚合\u版本的两个事件


    很好:如果插入由于比较错误而失败,则最好抛出一个不是常规的
    SQLExeption
    (以便以特殊方式处理版本冲突)

    您可以这样编写:

    private static final String INSERT_EVENT_SQL = ""
        + "WITH data (event_id, aggregate_id, aggregate_version, event_type, event_payload) AS"
        + "("
        + "    VALUES(?, ?, ?, ?, ?, ?)"
        + ")"
        + "INSERT INTO "
        + "   events"
        + "   (event_id, aggregate_id, aggregate_version, event_type, event_payload)"
        + "SELECT"
        + "   *"
        + "FROM"
        + "   data"
        + "WHERE"
        + "   data.aggregate_version = "
        + "      coalesce ((SELECT max(events.aggregate_version)"
        + "                 FROM events "
        + "                 WHERE events. aggregate_id = data. aggregate_id"
        + "                ), 0) + 1"
        + "RETURNING"
        + "   event_id, aggregate_id ;"
    
        // bind parameters
        // execute
        // get results
    
    并检查是否返回了任何行;并据此采取行动。您应该将其包装到
    事务中
    ,并发出
    设置事务隔离级别可序列化
    ,如Craig Ringer所述,以避免并发风险。即使这只是一条语句,在计算
    max
    时,另一个并行工作的进程(或外部客户端)也可能插入一行

    我不完全理解你专栏的意思,也不完全理解你为什么要做你正在做的事情。因此,我自己做了一些假设,在寻找
    max
    时,这些假设可能并不正确。您可能应该更改
    中的


    请参见DBFIDLE,以了解其行为模拟。

    在Postgres中,增加列的正常方式是将其声明为串行。

    create table . . . (
        tableId serial primary key,
        . . .
    );
    
    这在功能上等同于其他数据库中的
    auto_increment
    identity
    。您可以在中阅读有关
    serial


    如文件中所述,在某些情况下可能存在差距。然而,这通常是将递增的序列号放入表中的最佳方法,而数据库完成了大部分工作。

    SERIALIZABLE
    隔离可能对您有所帮助;您需要一个重试循环。否则你可能需要锁定。你为什么要完成所有这些工作而不是仅仅使用
    串行
    ?@GordonLinoff,我对这项技术不是很有经验。你说的
    SERIAL
    是指
    SERIALIZATION
    隔离级别,还是其他什么?@CraigRinger,我阅读了与
    SERIALIZABLE
    相关的文档,我不确定我是否想在实现所有重试机制时遇到麻烦。我预计在可预见的未来不会出现高流量,因此我决定使用锁定。@CraigRinger,您能否对这种方法发表意见:写入
    事件
    表将获得
    独占
    锁定(以便允许并发读取)。读取将以标准方式发布,但在建立关系之前需要进行的特殊读取也将获得
    独占
    锁。感谢您的澄清,但我认为这种方法不适合我的用例。标题为
    AGGREGATE\u VESRION
    的列仅在具有相同
    AGGREGATE\u ID
    的事件上下文中类似于自动递增。对于具有不同
    AGGREGATE\u ID
    值的事件,将存在重复且无序的
    AGGREGATE\u VERSION
    值。@vasily。这是一个更具挑战性的问题。我的一般建议是输入自动递增的值,并动态计算所需的值。谢谢您的回答。我阅读了一些文档,决定使用锁定而不是
    SERIALIZABLE
    。我想使用的锁定方案如下:写入
    事件
    表将获得
    独占
    锁(以便允许并发读取)。读取将以标准方式发出,但在建立关系之前需要进行的特殊读取也将获得
    独占
    锁定。在您看来,这种方法足够安全吗?我花了一段时间才了解这里发生了什么,但现在我可以确认此解决方案有效。您可以使用
    独占
    锁。但这是您通常试图避免的,除非您的应用程序具有非常低的并发性,并且您确实可以负担得起这种奢侈。不要显式锁定,让数据库知道需要做什么才能拥有一致的数据库。而且,如果某个事务因为出错而中止(总有一天会发生),请准备重试。