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

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

Lazy loading property and session.get problem

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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;
  }
}

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

Foo foo =(Foo)session.get(Foo.class,1 / *或数据库中存在的其他ID * /);
foo的Bar成员是一个未初始化的代理对象(在我们的示例中为javassist代理,但它可以是cglib,具体取决于您使用的字节码提供程序)。
如果您随后使用session.get来获取作为刚刚加载的Foo类成员的Bar对象(我们在同一会话中),则Hibernate不会发出另一个数据库查询,而是从会话(第一级)缓存中获取该对象。 。问题是这是未初始化的Bar类的代理,尝试调用此对象的getId()将返回0,而getTitle()将返回null。
我们当前的解决方案很丑陋,并检查从get返回的对象是否是代理,这里是代码(形成通用DAO实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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对象的惰性成员。


我有一个类似的问题:

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

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


您做错了。我没有测试您的代码,但是您永远不需要强制代理的初始化,属性访问器会为您完成。如果您显式使用Hibernate,则不必担心使用JPA,因为您已经失去了可移植性。

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

我不确定休眠是否足够聪明的唯一情况是,如果您发出save(),然后发出带有已保存对象ID的get(),则可能会出现问题,如果save()没有刷新对象到数据库。


您实际上是否需要进行延迟加载?
您是否可以不将FetchType设置为EAGER,并且始终使用联接(正确)加载它?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@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();
        }
    }

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


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


推荐阅读