使用Hibernate-JPA更新父子关系时出错

时间:2011-07-28 10:30:41

标签: java hibernate jpa persistence

我的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.valueAttributeValue.attributeType存在唯一约束,因为对于属性类型(例如大小),我们不希望允许属性值(例如小)多次出现。

如果我通过在单个交易中执行以下操作来更新AttributeType

  • 从“尺寸”属性类型
  • 中删除“小”属性值
  • 将“small”属性值添加到“size”属性类型

我得到一个异常,表明违反了唯一约束。这表明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;
  }
}

我可以通过迭代属性值来解决这个问题,找出哪些已经更新/删除/插入,然后按此顺序执行:

  1. 删除
  2. 更新
  3. 插入
  4. 但似乎ORM应该能够为我做到这一点。我已经读过Oracle提供了一个“deferConstraints”选项,只有在事务完成时才会检查约束。但是,我正在使用SQL Server,所以这对我没用。

3 个答案:

答案 0 :(得分:4)

您需要使用复合ID而不是生成的ID。

  

HHH-2801

     

当具有生成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 identifierChapter 8. Component Mapping
8.4. Components as composite identifiers

答案 1 :(得分:2)

我不确定我是否理解这个问题,因为它已经迟到了,但我要尝试的第一件事就是覆盖AttributeValue的 equals 方法来包含这两个唯一的字段。

答案 2 :(得分:0)

在hibernate会话中,有一个用于删除的队列和一个用于插入的队列。调试以查看插入之前是否有删除。

看看合并。请尝试使用更新。