Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When asked to load a lazy field, Hibernate loads all lazy fields

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>
like image 827
adragomir Avatar asked Aug 25 '14 13:08

adragomir


2 Answers

There are two essential resources to properly undestand this issue. Firstly, the lazy loading of properties is not the standard setting. See:

  • 20.1.8. Using lazy property fetching

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:

  • NHibernate new feature: Lazy Properties

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

like image 184
Radim Köhler Avatar answered Oct 21 '22 02:10

Radim Köhler


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

like image 43
rakesh Avatar answered Oct 21 '22 04:10

rakesh