Подтвердить что ты не робот

Hibernate - как сохранить новый элемент в коллекции без загрузки всей коллекции

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Образец конверсии с использованием коллекции с суммированной сумкой плюс ваш желаемый набор или список, выставленный как свойство

@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. Если вам нужна коллекция, которая ведет себя как Set, вы можете использовать ограничение SQL, которое может быть применено к столбцу или набору столбцов. См. здесь

Ответ 2

Как обычно я это делаю, нужно определить коллекцию как "обратную".

Это примерно означает: первичное определение ассоциации 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, итерации и отображения его.

Ответ 3

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

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

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

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

Ответ 4

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

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

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