【问题标题】:Lazy loading property and session.get problem延迟加载属性和session.get问题
【发布时间】:2010-09-08 21:08:48
【问题描述】:

在 Hibernate 中,我们有两个类,其中包含以下具有 JPA 映射的类:

package com.example.hibernate

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Foo {
  private long id;
  private Bar bar;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public long getId() {
    return id;
  }

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

  @ManyToOne(fetch = FetchType.LAZY)
  public Bar getBar() {
    return bar;
  }

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

package com.example.hibernate

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


public class Bar {
  private long id;
  private String title;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public long getId() {
    return id;
  }

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


  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  } 
}

现在,当我们使用 session get 从数据库加载类 Foo 的对象时,例如:

Foo foo = (Foo)session.get(Foo.class, 1 /* or some other id that exists in the DB*/); foo 的 Bar 成员是一个代理对象(在我们的例子中是 javassist 代理,但它可以是 cglib 之一,具体取决于您使用的字节码提供程序),它没有被初始化。 如果您随后使用 session.get 获取 Bar 对象,该对象是刚刚加载的 Foo 类的成员(我们在同一个会话中),Hibernate 不会发出另一个 DB 查询并从会话(第一级)缓存中获取对象.问题是这是未初始化的 Bar 类的代理,尝试调用此对象 getId() 将返回 0,而 getTitle() 将返回 null。 我们当前的解决方案非常丑陋,并检查从 get 返回的对象是否是代理这里是代码(形成一个通用的 DAO 实现):

@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true)
public <T extends IEntity> T get(Class<T> clazz, Serializable primaryKey) throws DataAccessException {
  T entity = (T) currentSession().get(clazz, primaryKey);
  if (entity != null) {
    if (LOG.isWarnEnabled()) {
      LOG.warn("Object not found for class " + clazz.getName() + " with primary key " + primaryKey);
    }
  } else if (entity instanceof HibernateProxy){ // TODO: force initialization due to Hibernate bug
    HibernateProxy proxy = (HibernateProxy)entity;
    if (!Hibernate.isInitialized(proxy)) {
      Hibernate.initialize(proxy);
    }
    entity = (T)proxy.getHibernateLazyInitializer().getImplementation();
  }
  return entity;
}

有没有更好的方法可以做到这一点,在Hibernate论坛中找不到解决方案,在Hibernate的JIRA中也没有找到问题。

注意:我们不能只使用 foo.getBar() (它将正确初始化代理)来获取 Bar 类对象,因为获取 Bar 对象的 session.get 操作不知道(或关心此事) Bar 类也是刚刚获取的 Foo 对象的惰性成员。

【问题讨论】:

    标签: java hibernate lazy-loading


    【解决方案1】:

    我也遇到过类似的问题:

    • 我使用 Session.save(nastyItem) 将对象保存到 Session 中。 但是,我没有填写映射为 update="false" insert="false" 的购房者(当您有一个组合主键时,这种情况经常发生,然后您将多对一映射为 insert="假”更新=“假”)
    • 我是一个加载项目列表的查询,而我刚刚保存的项目恰好是结果集的一部分
    • 现在出了什么问题? Hibernate 看到该项目已经在缓存中,并且 Hibernate 不会用新加载的值替换它(可能不会破坏我之前的引用 nastyItem),而是使用我自己放入 Session 缓存中的 MY nastyItem。更糟糕的是,现在买家的延迟加载被破坏了:它包含 null。

    为了避免这些 Session 问题,我总是在保存、合并、更新或删除后进行刷新和清除。必须解决这些讨厌的问题占用了我太多时间。

    【讨论】:

      【解决方案2】:

      没有真正看到这个问题,虽然我们确实遇到了间歇性延迟加载错误 - 所以也许我们有同样的问题,无论如何,是否可以选择使用不同的会话来加载 Bar 对象 - 应该从从头开始,我希望......

      【讨论】:

      • 在另一个会话中加载不是一种选择:真正的问题涉及更多,但简短的回答是我们不能。
      【解决方案3】:

      我无法重现您所看到的行为。这是我的代码:

      @Entity
      public class Foo {
          private Long id; private String name; private Bar bar;
      
          public Foo() { }
          public Foo(String name) { this.name = name; }
      
          @Id
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() { return id; }
          public void setId(Long id) { this.id = id; }
      
          @Basic
          public String getName() { return name; }
          public void setName(String name) { this.name = name; }
      
          @ManyToOne(fetch = FetchType.LAZY)
          public Bar getBar() { return bar; }
          public void setBar(Bar bar) { this.bar = bar; }
      }
      
      @Entity
      public class Bar {
          private Long id; private String name;
      
          public Bar() { }
          public Bar(String name) { this.name = name; }
      
          @Id
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() { return id; }
          public void setId(Long id) { this.id = id; }
      
          @Basic
          public String getName() { return name; }
          public void setName(String name) { this.name = name; }
      }
      
          public void testGets() {
              SessionFactory sf = new AnnotationConfiguration()
                  .addPackage("hibtest")
                      .addAnnotatedClass(Foo.class)
                      .addAnnotatedClass(Bar.class)
                  .configure().buildSessionFactory();
              Session session = null;
              Transaction txn = null;
      
              // Create needed data
              try {
                  session = sf.openSession();
                  txn = session.beginTransaction();
      
                  // Create a Bar
                  Bar bar = new Bar("Test Bar");
                  session.save(bar);
      
                  // Create a Foo
                  Foo foo = new Foo("Test Foo");
                  session.save(foo);
      
                  foo.setBar(bar);
      
                  txn.commit();
              } catch (HibernateException ex) {
                  if (txn != null) txn.rollback();
                  throw ex;
              } finally {
                  if (session != null) session.close();
              }
      
              // Try the fetch
              try {
                  session = sf.openSession();
                  Foo foo = (Foo) session.get(Foo.class, 1L);
                  Bar bar = (Bar) session.get(Bar.class, 1L);
                  System.out.println(bar.getName());
              } finally {
                  if (session != null) session.close();
              }
          }
      

      一切正常,正如人们所期望的那样。

      【讨论】:

        【解决方案4】:

        你真的需要延迟加载吗?
        您是否可以不将 FetchType 设置为 EAGER 并始终使用连接(正确)加载它?

        【讨论】:

        • 我确实需要延迟加载,因为并不总是需要这个属性(例如 Foo 对象的列表视图)
        • 我倾向于不使用延迟加载,因此对于有时需要附加属性而有时不需要的情况,我倾向于编写两种不同的方法 - 一种进行连接,另一种不需要.然后,它使您有机会调整每个查询以完全按照您的意愿行事。
        【解决方案5】:

        你做错了什么。我没有测试你的代码,但你永远不需要强制初始化代理,属性访问器会为你做这件事。如果您明确使用 Hibernate,请不要介意使用 JPA,因为您已经失去了可移植性。

        Hibernate 应该在需要获取或写入数据库时​​自动检测。如果您从代理发出 getProperty(),hibernate 或任何其他 jpa 提供程序应该从 db 中获取对应的行。

        我不确定hibernate是否足够聪明的唯一情况是,如果你发出一个save(),然后发出一个带有保存对象ID的get(),可能会出现问题如果 save() 没有将对象刷新到 db。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-12-28
          • 1970-01-01
          • 2021-04-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-03
          相关资源
          最近更新 更多