2

我有一个从数据库读取的Spring事务,如果找不到它,它会创建它。例如:如何让两个完全相同的读写并发事务等待对方?

@Transactional 
public int getUserIdCreateIfNotExistent(String email) throws Exception { 
    try { 
    return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email); 
    } catch (IncorrectResultSizeDataAccessException e) { 
    Thread.sleep(10000); 
    return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email); 
    } 
} 

当这个方法被调用两次的同时,将在具有两个不同的用户在数据库相同的电子邮件的结果(有独特的电子邮件没有限制,这只是为了演示)。

我希望第二个事务等待第一个事务,以便只创建一个用户。我试着将事务隔离级别更改为Isolation.SERIALIZABLE,但它所做的只是使提交最后一次回滚的事务。

编辑: 之前有人建议使用​​或类似的东西锁定的Java解决方案,是要明白,这个方法是一个Web应用程序的一部分,在特定端点被击中称为是非常重要的。该Web应用程序在多个不同的计算机上运行,​​并且负载平衡,并且在端点被同时调用两次时发生问题。这意味着代码可能在两台不同的计算机上并行执行,与另一台计算机上的同一个数据库对话

+0

隔离级别不会帮助,因为你插入新行。除非遇到独特的限制。 –

回答

0

解决与咨询锁:

@Transactional 
public int getUserIdCreateIfNotExistent(String email) throws Exception { 
    try { 
    jdbcTemplate.queryForRowSet("SELECT pg_advisory_xact_lock(hashtext('users'), hashtext(?))", email); 
    return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email); 
    } catch (IncorrectResultSizeDataAccessException e) { 
    Thread.sleep(10000); 
    return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email); 
    } 
} 
0

您可以使用“锁定”表。它将包含您需要锁定的所有内容,即表名(例如“用户”)和行密钥(例如“电子邮件”)。

如果您需要更快的速度,那么您可以在整个服务器上使用memcached进行复制。如果你的插入不是很多,那么数据库解决方案可能会很好。