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
当前聚合版本可以通过以下两种方式之一从数据库条目计算:
很好:如果插入由于比较错误而失败,则最好抛出一个不是常规的
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
。我想使用的锁定方案如下:写入事件
表将获得独占
锁(以便允许并发读取)。读取将以标准方式发出,但在建立关系之前需要进行的特殊读取也将获得独占
锁定。在您看来,这种方法足够安全吗?我花了一段时间才了解这里发生了什么,但现在我可以确认此解决方案有效。您可以使用独占
锁。但这是您通常试图避免的,除非您的应用程序具有非常低的并发性,并且您确实可以负担得起这种奢侈。不要显式锁定,让数据库知道需要做什么才能拥有一致的数据库。而且,如果某个事务因为出错而中止(总有一天会发生),请准备重试。