Hibernate - Как сохранить новый элемент в коллекции без загрузки всей коллекции - PullRequest
20 голосов
/ 11 сентября 2009

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

Когда у меня есть новая версия одного из этих объектов, я хочу сохранить его вместе с другими без загрузки всего набора. Расширенный FAQ имеет несколько советов по этому поводу:

Почему Hibernate всегда инициализирует коллекцию, когда я только хочу добавить или удалить элемент?

К сожалению, API коллекций определяет возвращаемые значения метода, которые могут быть вычислены только при попадании в базу данных. Есть три исключения из этого: Hibernate может добавлять к <bag>, <idbag> или <list>, объявленным с inverse="true", без инициализации коллекции; возвращаемое значение всегда должно быть истинным.

Если вы хотите избежать дополнительного трафика базы данных (т. Е. В критичном для производительности коде), реорганизуйте свою модель, чтобы использовать только связи «многие к одному». Это почти всегда возможно. Затем используйте запросы вместо доступа к коллекции.

Я новичок во всем этом и не уверен на 100% в том, как реорганизовать вашу модель, чтобы использовать только ассоциации "многие к одному". Может ли кто-нибудь дать мне пример указания на учебник, чтобы я мог узнать, как это решит мою проблему?

Ответы [ 4 ]

11 голосов
/ 11 сентября 2009

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

Некоторое решение для преодоления этой проблемы

Шаблон преобразования с использованием инкапсулированной коллекции Bag плюс желаемый набор или список, представленный в виде свойства

@Entity
public class One {

    private Collection<Many> manyCollection = new ArrayList<Many>();

    @Transient
    public Set<Many> getManyCollectionAsSet() { return new HashSet<Many>(manyCollection); }
    public void setManyCollectionAsSet(Set<Many> manySet) { manyCollection = new ArrayList<Many>(manySet); }

    /**
      * Keep in mind that, unlike Hibernate, JPA specification does not allow private visibility. You should use public or protected instead
      */
    @OneToMany(cascade=ALL)
    private Collection<Many> getManyCollection() { return manyCollection; }
    private void setManyCollection(Collection<Many> manyCollection) { this.manyCollection = manyCollection; }

}

Использовать ManyToOne вместо OneToMany

@Entity
public class One {

    /**
      * Neither cascade nor reference
      */

}

@Entity
public class Many {

    private One one;

    @ManyToOne(cascade=ALL)
    public One getOne() { return one; }
    public void setOne(One one) { this.one = one }

}

Кэширование - при применении из-за того, что, в зависимости от ваших требований, ваша конфигурация может увеличивать или уменьшать производительность вашего приложения. Смотри здесь

4 ° Ограничение SQL - если вам нужна коллекция, которая ведет себя как набор, вы можете использовать ограничение SQL, которое можно применить к столбцу или множеству столбцов . Смотрите здесь

9 голосов
/ 11 сентября 2009

Обычно я делаю это, чтобы определить коллекцию как «обратную».

Это примерно означает: первичное определение ассоциации 1-N делается на конце "N". Если вы хотите что-то добавить в коллекцию, вы изменяете связанный объект подробных данных.

Небольшой пример XML:

<class name="common.hibernate.Person" table="person">
    <id name="id" type="long" column="PERSON_ID">
        <generator class="assigned"/>
    </id>
    <property name="name"/>
    <bag name="addresses" inverse="true">
        <key column="PERSON_ID"/>
        <one-to-many class="common.hibernate.Address"/>
    </bag>
 </class>

 <class name="common.hibernate.Address" table="ADDRESS">
    <id name="id" column="ADDRESS_ID"/>
    <property name="street"/>
    <many-to-one name="person" column="PERSON_ID"/>
   </class>

тогда обновление производится исключительно по адресу:

Address a = ...;
a.setPerson(me);
a.setStreet("abc");
Session s = ...;
s.save(a);

Готово. Вы даже не трогали коллекцию. Считайте, что это только для чтения, что может быть очень полезно для запросов с HQL, итерации и отображения.

2 голосов
/ 11 сентября 2009

Если я хорошо понимаю ваши потребности:

  • у вас есть коллекция неизменяемых объектов, доступная только в памяти
  • как добавить элемент в эту коллекцию без инициализации всей коллекции (в текущем сеансе)?

Рассматривали ли вы сохранение вашей коллекции отключенной ?

  • Загрузите его один раз при запуске приложения
  • Добавление элемента означало бы две операции, выполняемые одним сервисом только в одном месте:
    • сохранить элемент (быстрая работа с базой данных, каскадирование к родительскому элементу не производится)
    • добавить элемент в существующую отключенную коллекцию (без операции базы данных)
0 голосов
/ 26 июня 2019

Я использовал для создания собственного запроса для этих случаев, как показано ниже:

    @Modifying
    @Transactional
    @Query(nativeQuery = true, value = "INSERT INTO categorytopic_topic(category_id, topic_id) VALUES (?1, ?2)")
    public void addTopic(long categoryId, long topicId);

Это очень быстро, потому что не нужно загружать коллекцию.

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