Hibernate one-to-one: getId() without fetching entire object
JavaHibernateJpaJava Problem Overview
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?
Java Solutions
Solution 1 - Java
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.
Solution 2 - Java
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.
Solution 3 - Java
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 :)
Solution 4 - Java
add @AccessType("property")
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@AccessType("property")
protected Long id;
Solution 5 - Java
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.
Solution 6 - Java
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();
}
}
Solution 7 - Java
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-
Solution 8 - Java
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?
Solution 9 - Java
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;
}