开发者

error updating parent-child relationship using Hibernate-JPA

开发者 https://www.devze.com 2023-03-23 07:18 出处:网络
My Hibernate-JPA domain model has these entities: AttributeType ------< AttributeValue The relevant Java classes look like this (getters and setters omitted):

My Hibernate-JPA domain model has these entities:

AttributeType ------< AttributeValue

The relevant Java classes look like this (getters and setters omitted):

@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;
}

Notice there's a unique constraint on AttributeValue.value and AttributeValue.attributeType, because for an attribute type (e.g. size) we don't want to allow an attribute value (e.g. small) to occur more than once.

If I update an AttributeType by performing the following operations within a single transaction:

  • delete "small" attribute value from "size" attribute type
  • add "small" attribute value to "size" attribute type

I get an exception that indicates the unique constraint was violated. This suggests that Hibernate-JPA is performing the insertion of the attribute value before the delete, which seems to invite this kind of problem for no obvious reason.

The class that performs the update of an AttributeType looks like this:

@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 = 开发者_如何学GoentityManager.merge(attributeType);
    entityManager.flush();
    entityManager.refresh(attributeType);
    return attributeType;
  }
}

I could workaround this problem by iterating over the attribute values, figuring out which ones have been updated/deleted/inserted, and performing them in this order instead:

  1. deletes
  2. updates
  3. inserts

But it seems like the ORM should be able to do this for me. I've read that Oracle provides a "deferConstraints" option that causes constraints to be checked only when a transaction has completed. However, I'm using SQL Server, so this won't help me.


You need to use a composite ID instead of a generated ID.

HHH-2801

The problem arises when a new association entity with a generated ID is added to the collection. The first step, when merging an entity containing this collection, is to cascade save the new association entity. The cascade must occur before other changes to the collection. Because the unique key for this new association entity is the same as an entity that is already persisted, a ConstraintViolationException is thrown. This is expected behavior.

Using a new collection (i.e., one-shot delete), as suggested in the previous comment) also results in a constraint violation, since the new association entity will be saved on the cascade of the new collection.

An example of one of the approaches (using a composite ID instead of a generated ID) is illustrated >in manytomanywithassocclass.tar.gz and is checked into 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;
        }
    }
}

See 5.1.2.1. Composite identifier on how to do it with JPA annotation.
See Chapter 8. Component Mapping
See 8.4. Components as composite identifiers


I am not sure if I understand the question as it is getting late, but first thing I would try would be to override AttributeValue's equals method to contain those two unique fields.


In hibernate session there is one queue for the delete and one for the insert. Debug to see if deletes comes before insert.

Look at the merge. Try using update instead.

0

精彩评论

暂无评论...
验证码 换一张
取 消