我的模型中有一个集合,其中包含一组我的根域对象的“先前版本”。因此,以前的版本是“不可变的”,我们永远不会想要更新它们,只希望在它们出现时添加过去的版本。 “版本化”的域对象也相当复杂,导致需要大量数据库访问才能检索。
当我有其中一个对象的新版本时,我想将其与其他对象保存,而不加载整个集合。高级常见问题解答有一些建议:
为什么Hibernate总是在我只想添加或删除元素时初始化一个集合?
不幸的是,集合API定义了方法返回值,这些值只能通过点击数据库来计算。这有三个例外:Hibernate可以添加到
<bag>
,<idbag>
或<list>
与inverse="true"
一起声明而不初始化集合;返回值必须始终为真。如果您想避免额外的数据库流量(即在性能关键代码中),请重构您的模型以仅使用多对一关联。这几乎总是可行的。然后使用查询代替集合访问。
我是所有这一切的新手,并且我不能100%确定如何重构模型以仅使用多对一关联。任何人都可以给我一个示例,指出我的教程,以便我可以了解这将如何解决我的问题?
答案 0 :(得分:11)
当你有一个List或基于Set的集合并且你在你的集合中添加一个新对象时,Hibernate将始终命中数据库,因为它在保存或更新之前使用equals实现比较逐个对象 - 当使用Set时 - 或者在使用List时比较索引列。由于Set和List语义,因此需要此行为。因此,无论您拥有大量记录,您的应用程序的性能都会显着降低。
解决此问题的一些解决方法
1º使用封装的Bag集合加上您所需的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; }
}
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º缓存 - 应用时,根据您的要求,您的配置可能会增加或降低应用程序的性能。见here
4° SQL约束 - 如果您希望集合的行为类似于Set,则可以使用SQL约束,该约束可应用于列或列集。见here
答案 1 :(得分:9)
我通常这样做的方法是将集合定义为“逆”。
大致意味着: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 :(得分:2)
如果我理解你的需要:
您是否考虑维护您的收藏集?
答案 3 :(得分:0)
我曾经为这种情况创建一个本机查询,如下所示:
@Modifying
@Transactional
@Query(nativeQuery = true, value = "INSERT INTO categorytopic_topic(category_id, topic_id) VALUES (?1, ?2)")
public void addTopic(long categoryId, long topicId);
这非常快,因为不需要加载集合。
答案 4 :(得分:0)
这将最小化所选数据
@LazyCollection(LazyCollectionOption.EXTRA)
但是在 foreach 迭代这个集合时要小心 N+1 个问题。