我的Hibernate-JPA域模型包含以下实体:
AttributeType ------< AttributeValue
相关的Java类看起来像这样(省略了getters和setter):
@Entity
public class AttributeType {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique = true, nullable = false)
private String name;
@OneToMany(mappedBy = "attributeType", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<AttributeValue> values = new ArrayList<AttributeValue>();
}
@Entity @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"value", "attribute_type_id"}))
public class AttributeValue {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne(optional = false)
private AttributeType attributeType;
@Column(nullable = false)
private String value;
}
请注意,AttributeValue.value
和AttributeValue.attributeType
存在唯一约束,因为对于属性类型(例如大小),我们不希望允许属性值(例如小)多次出现。
如果我通过在单个交易中执行以下操作来更新AttributeType
:
我得到一个异常,表明违反了唯一约束。这表明Hibernate-JPA在删除之前正在执行属性值的插入,这似乎没有明显的原因引发这种问题。
执行AttributeType
更新的类看起来像这样:
@Transactional(propagation = Propagation.SUPPORTS)
public class SomeService {
private EntityManager entityManager; // set by dependency injection
@Transactional(propagation = Propagation.REQUIRED)
public AttributeType updateAttributeType(AttributeType attributeType) throws Exception {
attributeType = entityManager.merge(attributeType);
entityManager.flush();
entityManager.refresh(attributeType);
return attributeType;
}
}
我可以通过迭代属性值来解决这个问题,找出哪些已经更新/删除/插入,然后按此顺序执行:
但似乎ORM应该能够为我做到这一点。我已经读过Oracle提供了一个“deferConstraints”选项,只有在事务完成时才会检查约束。但是,我正在使用SQL Server,所以这对我没用。
答案 0 :(得分:4)
您需要使用复合ID而不是生成的ID。
当具有生成ID的新关联实体出现问题时 被添加到集合中。第一步,合并实体时 包含此集合,是级联保存新关联 实体。级联必须在对集合进行其他更改之前发生。 因为这个新关联实体的唯一键是相同的 一个已经持久化的实体,一个ConstraintViolationException 抛出。 这是预期行为。
使用新的集合(即一次性删除),如中所示 之前的评论)也会导致约束违规,因为 新的关联实体将保存在新的级联中 集合。
其中一种方法(使用复合ID而不是生成的ID)的示例在manytomanywithassocclass.tar.gz中进行了说明,并被检入Svn。
@Entity
public class AttributeType {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
@Column(unique = true, nullable = false)
private String name;
@OneToMany(mappedBy = "attributeType", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<AttributeValue> values = new ArrayList<AttributeValue>();
//Getter, Setter...
}
@Entity
@Table (uniqueConstraints = @UniqueConstraint(columnNames = { "value", "attributeType_id" }))
public class AttributeValue{
@EmbeddedId AttributeValueId id;
@MapsId(value= "id")
@ManyToOne(optional = false)
private AttributeType attributeType;
private String value2;
public AttributeValue() {
this.id = new AttributeValueId();
}
public AttributeType getAttributeType() {
return attributeType;
}
public void setAttributeType(AttributeType pAttributeType) {
this.id.setAttributeTypeID(pAttributeType.getId());
this.attributeType = pAttributeType;
}
public String getValue() {
return id.getAttributeValue();
}
public void setValue(String value) {
this.id.setAttributeValue(value);
}
@Embeddable
public static class AttributeValueId implements Serializable {
private Integer id;
private String value;
public AttributeValueId() {
}
public AttributeValueId(Integer pAttributeTypeID, String pAttributeValue) {
this.id = pAttributeTypeID;
this.value = pAttributeValue;
}
public Integer getAttributeTypeID() {
return id;
}
public void setAttributeTypeID(Integer attributeTypeID) {
this.id = attributeTypeID;
}
public String getAttributeValue() {
return value;
}
public void setAttributeValue(String attributeValue) {
this.value = attributeValue;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime
* result
+ ((id == null) ? 0 : id
.hashCode());
result = prime
* result
+ ((value == null) ? 0 : value.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;
AttributeValueId other = (AttributeValueId) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
}
有关如何使用JPA注释的信息,请参阅5.1.2.1. Composite identifier
见Chapter 8. Component Mapping
见8.4. Components as composite identifiers
答案 1 :(得分:2)
我不确定我是否理解这个问题,因为它已经迟到了,但我要尝试的第一件事就是覆盖AttributeValue的 equals 方法来包含这两个唯一的字段。
答案 2 :(得分:0)
在hibernate会话中,有一个用于删除的队列和一个用于插入的队列。调试以查看插入之前是否有删除。
看看合并。请尝试使用更新。