Hibernate一对多关系出现问题

4

我正在尝试在Java应用程序中实现Hibernate持久化层,但是遇到了一些问题。每次尝试访问简单的单向关联时都会遇到无代理错误。我没有以正确的方式实现Hibernate - 我使用Session控制的线程方法,他们建议您不要在生产中使用。然而,在他们的教程中使用它。我仍然在努力使基础知识工作,所以我认为遵循教程就可以了。然而,我无法使简单的关联工作。我有一个类看起来像这样:

public class Foo {
    private Long id;
    private String name;
    private Bar bar;

    // Static Handler Methods - I'll flesh these out further down in the question.
    public static List getAllFoo();


    // Convenience methods
    public String getBarName() {
        return bar.getName();
    }

    // Accessor methods
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Bar getBar() {
        return bar;
    }

    public void setBar(Bar bar) {
        this.bar = bar;
    }
}

Foo的Bar类被实现为一个简单的单向关联。它在.hbm.xml文件中像这样:

<many-to-one name="bar" column="bar_id" not-null="true" />

我在Foo类的静态方法中创建了Foo对象,代码如下:

public static List getAllFoo() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Transaction t = session.beginTransaction();
    List foos = session.createCriteria(Foo.class).list();
    t.commit();
    return foos;
}

这个 Hibernate 实例被配置为使用连接池 1,并使用线程方式来处理会话,如下所示:

<property name="connection.pool_size">1</property>
<property name="current_session_context_class">thread</property>

每次尝试使用getBarName()函数访问关联对象时,我都会遇到一个异常。这是一个代理错误,看起来像这样:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:132)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:174)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at Bar_$$_javassist_7.getName(Bar_$$_javassist_7.java)
    at Foo.getBarName(Bar.java:126)

简而言之,我最初遇到的错误是这样的。自那时以来,我花了几天时间阅读 Hibernate 文档和这里的帖子,试图弄清楚如何使其工作。我学到的是,Hibernate 对象需要连接到会话中,即它们所创建的会话。在创建对象时,在 getAllFoo() 中,那些对象与该会话相关联。在该会话中,Bar 代理存在并有意义。但是,当我调用 t.commit() 时,因为我使用的是 Session 处理的线程方法,我正在结束该会话。这样做的结果是,当我稍后调用 bar.getName() 时,bar 现在是一个孤立的代理。它是一个代理,其会话已关闭。我找到了两个可能的解决方案: 1)不要关闭初始会话。通过不在 getAllFoo() 中调用 t.commit() 来保持其打开状态。 2)在调用关联之前将对象重新附加到新的会话中。
public Bar getBar() {
    Bar tmp = null;
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Transaction t = session.beginTransaction();
    session.load(this, this.id);
    tmp = bar;
    t.commit();
    return tmp;
}

我随后修改了getBarName(),让它调用getBar()而不是访问bar。这导致出现了一个新的错误:

org.hibernate.PersistentObjectException: attempted to load into an instance that was already associated with the session: [Foo#1]

我猜即使阅读了教程、文章和Hibernate文档数天,我仍然无法理解这个结构。所以我向StackOverflow求助。

首先,如果你能够弄清楚,那么当前代码中发生了什么? 会话是打开还是关闭的?为什么第一个方法有代理错误,但第二个方法声称会话仍然打开且对象仍然附加?

其次,处理会话的正确方法是什么 - 我该如何实现它? 我认为我想要使用的方法是每个请求一个会话 - 这似乎是最流行的并适用于我的情况。但是,如何在关联和延迟加载方面实际实现呢?

是的,我知道我不应该使用线程方法 - 但我还有一堆与JTA vs Thread vs Managed相关的问题,而这个问题已经太长了。

2个回答

1

我认为Hibernate默认采用延迟初始化。因此,当您在会话中加载Foos列表时,在尝试使用它们之前,它们关联的Bars都没有被加载。到那个时候,您已经关闭了会话,导致错误。

一些潜在的解决方案:

  • 在Foo-Bar关联上启用急切获取
  • 在获取时进行“联接”(有效地是急切获取)
  • 尝试在会话内访问Bar(这将起作用,但您只是为了加载相关的Bar对象而调用Foo.getBar())

评论中的更新:

会话/事务管理的惯用语:

Session sess = factory.openSession();
Transaction tx;
  try {
    tx = sess.beginTransaction();
    //do some work
    ...
    tx.commit();
  }
  catch (Exception e) {
    if (tx!=null) tx.rollback();
      throw e;
  }
  finally {
    sess.close();
  }

重新连接“分离”的对象(基于参考文档):
如果对象已被修改:在新会话中更新对象。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);

// in a higher layer of the application
cat.setMate(potentialMate);

// later, in a new session
secondSession.update(cat);  // update cat
secondSession.update(mate); // update mate

如果对象未被修改,可以使用lock():

//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);

这不是在某种程度上违背了延迟加载的整个意义吗?我认为延迟加载的目的是在需要数据时才加载它。如果我使用急切获取或任何相关方法,我将把整个数据库加载到内存中。我认为Hibernate的一部分是尝试避免这种情况...难道没有办法让延迟加载和每个请求一个会话(session-per-request)很好地协作吗? - Daniel Bingham
True。为了保持延迟加载,您可以将Foo实例重新附加到另一个Hibernate会话,然后它便能够加载关联对象。但是,我发现这对于客户端代码来说似乎不是一件轻松的操作。他们必须记住已经加载了哪些内容以及哪些没有... - marklai
关闭会话的正确方式是什么?将对象重新附加到新创建的会话的正确方式又是什么? - Daniel Bingham
更新了答案,并附上了一些参考文档中的示例。同样,客户端代码不能简单地执行[Foo foo = getFoo()],然后尝试[Foo foo = getFoo()],它需要依赖于像[Bar getBar(Foo f)]这样的dao方法,在新会话中获取Bar。 - marklai

0
  1. 异常是由于对象未从其他会话中分离而引起的。建议您关闭另一个会话。

  2. 处理问题的首选方法是保持会话活动,直到您完成工作为止。阅读此处了解详情


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接