2010-04-07 62 views
48

我想在不加载整个对象的情况下获取一对一关系的id。我以为我可以如下做到这一点使用延迟加载:休眠一对一:getId()而不提取整个对象

class Foo { 
    @OneToOne(fetch = FetchType.LAZY, optional = false) 
    private Bar bar; 
} 


Foo f = session.get(Foo.class, fooId); // Hibernate fetches Foo 

f.getBar(); // Hibernate fetches full Bar object 

f.getBar().getId(); // No further fetch, returns id 

我想f.getBar()来触发另取。我想让hibernate给我一个代理对象,它允许我调用.getId()而不实际获取Bar对象。

我在做什么错?

+0

相同的行为.. – Rob 2010-04-07 16:14:26

+0

它是一个休眠的bug:https://hibernate.atlassian.net/browse/HHH-3718 另请参阅比较字段或属性访问:http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-更好的领域或财产访问 – GKislin 2016-10-04 21:03:55

回答

29

使用属性访问策略

而不是

@OneToOne(fetch=FetchType.LAZY, optional=false) 
private Bar bar; 

使用

private Bar bar; 

@OneToOne(fetch=FetchType.LAZY, optional=false) 
public Bar getBar() { 
    return this.bar; 
} 

现在它工作正常!

如果您调用任何不是标识符获取方法的方法,则会初始化代理。但它在使用财产访问策略时才起作用。记在心上。

参见:Hibernate 5.2 user guide

+0

这是否意味着我必须更改我的实体以在属性级别具有所有注释以使其正常工作?如果我保持原样并将一个注释移动到一个注解到属性级别并将访问类型设置为属性,则它不起作用 – 2014-07-03 07:01:21

+0

@shane lee请参阅http://docs.jboss.org/ejb3/app-server/HibernateAnnotations/reference/en/html_single /#d0e1955 – 2014-07-04 12:31:08

+1

感谢您的回复。我不信任在我的项目中执行的查询,所以我在我自己的db集成测试中添加了验证。我现在正在工作。唯一的变化是在目标实体的ID上添加访问类型。这是唯一需要的改变。 @Id @GeneratedValue( 策略= GenerationType.SEQUENCE, 发生器= “FILECONTENT_ID_SEQ”) @SequenceGenerator( 名称= “FILECONTENT_ID_SEQ”, sequenceName = “FILECONTENT_ID_SEQ”) @Column( 名称= “ID”, nullable = false) @Access(AccessType.PROPERTY) private Long id; – 2014-07-07 01:01:04

0

您可以使用HQL查询。 getBar()方法将真正返回一个代理,直到调用一些数据绑定方法时才会被取回。我不确定你的问题到底是什么。你能给我们更多的背景吗?

+1

感谢您的答复。你所描述的不是发生了什么。 getBar()会导致提取发生。我希望你所描述的是一个代理对象被返回并且不执行提取。 有没有其他配置可能会丢失? – Rob 2010-04-07 15:58:45

+0

实际上getBar()后面的getId()导致实体被提取。你不会错过IMO的任何配置。也许一些查询,如“从Foo f中选择f.bar.id,其中f.id =?”将为你做诡计。 – 2010-04-07 16:51:38

+0

代理对象不应该在bar.getId()上获取完整的Bar。它已经知道这个ID,因为那是Foo的一部分。 无论如何,它会在不调用的情况下执行提取。getId() – Rob 2010-04-07 17:42:46

22

只需添加到亚瑟·罗纳德FD Garcia'post:你可能会迫使通过@Access(AccessType.PROPERTY)(或弃用@AccessType("property"))属性访问,看到http://256stuff.com/gray/docs/misc/hibernate_lazy_field_access_annotations.shtml

另一种解决方案可能是:

public static Integer getIdDirect(Entity entity) { 
    if (entity instanceof HibernateProxy) { 
     LazyInitializer lazyInitializer = ((HibernateProxy) entity).getHibernateLazyInitializer(); 
     if (lazyInitializer.isUninitialized()) { 
      return (Integer) lazyInitializer.getIdentifier(); 
     } 
    } 
    return entity.getId(); 
} 

也适用于分离的实体。

+1

我已经使用了你的想法,加上代理不能重写最终方法的事实,为了避免初始化,改变'getId()'方法本身。请,如果可以的话,请在本页看到我的回答,并告诉我你的想法。我也不明白你为什么要检查'lazyInitializer.isUninitialized()'。当实体是HibernateProxy时,你不能总是返回'lazyInitializer.getIdentifier()'吗? – MarcG 2015-07-15 20:42:13

+0

我不记得我为什么使用'if(lazyInitializer.isUninitialized())'。也许只有在真正需要的时候才会使用肮脏的技巧我认为它可能被省略。 – xmedeko 2015-07-20 07:43:05

+1

不建议使用Hibernate注释“Acc​​essType”。使用JPA2注释代替'@Access(AccessType.PROPERTY)' – minni 2015-09-30 14:43:54

3

在构造org.hibernate.Session你谁做的工作没有延迟加载的实体功能:

公共序列化则getIdentifier(Object对象)抛出HibernateException的;

发现在休眠3.3.2.GA:

public Serializable getIdentifier(Object object) throws HibernateException { 
     errorIfClosed(); 
     checkTransactionSynchStatus(); 
     if (object instanceof HibernateProxy) { 
      LazyInitializer li = ((HibernateProxy) object).getHibernateLazyInitializer(); 
      if (li.getSession() != this) { 
       throw new TransientObjectException("The proxy was not associated with this session"); 
      } 
      return li.getIdentifier(); 
     } 
     else { 
      EntityEntry entry = persistenceContext.getEntry(object); 
      if (entry == null) { 
       throw new TransientObjectException("The instance was not associated with this session"); 
      } 
      return entry.getId(); 
     } 
    } 
+0

+1。没有Session,它就无法工作,例如为分离的实体。 – xmedeko 2013-11-21 12:11:53

7

Java持久性与Hibernate本书在 “13.1.3了解代理” 提到了这一点:

只要你只能访问数据库标识符属性,没有 代理的初始化是必要的。 (请注意,如果您将标识符属性与直接字段访问进行映射,则这不是真实的 ;因此,休眠 甚至不知道getId()方法存在,如果调用它, 代理必须初始化。)

然而,基于此页面@xmedeko答案,我开发了一个黑客避免使用直接字段访问策略当初始化代理均匀。只需修改如下所示的getId()方法即可。

相反的:

public long getId() { return id; } 

用途:

public final long getId() { 
     if (this instanceof HibernateProxy) { 
      return (long)((HibernateProxy)this).getHibernateLazyInitializer().getIdentifier(); 
     } 
     else { return id; } 
    } 

这里的想法是,以纪念getId()方法final,使代理不能覆盖它。然后,调用该方法无法运行任何代理代码,因此无法初始化代理。该方法本身检查它的实例是否是代理,并且在这种情况下从代理返回id。如果实例是真实对象,则返回该ID。

+0

哈哈这真是一个可怕的破解:)“不要在家里做” – 2017-02-23 18:28:07

+0

@OndraŽižka你错了。此代码完美工作。此外,它没有违反任何规则,没有副作用,并且清楚它在做什么以及为什么。所以,如果你能想出任何不使用此代码的原因或者为什么它是“可怕的”,请分享。 – MarcG 2017-02-23 23:07:58

+0

它可以在没有事先通知的情况下更改Hibernate的内部类。虽然我不怀疑它的功能完美,但我不会将其放入应用程序中,这种应用程序应该持续多年。 – 2017-02-24 15:53:34

0

改变你的getter方法是这样的:

public Bar getBar() { 
    if (bar instanceof HibernateProxy) { 
     HibernateProxy hibernateProxy = (HibernateProxy) this.bar; 
     LazyInitializer lazyInitializer = hibernateProxy.getHibernateLazyInitializer(); 
     if (lazyInitializer.getSession() == null) 
      bar = new Bar((long) lazyInitializer.getIdentifier()); 
    } 

    return bar; 
} 
10

添加@AccessType( “属性”)

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
@AccessType("property") 
protected Long id; 
+10

不建议使用Hibernate注释“Acc​​essType”。改为使用JPA2注释:'@Access(AccessType.PROPERTY)' – minni 2015-09-30 14:46:32

1

现在有一个杰克逊休眠数据类型库的位置:

https://github.com/FasterXML/jackson-datatype-hibernate

而你c一个配置的功能:

Hibernate4Module hibernate4Module = new Hibernate4Module(); 
hibernate4Module.configure(Hibernate4Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true); 

这将包括延迟加载关系 -

5

的ID不幸接受的答案是错的。其他答案也不提供最简单或明确的解决方案。

使用BAR类的ID的属性访问级别。

@Entity 
public class Bar { 

    @Id 
    @Access(AccessType.PROPERTY) 
    private Long id; 

    ... 
} 

就这么简单:)

使用 @ManyToOne(取= FetchType.LAZY,可选= FALSE) 单值的关联只是不适合我顺利