Google Groups

Re: [play-framework] Re: [2.0] Ebean lazy loading


Timo Hoepfner Feb 22, 2012 3:01 AM
Posted in group: play-framework
Thanks Guillaume, your comment brought me on the right track.

TL;DR

Why is the play.core.enhancers.PropertiesEnhancer.rewriteAccess enhancement not applied to Scala code?

Long story:

In case someone else is wondering, this is what is happening:

After Play has compiled classes, it will enhance the compiled byte code. This is happening in PostCompile in PlayCommands.scala.

1. It will add getters and setters for fields, if there aren't any in place yet (play.core.enhancers.PropertiesEnhancer.generateAccessors)
2. It will will rewrite classes that directly access fields to use the accessors insted (play.core.enhancers.PropertiesEnhancer.rewriteAccess)
3. If using Ebean, the Ebean enhancer will be applied to the classes configured via application.conf (e.g. "models.*")

The Ebean enhancer will scan classes markes with @Entity and add its own getters and setters which call additional code before the actual value is read from or set to the field.
This additional code enables lazy loading, dirty checking and optimistic concurrency checking.
Then it will (also) replace each direct field access with calls to the Ebean getters/setters.
As play already replaced all occurences of direct field access with a call to getters and setters in step 2, this should effectively only modify the getters and setters created manually or in step 1.

Sample:

Employee employee=Employee.find.byId(1);
Company company=employee.company;

After Step 1&2, this will be converted to

Company company=employee.getCompany();

With Employee#getCompany() beeing something like

@PropertiesEnhancer.GeneratedAccessor
public Company getCompany(){
return this.company;
}

After step 3, the getter will be modified to be something like

@PropertiesEnhancer.GeneratedAccessor
public Company getCompany(){
return _ebean_get_company();
}

protected Company _ebean_get_company() {
  this._ebean_intercept.preGetter("company");
  return this.company;
}

The unexpected behaviour I came across, was that when I did something like employee.company.name in Java, lazy loading worked, but when doing @employee.company.name in a view template it didn't.

Why?

Because the steps 1&2 above are only executed for classes that have Java source code that lives below the "app" folder of the project.
The view templates are converted to Scala sources by play and are put into target/scala-2.9.1/src_managed before compilation. The enhancement is not done for them.

What to do right now?

When using direct field access:
- Java code just works
- Don't rely on lazy loading in Scala code (including views!) 
- Never ever change values on Ebean entities from Scala code as there will be no dirty checking in place

Alternatively use property access:
- The bytecode enhancement will be done after the view templates are already compiled
- That means you cannot use the acessors generated in step 1 of the enhancement process in view templates, as they are not there yet
- So manually add getters and setters to the Ebean entities (tendious) and only use these, at least from Scala code including view templates (ugly)

How to improve that?

Enable step 2 of the enhancement process for the view templates or scala classes in general. E.g. by adding the following to PostCompile in PlayCommands.scala:

val viewClassesDir = new File(srcManaged, "views")
val viewClasses = ( viewClassesDir ** "*.scala").get.map { sourceFile =>
  analysis.relations.products(sourceFile)
}.flatten.distinct
viewClasses.foreach(play.core.enhancers.PropertiesEnhancer.rewriteAccess(classpath, _))

I did only test that very briefly. It appears to work, but I don't know if it has unwanted side effects.

So the big question is, are there reasons why this currently isn't done?

I do think, that lazy loading has its place, especially when combined with Ebeans auto query tuning. In one test I had a page that generated >400 SQL queries per view due to lazy loading. Within a few minutes, Ebean's query tuning converted that to a single query per page view.

Thanks for reading this far,

Timo