I have a one2one relation between Student
and Address
. I want the firstName
and lastName
fields of Student
to be lazy loaded. Also I want lazy for the address
field.
These are my entity classes:
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.NO_PROXY)
private Address address;
@Basic(fetch = FetchType.LAZY)
@Column(name = "first_name")
private String firstName;
@Basic(fetch = FetchType.LAZY)
@Column(name = "last_name")
private String lastName;
// getters and setters
}
The Address class:
@Entity
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "s_id")
private Student student;
@Column
private String street;
// getters and setters
}
My test method looks like this ( the Java 8 lambda is just creating an entitymanager in back and executes all in transaction ):
@Test
public void dummyTest() {
JPA_UTILS.runInTransaction(e -> {
Student s = e.find(Student.class, 150L);
System.out.println("----------++++++++++++++-----------");
s.getFirstName();
System.out.println("----------++++++++++++++-----------");
});
}
So here I am loading an existing student from the database, then fetch the lazy property firstName
(mapped to the first_name
column). The problem is that Hibernate doesn't load only firstName
but also lastName
and address
fields:
just.hibernate.one2one.TestApp > dummyTest STANDARD_OUT
Hibernate:
select
student0_.id as id1_1_0_
from students student0_
where student0_.id=?
----------++++++++++++++-----------
Hibernate:
/* sequential select just.hibernate.one2one.Student */
select
student_.first_name as first_na2_1_,
student_.last_name as last_nam3_1_
from students student_
where student_.id=?
Hibernate:
/* load just.hibernate.one2one.Address */
select
address0_.id as id1_0_1_,
address0_.street as street2_0_1_,
address0_.s_id as s_id3_0_1_,
student1_.id as id1_1_0_
from addresses address0_
left outer join students student1_ on address0_.s_id=student1_.id
where address0_.s_id=?
----------++++++++++++++-----------
I don't want this behavior, I want to load only what I request. Can someone help me find the problem ?
Thanks
UPDATE1:
Instrumentation is done with maven, I'm posting only the relevant code (I've tried with gradle and the same results)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<tasks>
<taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
<classpath>
<path refid="maven.runtime.classpath" />
<path refid="maven.plugin.classpath" />
</classpath>
</taskdef>
<instrument verbose="false">
<fileset dir="${project.build.outputDirectory}">
<include name="**/*.class"></include>
</fileset>
</instrument>
</tasks>
</configuration>
</plugin>
There are two essential resources to properly undestand this issue. Firstly, the lazy loading of properties is not the standard setting. See:
Hibernate supports the lazy fetching of individual properties. This optimization technique is also known as fetch groups. Please note that this is mostly a marketing feature; optimizing row reads is much more important than optimization of column reads. However, only loading some properties of a class could be useful in extreme cases.
Secondly, based on the above doc part, there is a detailed article about that:
While this is related to NHibernate, the content is the same, because this feature comes from Hibernate.
We can read there:
What about multiple lazy properties? NHibernate support them, but you need to keep one thing in mind. NHibernate will load all the entity’s lazy properties, not just the one that was immediately accessed. By that same token, you can’t eagerly load just some of an entity’s lazy properties from HQL.
And again, we can understand that lazy
setting for a property is not a standard setting. It is intended for some very specific cases, e.g.:
This feature is mostly meant for unique circumstances, such as Person.Image, Post.Text, etc. As usual, be cautious in over using it.
SUMMARY: Lazy loading of properties (ValueTypes/strings, not associations) is there for special cases. It should help us to avoid loading some huge columns. BUT, once such a property is accessed, all others are loading as well
With Hibernate you can load your columns lazily by using @Basic(fetch=FetchType.LAZY)
, but for this to work Hibernate requires bytecode instrumentation to intercept the attribute access request and issue the secondary select statement on demand.If using the Maven bytecode enhancement plugin, then enableLazyInitialization
configuration property must be set to true as:
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
This above plugin has its own disadvantages. SO instead if you need only certain columns you can write a named query requesting only those columns like
TypedQuery<ViewTemplate> query=getEntityManager().createQuery("SELECT NEW com.abcd.test.Table(v.id,v.name,v.type) FROM ViewTemplate v where v.id>1",Table.class).getResultList();
And in your entity define a constructor with only these values
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With