Hibernate Mapping问题与不相关的集合

时间:2010-08-10 17:45:00

标签: hibernate collections mapping

欢迎,

我对Hibernate映射有一些问题。

数据库结构:

TableA
 -ID_A --PK

TableB
 -ID_B --PK
 -ID_A -- FK -> TableA

TableC
 -ID_C -- PK
 -ID_A -- FK -> TableA

POJO结构:

class TableA extends Pojo {

 /*Some Fields*/

}

class TableB extends Pojo {

  TableA tableA; 

 /*Some properties*/

}

class TableC extends Pojo {

 TableA tableA;

 Collection<tableB> tableBs;

}

我想要的是TableC Pojo映射中TableB元素的集合,映射键是tableA。

此集合应该是只读的。

映射应该是hbm而不是注释。

我可能已经为每一种可能的方式做到了...我得到的结果就是当我操作一个TableC对象然后一切都正确但是如果我加载它们的集合然后只有最后一个具有适当的集合集。

更新:案例描述。

用例1:加载TableC的单个对象

Session session = (Session) getHibernateTemplate().getSessionFactory().openSession();
SQLQuery sqlQuery = session.createSQLQuery("SELECT c.* FROM TableC c WHERE c.ID_C = 1"); //Oracle
  sqlQuery.addEntity("c", TableC.class);
return sqlQuery.list(); //Return list with sigle object of TableC

在这种情况下,一切正常。该对象加载了TableB对象列表中的所有数据和适当的项。 在这个对象上,我们可以操作,更改它并更新数据库中的修改。

用例2加载对象集合

Session session = (Session) getHibernateTemplate().getSessionFactory().openSession();
SQLQuery sqlQuery = session.createSQLQuery("SELECT c.* FROM TableC c WHERE c.ID_C in (1,2)"); //Oracle
  sqlQuery.addEntity("c", TableC.class);
return sqlQuery.list(); // throws "collection is not associated with any session" 

在这种情况下,Hibernate在检索下一个对象时抛出异常。

*代码只是样本,会话毕竟是关闭的。

4 个答案:

答案 0 :(得分:9)

经过一些研究,问题出现在Hibernate中,它被称为bug #HHH-2862

  

这主要是由于收集集合中的密钥在结果中不唯一而引起的。

当Hibernate使用'persistenceContext.addUninitializedCollection()'初始化集合时,这将检测到已经添加了具有给定键的集合,然后将旧实例设置为null并将当前设置为集合。但是,该集合已经通过较早的调用添加到持久性上下文中,并且当StatefulPersistenceContext.initializeNonLazyCollections()遍历持久化上下文中的所有集合时,对引用的调用forceInitialization()命中了引用的null引用“集合与任何会话”异常“都没有关联。这就解释了为什么在我的情况下只有最后一个对象有引用,只有一个一切正常,你的假设是关于延迟初始化问题。

有些想法如何绕过这个错误?

答案 1 :(得分:5)

当你有一个引用非主键的外键时,你应该使用property-ref Attribute

其文档

  

加入此外键的关联类的属性名称

因为您的Collection 是不可变的,您应该将mutable属性设置为false

这是您的映射

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping 
          PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--Set up your package right here-->
<hibernate-mapping package="br.com.ar">
     <class name="Aa" table="A">
         <id name="id">
             <generator class="native"/>
         </id>
    </class>
    <class name="Bb" table="B">
         <id name="id">
             <generator class="native"/>
         </id>
         <many-to-one name="aa" column="A_ID" class="Aa"/>
    </class>
    <class name="Cc" table="C">
         <id name="id">
             <generator class="native"/>
         </id>
         <many-to-one name="aa" column="A_ID" class="Aa"/>
         <bag name="bbList" table="B" mutable="false">
             <key column="A_ID" property-ref="aa"/>
             <one-to-many class="Bb"/>
         </bag>
    </class>
</hibernate-mapping>

每个课程描述如下

<强> br.com.ar.Aa

public class Aa {

    private Integer id;

    // getter's and setter's

}

<强> br.com.ar.Bb

public class Bb {

    private Integer id;

    private Aa aa;

    // getter's and setter's

}

<强> br.com.ar.Cc

public class Cc {

    private Integer id;

    private Aa aa;

    private Collection<Bb> bbList = new ArrayList<Bb>();

    // getter's and setter's

}

已添加为替代方法

好吧,让我们看看第一个用例

query = new StringBuilder().append("SELECT ")
                               .append("{cc.*} ")
                           .append("from ")
                               .append("C cc ")
                           .append("where ")
                               .append("cc.id = 1 ")
                           .toString();

你说

  

对象加载了所有数据及其Bb相关对象。在这个对象上我们可以操作,更改它并更新数据库中的修改

如您所见,您的 NATIVE SQL只检索Cc对象。 加入。但是你说它检索所有相关的对象,包括bbList。如果是这样,则会发生这种情况,因为您有一个映射(通知fetch =“select”lazy =“false”)

<class name="Cc" table="C">
     ...
     <bag name="bbList" table="B" mutable="false" fetch="select" lazy="false">
         <key column="A_ID" property-ref="aa"/>
         <one-to-many class="Bb"/>
     </bag>
</class>

fetch =“select”使用额外的选择。对于一对多和多对多关系(bbList,右???),fetch属性与lazy属性协同工作,以确定加载相关集合的方式和时间。如果lazy =“true”,则在应用程序访问集合时会加载集合,但如果lazy =“false”,则Hibernate 使用单独的SQL SELECT语句立即加载集合

但是如果运行

query = new StringBuilder().append("SELECT ")
                               .append("{cc.*} ")
                           .append("from ")
                               .append("C cc ")
                           .append("where ")
                               .append("cc.id in (1,2) ")
                           .toString();

我得到了预期的

  

org.hibernate.HibernateException:collection与任何会话

无关

为什么???

你想加入Cc及其相关的bbList通过A_ID列暗示使用property-ref属性,对吧???

<class name="Cc" table="C">
     ...
     <bag name="bbList" table="B" mutable="false" fetch="select" lazy="false">
         <key column="A_ID" property-ref="aa"/>
         <one-to-many class="Bb"/>
     </bag>
</class>

如果您想使用A_ID作为主键,必须是唯一的,就会发生这种情况。因为有很多属性具有相同的值,所以您将获得此异常。第一个用例不会抛出任何异常,因为您刚刚检索到一个实体

解决方法

尝试逐一获取

List<Cc> resultList = new ArrayList<Cc>();
for (Integer id : new Integer[] {1, 2}) {
    query = new StringBuilder().append("SELECT ")
                                   .append("{cc.*} ")
                               .append("from ")
                                   .append("C cc ")
                               .append("where ")
                                   .append("cc.id = :id ")
                               .toString();

    resultList.addAll(
             session.createSQLQuery(query)
                    .addEntity("cc", Cc.class)
                    .setParameter("id", id)
                    .list());
}

或使用普通的JDBC查询

答案 2 :(得分:0)

您可能在发布的代码中犯了错误,但假设它是正确的,但您的TableC类具有TableB的List,而您的TableC数据库表具有对表A的FK引用。

答案 3 :(得分:0)

恕我直言,你没有使用ORM,因为它应该被使用。您的类模型看起来与表结构完全相同。这不一定是这种情况。这样,您就无法从ORM中受益。

所以我建议将B和C的列表放在表A中。然后你可以在任何你想要的地方导航:

class A
{
 IList<B> Bs { get; private set; }
 IList<C> Cs { get; private set; }
}

class B
{
  A A { get; set; }
}

class C
{
  A A { get; set; }
}

你这样映射:

<class name="A">
  <bag name="As" class="A">
    <key column="ID_A"/>
  </bag>
  <bag name="Bs" class="B">
    <key column="ID_A"/>
  </bag>
</class>

<class name="B">
  <many-to-one name="A" class="A" column="ID_A"/>
</class>

<class name="C">
  <many-to-one name="A" class="A" column="ID_A"/>
</class>

然后您可以像这样简单地导航:

B b = session.Get<B>(id);
foreach(C c in b.A.Cs)
{
  // iterate C's
}

HQL查询:

SQLQuery sqlQuery = session.createQuery("FROM TableC c WHERE c.ID_C in (1,2)"); 
  • 我不知道你为什么在这里使用原生SQL。
  • addEntity是将参数添加到类型实体的查询中(实际上将其视为其id)。就我所见,您的查询中没有任何参数。

修改

查询以获得与某个C共享相同A的所有B:

    C c = session.Get<C>(cId);

    SQLQuery sqlQuery = session.createQuery(
@"select B
from B, C
where B.A = C.A
  and C = :c")
      .addEntity("c", c);

或更短:

    C c = session.Get<C>(cId);

    SQLQuery sqlQuery = session.createQuery(
@"select B
from B
where B.A = :a")
      .addEntity("a", c.A);