Go here for Lazy Loading and Transaction Isolation

Lazy Loading Introduction

Lazy Loading is when a Reference (also called Proxy) Bean or Collection is populated automatically by a query.

For a List Set or Map lazy loading is provided by the matching BeanList BeanSet and BeanMap objects found in com.avaje.ebean.collection. These are wrappers around the underlying List Set or Maps. If a BeanList is a Reference/Proxy then its underlying list is null but it has a Find object that can be used to 'lazy load' the list. Any methods called on BeanList such as size() hashCode() iterator() get() etc will automatically result in a query being executed fetching the underlying list.

For Lazy Loading on individual Entity Beans this is achieved by method interception (AOP). The appropriate 'getter' and 'setter' methods are intercepted and lazy loading is invoked as appropriate.

Get a Reference explicitly


// Get a reference(proxy) explicitly
Bug bugRef = (Bug)Ebean.getReference(Bug.class, 1);

// This does NOT invoke a lazy load as its the Id
Integer bugId = bugRef.getId();


// This WILL invoke the lazy load query loading the data 
// into bugRef before returning the title
String title = bugRef.getTitle();


 

The way this works is that the appropriate 'getter' and 'setter' methods are intercepted. The getTitle() method invokes the 'lazy loading query' and the data is loaded into the butRef instance before returning the title.

Id and Transient properties do not invoke lazy loading.

Before continuing I'd like to point out that these two methods below are exactly the same. The reason you would want to use the FindById object is that it gives you additional options for the query including whether to use caching and what associations to include in the query.


// Firstly note that these two queries are exactly the same

// Query 1:
Bug bug1 = (Bug)Ebean.find(Bug.class, 1);

// Query 2:
FindById find = new FindById(Bug.class, 1);
Bug bug2 = (Bug)Ebean.find(find);


 

Get a Reference implicitly

Entity Beans returned by a query will have Reference/Proxy objects. Note: From an Object graph perspective they are the 'leaves' of the tree.


FindById find = new FindById(Bug.class, 1);
	
Bug bug = (Bug)Ebean.find(find);

// user is a reference/proxy
User user = bug.getUserLogged();

// details is also a reference/proxy
List details = bug.getDetails();

// this invokes a query for the user
String name = user.getName();

// this invokes a query for the details
int size = details.size();


The resulting SQL from the above is three separate queries. The last two are the result of the lazy loading of userLogged and details.


<sql summary='[app.data.Bug]'>
SELECT b.id, b.body, b.cretime, b.duplicate_of, b.fixed_version, 
       b.reported_version, b.resolution, b.title, b.updtime, 
	   b.priority_code, b.product_id, b.status_code, b.type_code, 
	   b.user_assigned_id, b.user_logged_id 
FROM b_bug b 
WHERE b.id = ? 
</sql>
<sql summary='[app.data.User]'>
SELECT u.id, u.cookie_login, u.cretime, u.email, u.error_count, 
       u.last_login, u.name, u.pwd, u.reset_code, u.reset_time, 
	   u.salt, u.updtime, u.status_code 
FROM s_user u 
WHERE u.id = ? 
</sql>
<sql summary='[app.data.BugDetail]'>
SELECT d.id, d.body, d.cretime, d.post_date, d.title, d.updtime,
       d.bug_id, d.user_id 
FROM b_bug_detail d 
WHERE bug_id = ?
</sql>


setInclude()

If you knew that you where going to get the userLogged and details information then you would likely be better off including them in the original query. The next example uses the find.setInclude() method to specify that we want to also get that information.


FindById find = new FindById(Bug.class, 1);

// also include these associations in the query
find.setInclude("userLogged; details; ");
	
Bug bug = (Bug)Ebean.find(find);

// user is a reference/proxy
User user = bug.getUserLogged();

// details is also a reference/proxy
List details = bug.getDetails();

// this invokes a query for the user
String name = user.getName();

// this invokes a query for the details
int size = details.size();


The resulting SQL is:


<sql summary='[app.data.Bug, userLogged] +many[details]'>
SELECT b.id, b.body, b.cretime, b.duplicate_of, b.fixed_version,
       b.reported_version, b.resolution, b.title, b.updtime, 
	   b.priority_code, b.product_id, b.status_code, b.type_code,
	   b.user_assigned_id
        , cu.id, cu.cookie_login, cu.cretime, cu.email, cu.error_count,
		cu.last_login, cu.name, cu.pwd, cu.reset_code, cu.reset_time,
		cu.salt, cu.updtime
        , ed.id, ed.body, ed.cretime, ed.post_date, ed.title, 
		ed.updtime, ed.bug_id, ed.user_id 
FROM b_bug b
LEFT OUTER JOIN s_user cu ON b.user_logged_id = cu.id 
LEFT OUTER JOIN b_bug_detail ed ON b.id = ed.bug_id  
WHERE b.id = ? 
</sql>


In general, if you know you are going to use the information then you should probably look to include it in the query.

Negative: including a OneToMany(or ManyToMany) repeats 'master'

In the example above we included the 'details' which is a OneToMany association. If you take the sql and execute it you will note that the bug and userLogged information (columns prefixed with b. and cu.) are repeated for each row in the bug details (b_bug_detail) table.

That is, for 'master detail' queries (where a associated OneToMany or ManyToMany is included in the query) the benefit of getting the 'details' in the same query as the 'master' needs to be balanced against the cost of 'master' columns being repeated.

Note: Only one OneToMany(or ManyToMany) can be included

Also note that only one OneToMany or ManyToMany association is allowed to be included in the query. The reason is that including more than one would result in a cartesian product and that is likely to be unwise due to cost.

For the Bug entity bean it has two OneToMany associations in Details and Attachments. You could include the Details or the Attachments but not both in a single query.

Introduction User Guide (pdf) Install/Configure Public JavaDoc Whitepapers
General Database Specific Byte Code Deployment Annotations Features
Top Bugs Top Enhancements
woResponse