延迟加载属性和session.get问题
在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; } }
现在,当我们使用会话从数据库中加载类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实现):
@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对象的惰性成员。
解决方案
回答
虽然我们确实会遇到间歇性的延迟加载错误,但实际上并没有遇到这个问题,所以无论如何,也许我们都遇到了同样的问题,是否可以选择使用不同的会话来加载应该从头开始加载的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设置为EAGER并始终使用联接始终(正确)加载它?
回答
我有一个类似的问题:
- 我做了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()没有刷新对象到数据库。