Hibernate - 如何在不加载整个Collection的情况下持久保存Collection中的新项

时间:2009-09-11 07:42:33

标签: hibernate refactoring

我的模型中有一个集合,其中包含一组我的根域对象的“先前版本”。因此,以前的版本是“不可变的”,我们永远不会想要更新它们,只希望在它们出现时添加过去的版本。 “版本化”的域对象也相当复杂,导致需要大量数据库访问才能检索。

当我有其中一个对象的新版本时,我想将其与其他对象保存,而不加载整个集合。高级常见问题解答有一些建议:

  

为什么Hibernate总是在我只想添加或删除元素时初始化一个集合?

     

不幸的是,集合API定义了方法返回值,这些值只能通过点击数据库来计算。这有三个例外:Hibernate可以添加到<bag><idbag><list>inverse="true"一起声明而不初始化集合;返回值必须始终为真。

     

如果您想避免额外的数据库流量(即在性能关键代码中),请重构您的模型以仅使用多对一关联。这几乎总是可行的。然后使用查询代替集合访问。

我是所有这一切的新手,并且我不能100%确定如何重构模型以仅使用多对一关联。任何人都可以给我一个示例,指出我的教程,以便我可以了解这将如何解决我的问题?

5 个答案:

答案 0 :(得分:11)

当你有一个List或基于Set的集合并且你在你的集合中添加一个新对象时,Hibernate将始终命中数据库,因为它在保存或更新之前使用equals实现比较逐个对象 - 当使用Set时 - 或者在使用List时比较索引列。由于Set和List语义,因此需要此行为。因此,无论您拥有大量记录,您的应用程序的性能都会显着降低。

解决此问题的一些解决方法

使用封装的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; }

}

使用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 }

}

缓存 - 应用时,根据您的要求,您的配置可能会增加或降低应用程序的性能。见here

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 个问题。