Hibernate - @ElementCollection - Странное поведение удаления / вставки - PullRequest
39 голосов
/ 18 сентября 2010
@Entity
public class Person {

    @ElementCollection
    @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
    private List<Location> locations;

    [...]

}

@Embeddable
public class Location {

    [...]

}

Учитывая следующую структуру классов, когда я пытаюсь добавить новое местоположение в список расположений людей, это всегда приводит к следующим запросам SQL:

DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson

И

A lotsa' inserts into the PERSON_LOCATIONS table

Hibernate (3.5.x / JPA 2) удаляет все связанные записи для данного Персона и повторно вставляет все предыдущие записи, а также новую.

У меня была идея, что метод equals / hashcode в Location решит проблему, но это ничего не изменило.

Любые советы приветствуются!

Ответы [ 4 ]

58 голосов
/ 18 сентября 2010

Проблема как-то объясняется на странице о ElementCollection вики-книги JPA:

Первичные ключи в CollectionTable

Спецификация JPA 2.0 не предоставить способ определения Id в Embeddable. Однако, чтобы удалить или обновить элемент ElementCollection отображение, некоторые уникальные ключ обычно требуется. Иначе, при каждом обновлении провайдер JPA будет нужно удалить все из CollectionTable для Entity и затем вставьте значения обратно. Итак, JPA провайдер, скорее всего, примет что сочетание всех поля в Embeddable являются уникальными, в сочетании с внешним ключом (JoinColunm (с)). Это, однако, может быть неэффективно, или просто неосуществимо, если Embeddable большой или сложный.

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

Однако, , если вы определите @OrderColumn (чтобы указать столбец, используемый для поддержания постоянного порядка списка - что будет иметь смысл, поскольку вы используете List), Hibernate будет создайте первичный ключ (состоящий из столбца заказа и столбца соединения ) и сможете обновить таблицу сбора без удаления всего содержимого.

Примерно так (если вы хотите использовать имя столбца по умолчанию):

@Entity
public class Person {
    ...
    @ElementCollection
    @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
    @OrderColumn
    private List<Location> locations;
    ...
}

Ссылки

7 голосов
/ 30 апреля 2015

В дополнение к ответу Паскаля, вам также необходимо установить хотя бы один столбец как NOT NULL :

@Embeddable
public class Location {

    @Column(name = "path", nullable = false)
    private String path;

    @Column(name = "parent", nullable = false)
    private String parent;

    public Location() {
    }

    public Location(String path, String parent) {
        this.path = path;
        this.parent= parent;
    }

    public String getPath() {
        return path;
    }

    public String getParent() {
        return parent;
    }
}

Это требование задокументировано в AbstractPersistentCollection :

Обходной путь для таких ситуаций, как HHH-7072.Если элемент коллекции является компонентом, который полностью состоит из обнуляемых свойств, в настоящее время нам необходимо принудительно воссоздать всю коллекцию.Посмотрите использование hasNotNullableColumns в конструкторе AbstractCollectionPersister для получения дополнительной информации.Чтобы удалить строку за строкой, для этого потребуется SQL, например «WHERE (COL =? OR (COL равен нулю, а? Равен нулю))», а не текущий «WHERE COL =?»(сбой для нуля для большинства БД).Обратите внимание, что параметр должен быть связан дважды.До тех пор, пока мы в конечном итоге не добавим понятия «точки привязки параметров» в AST в ORM 5+, обработка этого типа условия является либо чрезвычайно сложной, либо невозможной.Принудительный отдых не идеален, но в ORM 4 не является никаким другим вариантом.

2 голосов
/ 30 октября 2018

Мы обнаружили, что сущности, которые мы определяли как типы ElementCollection, не имели определенного метода equals или hashcode и имели пустые поля.Мы предоставили их (через @lombok, что это стоит) для типа сущности, и это позволило hibernate (v 5.2.14) определить, что коллекция была или не была грязной.

Кроме того, эта ошибка возникла у наспотому что мы были в методе обслуживания, который был помечен аннотацией @Transaction(readonly = true).Поскольку hibernate будет пытаться очистить коллекцию связанных элементов и вставить ее заново, транзакция завершится неудачно при сбросе, и что-то не получится с этим очень трудным для отслеживания сообщением:

HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]

Вот пример нашей модели сущностей, в которой произошла ошибка

@Entity
public class Entity1 {
@ElementCollection @Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}

public class Entity2 {
  private UUID someUUID;
}

Изменение ее на эту

@Entity
public class Entity1 {
@ElementCollection @Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}

@EqualsAndHashCode
public class Entity2 {
  @Column(nullable = false)
  private UUID someUUID;
}

Исправлена ​​наша проблема.Удачи.

0 голосов
/ 24 января 2019

У меня была та же проблема, но я хотел отобразить список перечислений: List<EnumType>.

Я получил это, работая так:

@ElementCollection
@CollectionTable(
        name = "enum_table",
        joinColumns = @JoinColumn(name = "some_id")
)
@OrderColumn
@Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();

public void setEnumList(List<EnumType> newEnumList) {
    this.enumTypeList.clear();
    this.enumTypeList.addAll(newEnumList);
}

Проблема со мной заключалась в том, чтоList объект всегда заменялся с помощью установщика по умолчанию, и поэтому hibernate рассматривал его как полностью «новый» объект, хотя перечисления не изменились.

...