Hibernate - как сохранить новый элемент в коллекции без загрузки всей коллекции
У меня есть коллекция в моей модели, которая содержит набор "предыдущих версий" моего объекта корневого домена. Поэтому предыдущие версии "неизменяемы", и мы никогда не захотим их обновлять и хотим добавлять предыдущие версии по мере их возникновения. Кроме того, доменный объект с версией является довольно сложным и приводит к большому доступу к базе данных.
Когда у меня есть новая версия одного из этих объектов, я хочу сохранить ее вместе с остальными без загрузки всего набора. В расширенном FAQ есть несколько советов по этому поводу:
Почему Hibernate всегда инициализирует коллекцию, когда я хочу только добавить или удалить элемент?
К сожалению, API коллекций определяет возвращаемые значения метода, которые могут быть вычислены только путем попадания в базу данных. Есть три исключения из этого: Hibernate может добавить к <bag>
, <idbag>
или <list>
, объявленным с помощью inverse="true"
, без инициализации коллекции; возвращаемое значение всегда должно быть истинным.
Если вы хотите избежать дополнительного трафика базы данных (т.е. в критическом критически важном коде), реорганизуйте свою модель для использования только ассоциаций "один-к-одному". Это почти всегда возможно. Затем используйте запросы вместо доступа к коллекции.
Я новичок во всем этом, и я не уверен на 100% о том, как реорганизовать вашу модель для использования только ассоциаций "один-на-один". Может ли кто-нибудь, пожалуйста, привести пример моего учебника, чтобы я мог узнать, как это решит мою проблему?
Ответы
Ответ 1
Когда у вас есть коллекция List или Set-based, и вы добавляете новый объект в свою коллекцию, Hibernate всегда попадает в базу данных, потому что он сравнивает один за другим объект, используя реализацию equals перед сохранением или обновлением - при использовании Set - или путем сравнения столбца индекса при использовании списка. Такое поведение необходимо из-за семантики Set и List. Из-за этого производительность вашего приложения может значительно уменьшиться, есть ли у вас куча записей.
Некоторое обходное решение для решения этой проблемы
1º Образец конверсии с использованием коллекции с суммированной сумкой плюс ваш желаемый набор или список, выставленный как свойство
@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; }
}
2º Используйте 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 }
}
3º Кэширование - при применении из-за того, что в зависимости от ваших требований ваша конфигурация может увеличить или уменьшить производительность вашего приложения. См. здесь
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);
Это очень быстро, потому что не нужно загружать коллекцию.