2010-04-07 63 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) 單值的關聯只是不適合我順利