I have a collection in my model that contains a set of \'previous versions\' of my root domain object. The previous versions are therefore \'immutable\' and we will never wa
When you have a List or Set-based collection and you add a new object into your collection, Hibernate will always hit the database because it compare one by one object by using equals implementation before saving or updating - when using a Set - or by comparing a index column when using a List. This behavior is needed because of the Set and List semantic. Because of that, the performance of your application can decrease significantly whether you have a bunch of records.
Some workaround to overcome this issue
1º Conversion pattern by using a encapsuled Bag collection plus your desired Set or List exposed as a property
@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º Use ManyToOne instead of 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º Caching - when applied because of, depending on your requirements, your configuration can increase or decrease the performance of your application. See here
4° SQL constraint - If you want a collection that behaves like a Set, you can use a SQL constraint, which can be applied to a column or set of columns. See here
The way I typically do this is to define the collection as "inverse".
That roughly means: the primary definition of the 1-N association is done at the "N" end. Tf you want to add something to the collection you alter the associated object of the detail data.
A small XML example:
<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>
then the update is done exclusively in Address:
Address a = ...;
a.setPerson(me);
a.setStreet("abc");
Session s = ...;
s.save(a);
Done. You did not even touch the collection. Consider it read-only, which may be very practical for querying with HQL, and iterating and displaying it.
If I understand well your need:
Did you consider maintaining your collection disconnected?
I used to create a native query for this cases, like below:
@Modifying
@Transactional
@Query(nativeQuery = true, value = "INSERT INTO categorytopic_topic(category_id, topic_id) VALUES (?1, ?2)")
public void addTopic(long categoryId, long topicId);
It's very fast because does not need load the collection.