Java 延迟加载属性和session.get问题

Java 延迟加载属性和session.get问题,java,hibernate,lazy-loading,Java,Hibernate,Lazy Loading,在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; impor

在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/*或数据库中存在的其他id*/); foo的Bar成员是一个未初始化的代理对象(在我们的示例中是javassist代理,但它可以是cglib-one,具体取决于您使用的字节码提供程序)。 如果然后使用session.get获取作为刚加载的Foo类成员的Bar对象(我们在同一个会话中),Hibernate不会发出另一个DB查询,而是从会话(一级)缓存中获取该对象。问题是这是一个未初始化的Bar类的代理,尝试调用此对象getId()将返回0,getTitle()将返回null。 我们当前的解决方案非常难看,它会检查get返回的对象是否是代理。下面是代码(形成通用DAO实现):

@SuppressWarnings(“未选中”)
@凌驾
@事务(只读=真)
public T get(类clazz,Serializable primaryKey)抛出DataAccessException{
T entity=(T)currentSession().get(clazz,primaryKey);
如果(实体!=null){
if(LOG.iswarnabled()){
LOG.warn(“未找到主键为“+primaryKey”的类“+clazz.getName()+”的对象);
}
}else if(HibernateProxy的实体实例){//TODO:由于Hibernate错误而强制初始化
HibernateProxy=(HibernateProxy)实体;
如果(!Hibernate.i初始化(代理)){
初始化(代理);
}
实体=(T)proxy.getHibernateLazyInitializer().getImplementation();
}
返回实体;
}
在Hibernate论坛中找不到解决方案,在Hibernate的JIRA中也找不到问题,有没有更好的方法来解决这个问题


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

没有真正看到这个问题,虽然我们确实会出现间歇性的延迟加载错误-因此也许我们也有同样的问题,不管怎样,是不是可以选择使用不同的会话来加载Bar对象-应该从头开始加载,我希望…

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

@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();
        }
    }

正如人们所期望的那样,一切都很好。

您真的需要进行延迟加载吗?

您能否将FetchType改为“渴望”,并始终使用联接(正确)加载它?

我遇到了类似的问题:

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

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

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

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


我不确定hibernate是否足够聪明的唯一情况是,如果您发出save()命令,然后发出带有已保存对象id的get(),如果save()命令没有将对象刷新到db。

在另一个会话中加载不是一个选项:真正的问题更复杂,但简单的回答是我们不能。我确实需要进行延迟加载,因为并不总是需要此属性(例如,Foo对象的列表视图)我倾向于不使用延迟加载,因此对于有时需要附加属性,有时不需要的情况,我倾向于编写两种不同的方法—一种进行连接,另一种不进行连接。然后,它让您有机会调整每个查询,以完全满足您的需要。
@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();
        }
    }