3

I have an app running Hibernate 4.2.21 on JBoss AS 7.2

We currently have a few @OneToOne relationships which, due to known limitations of lazy loading, will alway fetch eagerly on the inverse side.

In order to enable Lazy loading for inverse relationships I am trying to enable build-time bytecode instrumentation.

Here's what I've done so far...

1) Activate instrumentation using maven-antrun-plugin (I tried the hibernate-enhance-maven-plugin and couldn't get it working but thats for another question), I now get the following maven output in the build log:

[INFO] --- maven-antrun-plugin:1.7:run (Instrument domain classes) @ MyApp-entities ---
[INFO] Executing tasks

instrument:
[instrument] starting instrumentation
[INFO] Executed tasks

2) Next I annotated all @OneToOne relationships as follows...

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "client", optional=false)
    @LazyToOne(LazyToOneOption.NO_PROXY)
    public ClientPrefs getClientPrefs() {
        return clientPrefs;
    }

    public void setClientPrefs(ClientPrefs clientPrefs) {
        this.clientPrefs = clientPrefs;
    }

3) Then I add implement FieldHandled to the @Entity classes along with private field and getter and setter:

private FieldHandler fieldHandler;

success...I am now getting following output in the deployment log:

15:54:09,720 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 56) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Session
15:54:09,730 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 57) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Session
15:54:09,969 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 56) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Client
15:54:09,970 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 57) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Client
15:54:09,999 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 56) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Country
15:54:10,003 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 57) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Country
15:54:10,054 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 56) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Pool
15:54:10,054 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 57) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.Pool
15:54:10,569 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 56) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.User
15:54:10,624 INFO  [org.hibernate.tuple.entity.EntityMetamodel] (ServerService Thread Pool -- 57) HHH000157: Lazy property fetching available for: uk.co.myapp.entities.User

The relationships now no longer eagerly load...but they don't lazy load either, they just return null silently.

I have tried removing the FieldHandled interface and FieldHandler field from the Entity as I was not sure if this was necessary, after this I no longer get the 'HHH000157: Lazy property fetching available for:'message at startup and it goes back to eagerly loading by default.

Am I missing something here? The hibernate docs are not explicit on how to actually set this up

EDIT: Added the Ant task config as per comments:

<build>
        <plugins>
               <plugin>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.7</version>
                    <executions>
                        <execution>
                            <phase>process-classes</phase>
                            <id>Instrument domain classes</id>
                            <configuration>
                                <target name="instrument">
                                    <taskdef name="instrument"
                                        classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
                                        <classpath>
                                            <path refid="maven.dependency.classpath" />
                                            <path refid="maven.plugin.classpath" />
                                        </classpath>
                                    </taskdef>
                                    <instrument verbose="true">
                                        <fileset dir="${project.build.outputDirectory}">
                                            <include name="MyApp-entities/co/uk/myapp/entities/*.class" />
                                        </fileset>
                                    </instrument>
                                </target>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                    <dependencies>
                        <dependency>
                            <groupId>org.hibernate</groupId>
                            <artifactId>hibernate-core</artifactId>
                            <version>4.2.21.Final</version>
                        </dependency>

                        <dependency>
                            <groupId>org.javassist</groupId>
                            <artifactId>javassist</artifactId>
                            <version>3.18.1-GA</version>
                        </dependency>
                    </dependencies>
                </plugin>
           </plugins>
    </build>
DaveB
  • 3,054
  • 6
  • 32
  • 58
  • You only need to add the `FieldHandled` interface, if you want to do the _instrumentation_ by hand. In that case you will need to call `FieldHandler.readObject` before every access to your lazy field to ensure that it is initialized properly. But if you activate instrumentation, you must not implement `FieldHandled`. It seems that you didn't configure the instrumentation task correctly - please add that snippet of your configuration. Btw. if you are using an IDE it might happen that it overwrites the instrumented classes immediately. – Tobias Liefke Jul 17 '17 at 20:04
  • I just feel like you enhance it first through command line, then you annotated it in the IDE, which then compiles a new set of classes overwriting the enhanced classes. – Itherael Jul 17 '17 at 23:21
  • @TobiasLiefke right ok, that makes sense, I have added details of the ant instrumentation task configuration, maybe you can spot something missing with it – DaveB Jul 18 '17 at 20:33
  • Why you are not using PROXY option? Also in your example your OneToOne is optional, and thus, it could be lazy loaded out of the box through specification of fetchMode. And another point to look for, once you are trying to use the lazy loading, are you applying either for open session in view pattern or for extended persistence context ? If not, the instrumented class could be not able to load the lazy association, and return false null results, without throwing any exception (have a deeper look into implementation code). – Ilya Dyoshin Jul 18 '17 at 21:58
  • @IlyaDyoshin I was not aware of any other solutions for lazy loading apart from faking a OneToMany relationship which I do not want to do, please provide more details as an answer if you can. We are using an extended persistence context btw – DaveB Jul 19 '17 at 08:57
  • @IlyaDyoshin It's the other way around: When the `OneToOne` is _not_ optional, lazy loading is supported with a proxy object out of the box. When it is optional, than Hibernate needs to check if it is `null`, as a proxy is never `null` itself - which prevents lazy loading without instrumentation. – Tobias Liefke Jul 19 '17 at 09:57
  • @TobiasLiefke in addition, I have tried making the relationships optional=false and lazy loading is still not happening, this appears to be an outstanding issue in hibernate - https://hibernate.atlassian.net/browse/HHH-9515 – DaveB Jul 19 '17 at 14:49
  • If the join column is not in the table to which a parent in a one-to-one association is mapped, then the association cannot be lazy if `@PrimaryKeyJoinColumn` or bytecode instrumentation are not utilized. Even if the association is not optional, Hibernate has to determine the id of the associated entity instance to store it in the proxy, thus it has to go to the associated table anyway. I provided more details in one of my earlier [answers](https://stackoverflow.com/questions/36061550/lazy-one-to-one-spring-jpa-and-building-dynamic-json/36143746#36143746) about this. – Dragan Bozanovic Jul 20 '17 at 01:09
  • That's right, I missed the `mappedBy` in the declaration. – Tobias Liefke Jul 20 '17 at 09:10
  • Did you enable hibernate.ejb.use_class_enhancer in persistance xml ? – igorzg Jul 21 '17 at 09:31
  • @DraganBozanovic Yes correct, but my problem still stands...i cannot get instrumentation to work! – DaveB Jul 22 '17 at 08:31

3 Answers3

0

@LazyToOne(LazyToOneOption.NO_PROXY) requires <property name="hibernate.ejb.use_class_enhancer" value="true"/> in your persistance.xml

Lazy, give back the real object loaded when a reference is requested (Bytecode enhancement is mandatory for this option, fall back to PROXY if the class is not enhanced) This option should be avoided unless you can't afford the use of proxies

NO_PROXY option

igorzg
  • 1,408
  • 13
  • 17
  • this option is for runtime instrumentation, i am trying to enable instrumentation at buildtime – DaveB Jul 22 '17 at 08:29
0

As usual it was a configuration issue, it seems that the ant run plugin needs to be pointed to an exact directory that contains the entities rather than one of the parent directories

This worked for me...

<instrument verbose="true">
    <fileset dir="${project.build.directory}/classes/uk/co/myapp/entities">
        <include name="*.class" />
    </fileset>
</instrument>
DaveB
  • 3,054
  • 6
  • 32
  • 58
0

you should fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.

The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add @LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.

The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown JEE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case @LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.

TanvirChowdhury
  • 1,851
  • 13
  • 21
  • The question was specifically about implementing build-time instrumentation....and has already been answered – DaveB Apr 10 '19 at 15:14