73

I want to fetch the id of a one-to-one relationship without loading the entire object. I thought I could do this using lazy loading as follows:

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

I want f.getBar() to not trigger another fetch. I want hibernate to give me a proxy object that allows me to call .getId() without actually fetching the Bar object.

What am I doing wrong?

Rob
  • 1,355
  • 2
  • 14
  • 17
  • 1
    Same behaviour using @ManyToOne(fetch = FetchType.LAZY, optional = false) Single-valued associations are just not going well for me.. – Rob Apr 07 '10 at 16:14
  • 1
    Its a Hibernate bug: https://hibernate.atlassian.net/browse/HHH-3718 See also comparing field or property access: http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access – Grigory Kislin Oct 04 '16 at 21:03
  • By default, the Proxy is not initialized when only the id is accessed. https://stackoverflow.com/questions/53466913/how-to-avoid-initializing-a-hibernate-proxy-when-only-the-entity-id-is-needed – Imtiaz Shakil Siddique Apr 28 '23 at 19:13

9 Answers9

40

Use property access strategy

Instead of

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

Use

private Bar bar;

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

Now it works fine!

A proxy is initialized if you call any method that is not the identifier getter method. But it just works when using property access strategy. Keep it in mind.

See: Hibernate 5.2 user guide

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
Arthur Ronald
  • 33,349
  • 20
  • 110
  • 136
  • Does that mean I have to change my entity to have all annotations at the property level for this to work? If i leave as is and move the one to one annotation to the property level and set access type to property it does not work – shane lee Jul 03 '14 at 07:01
  • @shane lee See http://docs.jboss.org/ejb3/app-server/HibernateAnnotations/reference/en/html_single/#d0e1955 – Arthur Ronald Jul 04 '14 at 12:31
  • 2
    Thanks for the reply. I didnt trust the query being executed in my project so I added in my own db integration test to verify. I have it working now. The only change is to add access type on the id of the target entity. That was the only change needed. @Id @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "FILECONTENT_ID_SEQ") @SequenceGenerator( name = "FILECONTENT_ID_SEQ", sequenceName = "FILECONTENT_ID_SEQ") @Column( name = "ID", nullable = false) @Access(AccessType.PROPERTY) private Long id; – shane lee Jul 07 '14 at 01:01
  • Is there any workaround to get at a field other than the identity/identifier getter method without full object retrieval/proxy initialization? – Patrick M Mar 08 '16 at 21:08
  • PatrickM, not using this feature. You could create a different reduced entity mapped to the same table. – Ondra Žižka Feb 23 '17 at 18:08
33

Just to add to the Arthur Ronald F D Garcia'post: you may force property access by @Access(AccessType.PROPERTY) (or deprecated @AccessType("property")), see http://256stuff.com/gray/docs/misc/hibernate_lazy_field_access_annotations.shtml

Another solution may be:

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

Works for detached entities, too.

Gray
  • 115,027
  • 24
  • 293
  • 354
xmedeko
  • 7,336
  • 6
  • 55
  • 85
  • 1
    I have used your idea, plus the fact that proxies cannot override final methods, to alter the `getId()` method itself to avoid initialization. Please, if you can, see my answer in this page and tell me what you think. I also don't understand why you are checking if `lazyInitializer.isUninitialized()`. Can't you always return `lazyInitializer.getIdentifier()` when the entity is a HibernateProxy? – Marcelo Glasberg Jul 15 '15 at 20:42
  • I don't remember why I've used `if (lazyInitializer.isUninitialized())`. Maybe to use dirty trick only when really necessary. I think it may be omitted. – xmedeko Jul 20 '15 at 07:43
  • 2
    The Hibernate annotation "AccessType" is deprecated. Use the JPA2 annotation instead `@Access(AccessType.PROPERTY)` – minni Sep 30 '15 at 14:43
29

Unfortunately the accepted answer is wrong. Also other answers doesn't provide the simplest or a clear solution.

Use the Property Access Level for the ID of the BAR class.

@Entity
public class Bar {

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

    ...
}

Just as simple as that :)

Paul Wasilewski
  • 9,762
  • 5
  • 45
  • 49
  • Doesn't work if ID is a composite key ( `@EmbeddedId` ) and logical names don't match column names ( eg. `@Column(name="U_NAME") private String username` ). –  Jul 27 '19 at 20:58
  • But how does it work, I've been reading @Access documentation, but I can't figure out how it impacts the behavior of the Hibernate proxy? – Logan Wlv May 20 '22 at 07:41
  • Property Based Access has many downfalls... please check this https://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access – Imtiaz Shakil Siddique Apr 28 '23 at 18:45
17

add @AccessType("property")

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@AccessType("property")
protected Long id;
Dima
  • 1,045
  • 14
  • 23
  • 15
    The Hibernate annotation "AccessType" is deprecated. Use the JPA2 annotation instead: `@Access(AccessType.PROPERTY)` – minni Sep 30 '15 at 14:46
12

The Java Persistence with Hibernate Book mentions this in "13.1.3 Understanding Proxies":

As long as you access only the database identifier property, no initialization of the proxy is necessary. (Note that this isn’t true if you map the identifier property with direct field access; Hibernate then doesn’t even know that the getId() method exists. If you call it, the proxy has to be initialized.)

However, based on @xmedeko answer in this page I developed a hack to avoid initializing the proxy even when using direct field access strategy. Just alter the getId() method like shown below.

Instead of:

    public long getId() { return id; }

Use:

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

The idea here is to mark the getId() method as final, so that proxies cannot override it. Then, calling the method cannot run any proxy code, and thus cannot initialize the proxy. The method itself checks if its instance is a proxy, and in this case returns the id from the proxy. If the instance is the real object, it returns the id.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
  • 9
    Haha this is really a horrible hack :) "Don't do it at home" – Ondra Žižka Feb 23 '17 at 18:28
  • 2
    @OndraŽižka You are wrong. This code works perfectly. Also, it doesn't break any rules, there are no side effects, and it's clear what it's doing and why. So, if you can think of any reason not to use this code or why it is "horrible", please share. – Marcelo Glasberg Feb 23 '17 at 23:07
  • 5
    It goes to Hibernate's internal classes which may change without prior notice. While I don't doubt it works perfectly, it's not something I would put into an application that's supposed to last for years. – Ondra Žižka Feb 24 '17 at 15:53
  • Wrong again. That's maybe the most fundamental piece of Hibernate functionality: To be able to get the proxy. This is also used to initialize the proxy, or remove the proxy, when necessary. It's used a lot both internally and in the application level. The chance of this going away without a complete Hibernate rewrite is virtually zero. – Marcelo Glasberg Feb 24 '17 at 16:32
  • 7
    Sure, good for you. To others, I recommend not to rely on this unless they plan to stick with same major version. – Ondra Žižka Feb 25 '17 at 01:51
  • Thanks for sharing. – Marcelo Glasberg Feb 26 '17 at 01:49
4

In org.hibernate.Session you have a function who do the work without lazy loading the entity :

public Serializable getIdentifier(Object object) throws HibernateException;

Found in hibernate 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();
        }
  }
Grégory
  • 1,473
  • 1
  • 17
  • 28
4

There is now a jackson hibernate datatype library here:

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

And you can configure the features:

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

This will include the id of the lazy loaded relationship-

chrismarx
  • 11,488
  • 9
  • 84
  • 97
0

You could use a HQL query. The getBar() method will truly return a proxy, that won't be fetched until you invoke some data bound method. I'm not certain what exactly is your problem. Can you give us some more background?

Bozhidar Batsov
  • 55,802
  • 13
  • 100
  • 117
  • 1
    Thanks for the response. What you describe is not what happens. getBar() is causing the fetch to occur. I would expect what you describe, that a proxy object is returned and no fetch is executed. Is there any other configuration I could be missing? – Rob Apr 07 '10 at 15:58
  • Actually the getId() following getBar() is causing the entity to be fetched. You're not missing any configuration IMO. Maybe some query like "select f.bar.id from Foo f where f.id=?" will do the trick for you. – Bozhidar Batsov Apr 07 '10 at 16:51
  • The proxy object should not fetch the full Bar on bar.getId(). It already knows the id, since that is part of Foo. Anyway, it executes the fetch without invoking .getId() – Rob Apr 07 '10 at 17:42
0

change your getter method like this:

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;
}
Nik Kashi
  • 4,447
  • 3
  • 40
  • 63