服务器重启后插入时违反唯一约束
Unique constraint violation on insertion after server restart
我是 运行 JBoss EAP 7 服务器,带有 Oracle 11g 数据库和 Hibernate for JPA。我注意到了一些奇怪的事情。当我第一次创建数据库并启动服务器时,一切正常。我从客户端发送请求,服务器将数据保存在数据库中。
如果我重新启动服务器并尝试做同样的事情,我会得到每个请求的唯一约束违反异常:
java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SCHEMA.SYS_C0010299) violated
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:208)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1046)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1336)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3613)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3694)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1354)
at org.jboss.jca.adapters.jdbc.WrappedPreparedStatement.executeUpdate(WrappedPreparedStatement.java:537)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
我用下面的查询检查了 sqlplus 中的约束。 (我 运行 作为系统用户进行查询,而不是与服务器相同的用户,如果这很重要的话)。
SELECT A.TABLE_NAME,C.TABLE_NAME,COLUMN_NAME FROM ALL_CONS_COLUMNS A
JOIN ALL_CONSTRAINTS C ON A.CONSTRAINT_NAME = C.CONSTRAINT_NAME
WHERE C.CONSTRAINT_NAME = 'SYS_C0010299';
它似乎发生在我的 table 之一的主键上。该主键是使用序列生成的。
@Id
@Column(name="ID_COL")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "SEQ_NAME_GEN")
@SequenceGenerator(name = "SEQ_NAME_GEN", sequenceName = "SEQ_NAME")
private Long id;
如果我创建一个全新的数据库,应用程序一开始会再次正常工作,直到我重新启动服务器。为什么会这样?
这是实体 class 与另一个实体 class 的关系:
// other class
@OneToMany(cascade=CascadeType.ALL, mappedBy="otherClass")
@MapKey(name = "mapKey")
private Map<MapKey, ConstraintViolationEntityClass>
map;
// problematic class (ConstraintViolationEntityClass)
@Column(name = "MAP_KEY")
@Enumerated(EnumType.ORDINAL)
private EnumType enumType;
@ManyToOne
@JoinColumn(name = "OTHER_CLASS_ID", nullable = false)
private OtherClass otherClass;
这是我用来为 ConstraintViolationEntityClass 创建 table 的 SQL 代码:
create table schema.ConstraintViolationEntityTable (
id_col number(10) not null primary key,
map_key number(2) not null,
other_class_id number(10) not null,
constraint other_class_fk foreign key (other_class_id) references schema.other_class(id)
);
这是我的 persistence.xml:
<persistence-unit name="unit1" transaction-type="JTA">
<jta-data-source>java:jboss/OracleDS</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="validate" />
</properties>
</persistence-unit>
由于某些原因,成功请求插入的行的某些主键是负数。并检查 dba_sequences
,序列的 last_number
是 43,即使 table 中只有 24 行(每个客户端请求添加 12 行)
正如 PeterM 链接到的答案中所述,序列生成器的默认分配大小为 50,这是问题的根本原因,因为您定义的序列增量为 1。我只是评论关于负值问题。
分配大小为 50(在 SequenceGenerator.allocationSize
中设置)意味着 Hibernate 将:
- 使用
INCREMENT BY 50
创建序列(如果允许的话)
- 从序列
中获取下一个值n
- 从
n-50
到 n
开始分配 ID
- 一旦运行数量不足
,请重复上述两个步骤
由于您已将序列递增 1,因此很容易看出负值的来源(以及为什么会出现约束违规)。如果您尝试插入超过 50 行,您将 运行 违反约束而无需重新启动服务器。
我是 运行 JBoss EAP 7 服务器,带有 Oracle 11g 数据库和 Hibernate for JPA。我注意到了一些奇怪的事情。当我第一次创建数据库并启动服务器时,一切正常。我从客户端发送请求,服务器将数据保存在数据库中。
如果我重新启动服务器并尝试做同样的事情,我会得到每个请求的唯一约束违反异常:
java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SCHEMA.SYS_C0010299) violated
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:208)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1046)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1336)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3613)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3694)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1354)
at org.jboss.jca.adapters.jdbc.WrappedPreparedStatement.executeUpdate(WrappedPreparedStatement.java:537)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
我用下面的查询检查了 sqlplus 中的约束。 (我 运行 作为系统用户进行查询,而不是与服务器相同的用户,如果这很重要的话)。
SELECT A.TABLE_NAME,C.TABLE_NAME,COLUMN_NAME FROM ALL_CONS_COLUMNS A
JOIN ALL_CONSTRAINTS C ON A.CONSTRAINT_NAME = C.CONSTRAINT_NAME
WHERE C.CONSTRAINT_NAME = 'SYS_C0010299';
它似乎发生在我的 table 之一的主键上。该主键是使用序列生成的。
@Id
@Column(name="ID_COL")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "SEQ_NAME_GEN")
@SequenceGenerator(name = "SEQ_NAME_GEN", sequenceName = "SEQ_NAME")
private Long id;
如果我创建一个全新的数据库,应用程序一开始会再次正常工作,直到我重新启动服务器。为什么会这样?
这是实体 class 与另一个实体 class 的关系:
// other class
@OneToMany(cascade=CascadeType.ALL, mappedBy="otherClass")
@MapKey(name = "mapKey")
private Map<MapKey, ConstraintViolationEntityClass>
map;
// problematic class (ConstraintViolationEntityClass)
@Column(name = "MAP_KEY")
@Enumerated(EnumType.ORDINAL)
private EnumType enumType;
@ManyToOne
@JoinColumn(name = "OTHER_CLASS_ID", nullable = false)
private OtherClass otherClass;
这是我用来为 ConstraintViolationEntityClass 创建 table 的 SQL 代码:
create table schema.ConstraintViolationEntityTable (
id_col number(10) not null primary key,
map_key number(2) not null,
other_class_id number(10) not null,
constraint other_class_fk foreign key (other_class_id) references schema.other_class(id)
);
这是我的 persistence.xml:
<persistence-unit name="unit1" transaction-type="JTA">
<jta-data-source>java:jboss/OracleDS</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="validate" />
</properties>
</persistence-unit>
由于某些原因,成功请求插入的行的某些主键是负数。并检查 dba_sequences
,序列的 last_number
是 43,即使 table 中只有 24 行(每个客户端请求添加 12 行)
正如 PeterM 链接到的答案中所述,序列生成器的默认分配大小为 50,这是问题的根本原因,因为您定义的序列增量为 1。我只是评论关于负值问题。
分配大小为 50(在 SequenceGenerator.allocationSize
中设置)意味着 Hibernate 将:
- 使用
INCREMENT BY 50
创建序列(如果允许的话) - 从序列 中获取下一个值
- 从
n-50
到n
开始分配 ID
- 一旦运行数量不足 ,请重复上述两个步骤
n
由于您已将序列递增 1,因此很容易看出负值的来源(以及为什么会出现约束违规)。如果您尝试插入超过 50 行,您将 运行 违反约束而无需重新启动服务器。