Oracle X 10g上的外键数据完整性违规

时间:2014-09-19 11:32:21

标签: oracle jpa

我有针对MySQL实例或Oracle实例运行的集成测试。

在针对MySQL实例构建Maven项目时,测试正常。

这是表结构:

drop table if exists operator;
create table operator (
  id bigint(20) unsigned not null auto_increment,
  version int(10) unsigned not null,
  name varchar(50),
  unique key name (name),
  description varchar(255),
  operator_id varchar(50),
  unique key operator_id (operator_id),
  image varchar(255),
  url varchar(255),
  country_id bigint(20) unsigned not null,
  primary key (id),
  unique key id (id),
  key country_id (country_id),
  constraint operator_fk1 foreign key (country_id) references country (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

但是当相同的测试针对Oracle实例运行时,它会给我一个例外:

1. insert into operator (version, country_id, description, image, name, operator_id, url, id) values (0, 19, 'The SFR operator', 'sfr.jpg', 'SFR', NULL, 'sfr.fr', 10)
java.sql.SQLIntegrityConstraintViolationException: ORA-02291: integrity constraint (NITROPROJECT.OPERATOR_FK1) violated - parent key not found

这是表结构:

create table operator (
  id number(10) not null,
  version number(10) not null,
  name varchar2(50),
  constraint operator_u1 unique (name),
  description varchar2(255),
  operator_id varchar2(50),
  constraint operator_u2 unique (operator_id),
  image varchar2(255),
  url varchar2(255),
  country_id number(10) not null,
  constraint operator_pk primary key (id),
  constraint operator_fk1 foreign key (country_id) references country (id)
);
create sequence sq_id_operator increment by 1 start with 1 nomaxvalue nocycle cache 10;
create or replace trigger tr_id_inc_operator 
before insert 
on operator
for each row
declare
begin
  if (:new.id is null)
  then
    select sq_id_operator.nextval into :new.id from dual;
  end if;
end;
/

create table country (
  id number(10) not null,
  version number(10) not null,
  code varchar2(4) not null,
  constraint country_u1 unique (code),
  name varchar2(50) not null,
  list_order number(10),
  constraint country_pk primary key (id)
);
create sequence sq_id_country increment by 1 start with 1 nomaxvalue nocycle cache 10;
create index country_i1 on country (list_order, name);
create or replace trigger tr_id_inc_country
before insert
on country
for each row
declare
begin
  select sq_id_country.nextval into :new.id from dual;
end;
/

使用以下服务创建国家/地区:

@Modifying
@Transactional(rollbackFor = EntityAlreadyExistsException.class)
@Override
public Country add(Country country) {
    if (findByCode(country.getCode()) == null) {
        // Save the returned id into the entity
        country = countryRepository.saveAndFlush(country);
        return country;
    } else {
        throw new EntityAlreadyExistsException();
    }
}

我可以在控制台日志中看到实际创建了该国家/地区并返回了其主键ID:

2014-09-19 13:00:05,839 DEBUG  [sqlonly]  com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:147)
1. insert into country (version, code, list_order, name, id) values (0, 'fr', 1, 'France', 19)

事实上,我还添加了一个finder调用,以确保在创建国家后可以检索该国:

countryFR = new Country();
countryFR.setCode("fr");
countryFR.setName("France");
countryFR.setListOrder(1);
Country country = countryService.findByCode(countryFR.getCode());
if (country == null) {
    countryFR = countryService.add(countryFR);
} else {
    countryFR = country;
}
Country myc = countryService.findById(countryFR.getId());
if (myc != null) {
    logger.debug("==============>> Found the country id: " + myc.getId());
}

控制台日志确实显示了记录器输出:

2014-09-19 13:00:05,854 DEBUG  [BTSControllerTest] ==============>> Found the country id: 19

注意:控制台日志不会显示与该findById调用相对应的任何select语句。

然后尝试插入一个运算符:

1. insert into operator (version, country_id, description, image, name, operator_id, url, id) values (0, 19, 'The SFR operator', 'sfr.jpg', 'SFR', NULL, 'sfr.fr', 10)

您可以看到国家/地区ID与插入国家/地区的ID相同。

总结一下:

上面关于缺少select语句的说明让我想知道这个国家是否真的插入了。

插入国家/地区后检索到的主键ID为19会让我认为该国家实际上是插入的。

那么Oracle怎么抱怨它找不到运营商的外键呢?

我使用JPA2 hibernate-jpa-2.1-api 1.0.0.Final和spring-data-jpa 1.6.2.RELEASE和hibernate 4.3.6.Final

以下是连接属性:

jpaPropertiesMap.put("hibernate.dialect", databaseProperties.getHibernateDialect());
jpaPropertiesMap.put("hibernate.show_sql", "true");
jpaPropertiesMap.put("hibernate.format_sql", "true");
jpaPropertiesMap.put("hibernate.hbm2ddl.auto", databaseProperties.getHibernateHbm2ddlAuto());
jpaPropertiesMap.put("hibernate.transaction.factory_class", "org.hibernate.transaction.JDBCTransactionFactory");
jpaPropertiesMap.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
jpaPropertiesMap.put("hibernate.c3p0.min_size", "5");
jpaPropertiesMap.put("hibernate.c3p0.max_size", "20");
jpaPropertiesMap.put("hibernate.c3p0.timeout", "1000");
jpaPropertiesMap.put("hibernate.c3p0.max_statements", "50");

编辑:

我在JPA设置中添加了以下属性:

jpaPropertiesMap.put("hibernate.connection.autocommit", "true");
jpaPropertiesMap.put("hibernate.cache.use_query_cache", "false");
jpaPropertiesMap.put("hibernate.cache.use_second_level_cache", "false");

但它在这个问题上没有任何改变。

1 个答案:

答案 0 :(得分:0)

我找到了解决方案。我的序列触发器缺少if语句。现在它有一个如下:if(:new.id为null)

create or replace trigger tr_id_inc_country
before insert
on country
for each row
declare
begin
  if (:new.id is null)
    then
    select sq_id_country.nextval into :new.id from dual;
  end if;
end;
/

我认为Hibernate正在获取插入的序列号,然后Oracle在提交时获得了另一个序列号。我只是在这里猜测。