ошибка обновления родительско-дочерних отношений с использованием Hibernate-JPA - PullRequest
3 голосов
/ 28 июля 2011

Моя модель домена Hibernate-JPA имеет следующие объекты:

AttributeType ------< AttributeValue

Соответствующие классы Java выглядят следующим образом (методы получения и установки отсутствуют):

@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, выполнив следующие операции в одной транзакции:

  • удалить значение атрибута "small" из типа атрибута "size"
  • добавить значение атрибута "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. Вставка

Но похоже, что ORM должен сделать это для меня. Я читал, что Oracle предоставляет опцию «deferConstraints», которая позволяет проверять ограничения только после завершения транзакции. Однако я использую SQL Server, поэтому это мне не поможет.

Ответы [ 3 ]

4 голосов
/ 09 августа 2011

Вам нужно использовать составной идентификатор вместо сгенерированного идентификатора.

HHH-2801

Проблема возникает, когда новый объект ассоциации с сгенерированным идентификатором добавлен в коллекцию. Первый шаг при объединении сущности содержащий эту коллекцию, является каскадом для сохранения новой ассоциации юридическое лицо. Каскад должен происходить до других изменений в коллекции. Поскольку уникальный ключ для этого нового объекта ассоциации такой же, как Для сущности, которая уже существует, ConstraintViolationException выброшены. Это ожидаемое поведение .

Использование новой коллекции (т.е. удаление одним выстрелом), как это предлагается в предыдущий комментарий) также приводит к нарушению ограничения, так как новый объект ассоциации будет сохранен на каскаде нового коллекция.

Пример одного из подходов (использование составного идентификатора вместо сгенерированного идентификатора) показан> в 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;
        }
    }
}

См. 5.1.2.1. Составной идентификатор о том, как это сделать с аннотацией JPA.
См. Глава 8. Сопоставление компонентов
См. 8.4. Компоненты в качестве составных идентификаторов

2 голосов
/ 11 августа 2011

Я не уверен, что понимаю вопрос, поскольку он становится поздним, но в первую очередь я бы попытался переопределить метод AttributeValue, равный , чтобы содержать эти два уникальных поля.

0 голосов
/ 09 августа 2011

В сеансе гибернации есть одна очередь для удаления и одна для вставки. Отладка, чтобы увидеть, происходит ли удаление перед вставкой.

Посмотрите на слияние. Попробуйте использовать обновление вместо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...