我正在一个集群环境下开发项目,其中有许多节点和一个单独的数据库。该项目使用Spring-data-JPA(1.9.0)和Hibernate(5.0.1)。我遇到了如何解决防止重复行问题的困难。
为了举例说明,这里是一个简单的表格。
我该如何编写一个服务方法来有条件地插入到这个表中? 我尝试过的几个方法都行不通:
1. 查找->操作 - 服务方法将使用存储库查看条目是否已存在,然后根据需要更新找到的条目或保存新条目。这样做行不通。 2. 尝试插入->如果失败则更新 - 服务方法会尝试插入,捕捉由于唯一约束而引起的异常,然后进行更新。这样做是不行的,因为事务已经处于回滚状态,无法进行进一步的操作。 3. 使用“INSERT INTO ... WHERE NOT EXISTS ...”的本地查询* - 存储库有一个新的本地查询:
为了举例说明,这里是一个简单的表格。
@Entity
@Table(name = "scheduled_updates")
public class ScheduledUpdateData {
public enum UpdateType {
TYPE_A,
TYPE_B
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private UUID id;
@Column(name = "type", nullable = false)
@Enumerated(EnumType.STRING)
private UpdateType type;
@Column(name = "source", nullable = false)
private UUID source;
}
重要的部分是有一个UNIQUE(type, source)
约束。
当然,还有匹配的示例存储库:
@Repository
public class ScheduledUpdateRepository implements JpaRepository<ScheduledUpdateData, UUID> {
ScheduledUpdateData findOneByTypeAndSource(final UpdateType type, final UUID source);
//...
}
这个例子的想法是,系统的某些部分可以插入行,以便在两次运行之间的任意次数运行。当那个东西实际运行时,它不必担心对同一件事情进行两次操作。我该如何编写一个服务方法来有条件地插入到这个表中? 我尝试过的几个方法都行不通:
1. 查找->操作 - 服务方法将使用存储库查看条目是否已存在,然后根据需要更新找到的条目或保存新条目。这样做行不通。 2. 尝试插入->如果失败则更新 - 服务方法会尝试插入,捕捉由于唯一约束而引起的异常,然后进行更新。这样做是不行的,因为事务已经处于回滚状态,无法进行进一步的操作。 3. 使用“INSERT INTO ... WHERE NOT EXISTS ...”的本地查询* - 存储库有一个新的本地查询:
@Repository
public class ScheduledUpdateRepository implements JpaRepository<ScheduledUpdateData, UUID> {
// ...
@Modifying
@Query(nativeQuery = true, value = "INSERT INTO scheduled_updates (type, source)" +
" SELECT :type, :src" +
" WHERE NOT EXISTS (SELECT * FROM scheduled_updates WHERE type = :type AND source = :src)")
void insertUniquely(@Param("type") final String type, @Param("src") final UUID source);
}
很不幸,这也行不通,因为Hibernate似乎会首先执行WHERE子句使用的SELECT - 这意味着最终将尝试多次插入,从而导致唯一约束冲突。
我确实不了解JTA、JPA或Hibernate的很多细节。在多个JVM之间插入具有唯一约束条件(除主键外)的表的任何建议吗?
编辑2016-02-02
使用Postgres(2.3)作为数据库,尝试使用隔离级别SERIALIZABLE - 不幸的是,仅靠它仍然会导致约束冲突异常。
@Version
并不能解决问题,因为当两个事务认为该行尚不存在时,就会出现错误。 - mrusinak