Persistence Context

Definition as per JPA Specification

A persistence context is a set of managed entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed by the entity manager. (JPA Spec Section 5.1).

Unique Entity instances and ORM

Any ORM will be mapping (flat) relational result sets into object graphs (trees). During the process of this conversion from flat result sets into object graphs duplicated data (in the result set) is managed by the Persistence Context.

You could say that it is the job of the Persistence Context to handle the duplication in the result set in order to build an object graph that is consistent.

Example Data

Lets take a look at some example data from a simple Order Table. This shows 3 orders all for Customer 27.

ORDER TABLE:

ID STATUS ORDER_DATE CUSTOMER_ID
100 SHIPPING 01/Jan/2007 27
101 SHIPPING 10/Jan/2007 27
102 NEW 14/Jan/2007 27

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.

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