Statement level Persistence Context
Lets say all the orders are fetched...
In the example data we can see that customer 27 has all 3 orders. When the ORM converts
the flat result set into an object graph you will end up with 3 order instances all referencing
the same customer instance (for customer 27).
The way this occurs is that as in reading the result set, for each row the ORM looks into
the persistence context for customer 27 and if it exists references that instance. If a
reference to customer 27 doesn't exist then one is built and put into the persistence context.
FindByPredicates find = new FindByPredicates();
find.setBeanType(Order.class);
List orders = Ebean.findList(find);
for (int i=0; i<orders.size(); i++) {
Order order = orders.get(i);
Customer customer = order.getCustomer();
//the instance customer is the same for each order
}
The Persistence Context is used for every ORM query. Its job is ensure that a 'consistent'
Object graph is built from the 'flat' result set.
Relational Foreign Keys are converted by the ORM to 'lazy loading references' (like customer).
Instead of having 3 Integers of value 27 in the relational resultset the ORM converts
these into a single instance of Customer that is SHARED by all 3 order objects in the object graph.
With ORM you will have the ability to fetch associated data in the query. So instead of
just getting the foreign key the query could include related data such as
@ManyToOne/@OneToOne type data such as Customer (for an order)
and @OneToMany/@ManyToMany type data such as order lines (for an order).
Each of these queries will produce a relational query with duplicated data, and the Persistence
Context is used to eliminate the duplication to build a consistent object graph.
When including a 'One' property in the query (such as customer), then all
the customer data is duplicated instead of just the foreign key.
FindByPredicates find = new FindByPredicates();
find.setBeanType(Order.class);
find.setInclude("customer");
// query includes the customer information
List list = Ebean.findList(find);
In the relational query below the Customer data cc.id, cc.name etc will
be repeated 3 times.
<sql summary='[app.data.test.Order, customer]'>
SELECT o.id, o.status, o.order_date, o.updtime
, cc.id, cc.name, cc.updtime
FROM or_order o
JOIN or_customer cc ON o.customer_id = cc.id
</sql>
When including a 'Many' property in the query, then the relational result set
duplicates the order and customer information.
FindByPredicates find = new FindByPredicates();
find.setBeanType(Order.class);
find.setInclude("customer, orderLines");
// query includes the customer, and order lines information
List list = Ebean.findList(find);
In the relational query below the Order and Customer data is repeated
for each corresponding row in the or_order_detail table. If order 100
had 4 order lines then order 100 (and it associated customer 127) data
will be repeated 4 times.
<sql summary='[app.data.test.Order, customer] +many[details]'>
SELECT o.id, o.status, o.order_date, o.updtime
, cc.id, cc.name, cc.updtime
, dd.id, dd.cretime, dd.order_qty, dd.ship_qty, dd.updtime, dd.order_id
FROM or_order o
JOIN or_customer cc ON o.customer_id = cc.id
LEFT OUTER JOIN or_order_detail dd ON o.id = dd.order_id
</sql>
Note that in general you will only be able to include at most 1 'Many' associated
property in a single query. If you had two then this would result in a cartesian
product which is likely to be costly.
Transaction level Persistence Context
By default Ebean and JPA both provide Transaction level Persistence Context.
What this means is that the Persistence Context 'lives' for the length of the transaction.
If you have multiple queries using the same transaction then the same Persistence Context is
used for all of those queries.
Ebean.startTransaction();
try {
Customer cust0 = (Customer)Ebean.find(Customer.class, 27);
FindByPredicates find = new FindByPredicates();
find.setBeanType(Order.class);
List orders = Ebean.findList(find);
for (int i=0; i<orders.size(); i++) {
Order order = orders.get(i);
Customer customer = order.getCustomer();
//customer == cust0
}
Ebean.commitTransaction();
} finally {
Ebean.endTransaction();
}
With the example data, the cust0 instance is the same instance as the order.getCustomer() instance.
Extended Persistence Context
JPA provides 'Extended Persistence Context'.
This could be thought of as the Persistence Context being 'owned' by the EntityManager
rather than the Transaction. In this case the same Persistence Context can be used for
multiple transactions.
JPA needs to provide this due to the mechanism it uses to support Optimistic Concurrency
Checking for objects without a version property. Please refer to the whitepaper on JPA
Architecture for an explanation of this.
Ebean originally had a mechanism where the 'Persistence Context' could be detached from a
Transaction and later attached to another transaction. This would have been a way to
provide 'Extended Persistence Context' for those who want it.
At a point it was decided to drop this ability in the bid to keep things simple and
until it becomes a feature people want.
Lazy loading and the Persistence Context
When lazy loading occurs often the 'parent' object is added into the persistence context
prior to running the lazy loading query.
For example, if we fetched order and then lazy loaded the order's orderLines, we want each
of the orderLines to refer back to the 'parent' order. This is achieved by putting the order
instance into the persistence context prior to executing the lazy loading.
Order order = (Order)Ebean.find(Order.class, 127);
//lazy load the order lines...
//Note that the order instance is put into the
//persistence context prior to executing the query
List<OrderLine> orderLines = order.getLines();
OrderLine line = orderLines.get(0);
Order parentOrder = line.getOrder();
// this is true
boolean isSame = (parentOrder == order);
// parentOrder and order are the same instance.
Read Consistency Issues
Note that a Persistence Context means that you may not get 'read consistency' from a
traditional Database perspective. People wanting or using 'Extended persistence context'
should probably have a think about how 'Read Consistency' is effected and how long they
want to keep and reuse a Persistence Context for.
|