参照完整性约束违规-如何在同一事务中引用实体之前强制写入引用实体?

时间:2018-08-22 15:28:10

标签: hibernate

我有一个Hibernate实体,其中包含对另一个实体的引用:

class AddressEntity {

    private @ManyToOne(cascade=CascadeType.PERSIST) AddressKeyEntity addressKey;

    ...

当尝试通过AddressKeyEntity服务内的AddressEntity保存currentSession()和包含对象@Transactional时,在刷新时出现错误:

Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FKRA3ESUOPYYQJ271LGGEASAFXU: PUBLIC.ADDRESSENTITY FOREIGN KEY(ADDRESSKEY_ID) REFERENCES PUBLIC.ADDRESSKEYENTITY(ID) (14410)"; SQL statement:
insert into AddressEntity (addressKey_id, latitude, longitude, id) values (?, ?, ?, ?) [23506-196]

从该错误看来,引用对象正在被引用对象之前 写入数据库,从而导致FK约束违反。

如何解决?

更新 这是save方法(我称为persist)和两个相关方法的代码。

public void merge(AddressEntity item) {
    if(item != null) {
        item.resetBoundaries(nb -> mergeNamedBoundary(nb.getValue()));
        Session session = sessionFactory.getCurrentSession();
        if(!session.contains(item)) {
            session.save(item);
        }
    }
}

public AddressKeyEntity merge(AddressKeyEntity addressKeyEntity) {

    AddressKeyEntity ret = fetch(addressKeyEntity);

    if(ret == null) {
        sessionFactory.getCurrentSession().save(addressKeyEntity);
        ret = addressKeyEntity;
    }       
    return ret;

}

public Map<AddressKeyEntity, AddressEntity> persist(Map<AddressKeyEntity, AddressEntity> newValues) {
    Session session = sessionFactory.getCurrentSession();

    newValues.keySet().forEach(this::merge);
    session.flush();

    Map<AddressKeyEntity, AddressEntity> out = new HashMap<>();
    newValues.forEach((k,v) -> {
        AddressKeyEntity pKey = merge(v.getAddressKey());
        v.setAddressKey(pKey);
        merge(v);
        out.put(pKey, v);
    });
    session.flush();
    return out;
}

这是AddressKeyEntity

package com.ctc.gis.dto.address;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.NaturalId;

import com.ctc.gis.api.AddressKey;
import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = {"address", "city", "state", "zip", "apn"}) })
public class AddressKeyEntity implements AddressKey {

    private @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) Integer id;

    private @NaturalId String address;

    private @NaturalId String city;

    private @NaturalId String state;

    private @NaturalId Integer zip;

    private @NaturalId String apn;

    public AddressKeyEntity() {
    }

    public AddressKeyEntity(String address, String city, String state, Integer zip, String apn) {
        this.setAddress(address);
        this.setCity(city);
        this.setState(state);
        this.setZip(zip);
        this.setApn(apn);
    }

    public AddressKeyEntity(AddressKey addressKey) {
        this(addressKey.getAddress(), addressKey.getCity(), addressKey.getState(), addressKey.getZip(), addressKey.getApn());
    }

    @Override
    public String getAddress() {
        return address;
    }

    public void setAddress(String streetAddress) {
        this.address = normalize(streetAddress);
    }

    @Override
    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = normalize(city);
    }

    @Override
    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = normalize(state);
    }

    private static String normalize(String str) {
        return str == null ? null : str.toUpperCase();
    }

    @Override
    public Integer getZip() {
        return zip;
    }

    public void setZip(Integer zip) {
        this.zip = zip;
    }

    @Override
    public String getApn() {
        return apn;
    }

    public void setApn(String apn) {
        this.apn = apn;
    }

    @Override
    public String toString() {
        return asKey();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((apn == null) ? 0 : apn.hashCode());
        result = prime * result + ((address == null) ? 0 : address.hashCode());
        result = prime * result + ((city == null) ? 0 : city.hashCode());
        result = prime * result + ((state == null) ? 0 : state.hashCode());
        result = prime * result + ((zip == null) ? 0 : zip.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AddressKeyEntity other = (AddressKeyEntity) obj;
        if (apn == null) {
            if (other.apn != null)
                return false;
        } else if (!apn.equals(other.apn))
            return false;
        if (address == null) {
            if (other.address != null)
                return false;
        } else if (!address.equals(other.address))
            return false;
        if (city == null) {
            if (other.city != null)
                return false;
        } else if (!city.equals(other.city))
            return false;
        if (state == null) {
            if (other.state != null)
                return false;
        } else if (!state.equals(other.state))
            return false;
        if (zip == null) {
            if (other.zip != null)
                return false;
        } else if (!zip.equals(other.zip))
            return false;
        return true;
    }

    @JsonIgnore
    public boolean isTransient() {
        return id == null;
    }

    public Integer getId() {
        return id;
    }
}

1 个答案:

答案 0 :(得分:0)

问题是由AddressKeyEntity.equals()的不适当实现引起的,如问题所示。

在将普通if (getClass() != obj.getClass())与已被休眠代理替换的AddressKeyEntity进行比较时,条件equals()和所有直接字段访问都会导致不正确的不匹配。

错误的persist()导致equals()方法中的HashMap逻辑崩溃。

通过用定制的方法替换Eclipse生成的class Test : IJobTask { public void Start(string val = "") { throw new NotImplementedException(); } } public interface ITest { void MyMethod<T>(T model) where T : IJobTask; } public class ConcreteTest : ITest { public void MyMethod<T>(T model) where T : IJobTask { } } public class Main { public Main() { var ct = new ConcreteTest(); ct.MyMethod(new Test()); } } 方法来解决,该方法仅使用接口上的字段获取器比较每个字段,而忽略类差异。