Monday Jun 14, 2010

The Typing of Criteria Queries

There were some important changes to the Criteria APIs in the JPA Final Release that probably haven't gotten enough attention. These changes are significant because they tighten up the type safety straight through from query definition to query execution.

These fall into two main areas:

  • Changes to the Criteria API itself.
  • Changes to the APIs for query execution.

In addition, there were a number of more minor changes, many related to improving the naming of interfaces and methods. The most obvious of these from a developer point of view is probably the renaming of the QueryBuilder interface to CriteriaBuilder.

One of the earlier gaps in the typing consistency of criteria queries was that the types of the query results were not carried through into query execution. The JPA Final Release tightens this up by enabling you to type CriteriaQuery objects according to their expected results.

To leverage this, when you create a CriteriaQuery object, you parameterize it according to the expected result type.

For example, a query to return a list of all customers would be defined as follows:

EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> c = cq.from(Customer.class);
cq.select(c);

Now that the CriteriaQuery object is itself typed, we can carry this typing through to query execution:

TypedQuery<Customer> tq = em.createQuery(cq);
List<Customer> results = tq.getResultList();

The TypedQuery interface extends the Query interface. It can also be used for the execution of JPQL queries by means of new methods on the EntityManager interface:

<T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass);

<T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass);

Thus, for example:

...
TypedQuery<Customer> tq = em.createQuery("SELECT c FROM Customer c", Customer.class);
List<Customer> allCustomers = tq.getResultList();

This approach, together with the use of the CriteriaBuilder construct method, also allows us to use a constructor in the select list of a criteria query. For example, suppose we have a CustomerDetails class, with a constructor that takes a customer's name, address, and balance owed. This time we'll illustrate with a query that returns a single result:

CriteriaBuilder cb = ...;
CriteriaQuery<CustomerDetails> cq = cb.createQuery(CustomerDetails.class);
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.select(cb.construct(
                CustomerDetails.class,
                c.get(Customer_.name),
                c.get(Customer_.address),
                c.get(Customer_.balanceOwed)));
TypedQuery<CustomerDetails> tq = em.createQuery(cq);
CustomerDetails result = tq.getSingleResult();

Suppose we don't want to use a constructor. How do we type CriteriaQuery objects that return multiple result items?

There are several options.

The one that I prefer is to define such queries in terms of Tuple types. A "tuple" query" can be created either by passing Tuple.class to the CriteriaBuilder createQuery method or by use of the CriteriaBuilder createTupleQuery method.

For example,

CriteriaBuilder cb = ...;
CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);

or

CriteriaBuilder cb = ...;
CriteriaQuery<Tuple> cq = cb.createTupleQuery();

To specify the select clause of the query, you can either pass the selection items to the CriteriaBuilder tuple method, like this:

Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.select(cb.tuple(
               c.get(Customer_.name),
               c.get(Customer_.address),
               c.get(Customer_.balanceOwed)));

or pass them to the CriteriaQuery multiselect method:

CriteriaBuilder cb = ...;
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.multiselect(
        c.get(Customer_.name),
        c.get(Customer_.address),
        c.get(Customer_.balanceOwed));

The multiselect method is a convenience method, and requires the persistence provider to aggregate multiple selection items in a way that correponds to the result type that you have specified for the query.

Having defined such a tuple query, there are several ways to extract the results when the query is executed.

By-position is probably the most obvious. Note that we must specify the expected result type of each tuple item.

TypedQuery<Tuple> tq = em.createQuery(cq);
Tuple result = tq.getSingleResult();
String name = result.get(0, String.class);
Address address = result.get(1, Address.class);
Double balanceOwed = result.get(2, Double.class);

Like SQL, the Criteria API allows you to assign "aliases" to selection items for use in tuple result extraction. To do this, we would modify our query as follows:

CriteriaBuilder cb = ...;
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.multiselect(
        c.get(Customer_.name).alias("customerName"),
        c.get(Customer_.address).alias("customerAddress"),
        c.get(Customer_.balanceOwed).alias("customerBalance"));

We can now extract the tuple items by name:

TypedQuery<Tuple> tq = em.createQuery(cq);
Tuple result = tq.getSingleResult();
String name = result.get("customerName", String.class);
Address address = result.get("customerAddress", Address.class);
Double balanceOwed = result.get("customerBalance", Double.class);

Finally, if you prefer results to be return as an array, that is possible too. To specify the result list, use either the CriteriaBuilder array method in conjunction with select, or use the multiselect method. The criteria query creation can either be specified without a type or with Object or Object[] as the result type. To avoid compiler warnings, I would write the query like this:

CriteriaBuilder cb = ...;
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.multiselect(
        c.get(Customer_.name),
        c.get(Customer_.address),
        c.get(Customer_.balanceOwed));
TypedQuery<Object[]> tq = em.createQuery(cq);
Object[] result = tq.getSingleResult();

Monday Mar 29, 2010

JPA.next - Thinking about the Future

It's already close to 4 months since we released JPA 2.0, so it's time to start planning for the next release.

JPA 2.0 was strongly influenced by input that we received from the Java developer community, and many of the new features and improvements in JPA 2.0 were added and prioritized in response to developer feedback.

These include all of the following (and more!):

  • collections of basic types
  • collections of embeddables
  • nested embeddables
  • persistently ordered lists
  • orphan removal
  • pessimistic locking
  • foreign key mappings for unidirectional one-to-many relationships
  • improved support for maps
  • criteria query API
  • improvements to JPQL, including collection-valued in-expressions, more generalized support for operators and functions, and restrictions on query polymorphism
  • further standardization of configuration properties and hints

Feedback from the community is very important to us, so please share your thoughts on where you think we should go next. What features do you think are most important to add in JPA 2.1?

Feel free to comment here, and please also share your input by posting to the JPA 2.0 feedback list, jsr-317-feedback@sun.com. Input sent to it forwards to all of the members of the JPA 2.0 expert group for consideration.

Thanks!

Monday Mar 08, 2010

JPA with MySQL, EclipseLink, and GlassFish

Arun Gupta and I recently did a webinar on using the latest Java Persistence 2.0 features with MySQL.

This is a quick overview of new JPA features coupled with a NetBeans demo for which I put together a mapping from a MySQL database schema to JPA.

The replay is now available here.

Friday Dec 11, 2009

Java Persistence 2.0 Final Release Is Now Available

It is now official: JPA 2.0 has been released, together with Java EE 6.

I'd like to take this opportunity to again thank the members of the JSR 317 expert group for all of their contributions over the past few years. It has been a long haul, but after over 3000 emails and 100 hours of conference calls, we are done!

I'd also like to give a big thanks to the members of the EclipseLink team, who did a fantastic job of producing the JPA 2.0 reference implementation.

Finally, I'd also like to thank the members of the community who have sent us your input. Hopefully as you can tell, a lot of the new features in JPA 2.0 were driven by your suggestions and requests. Please keep them coming!

Here are the bits:

Friday Mar 27, 2009

Java Persistence 2.0 Proposed Final Draft

When we released the JPA 2.0 Public Draft several months ago, one of our main goals was to get feedback on the new functionality – and, in this case, the most important new piece was the criteria API.

We did.  We received quite a few responses to the criteria API—many of you pleased that we had addressed this area—others suggesting that we should look at providing a typesafe API rather than a string-based API.

Gavin King immediately took up the challenge.  Gavin's proposal to the group, described here in his blog, was a typesafe API based on a metamodel of the managed classes in the persistence unit.

The expert group was in agreement that the metamodel would be valuable to have, independent of its use to support a criteria API.  (Actually, we had discussed the usefulness of a metamodel API back in the JPA 1.0 days, although understandably it was low on our priority list back then). The group was in less agreement on the form of the criteria API itself.  After many discussions on the pluses and minuses of a metamodel-based API compared to a string-based API, we decided to adopt the typesafe API, but to provide developers the option of using a string-based approach if they prefer.  More on that below—but first, some background on the metamodel API and how to use the typesafe API.


Metamodel API

From a query point of view, the metamodel captures what the spec refers to in JPQL terms as the "abstract schema" of the persistence unit — that is, the logical view over the persistent state and relationships of the managed classes of the persistence unit (entities, mapped superclasses, embeddables).

The metamodel can be browsed dynamically, using the new javax.persistence.metamodel interfaces. These interfaces can be used directly to write typesafe queries with the new API or, more conveniently,  metamodel classes that capture the metamodel of the persistence unit can be generated at the time the corresponding managed classes are compiled.  To facilitate this, we plan to release an annotation processor to be run in conjunction with javac to generate these classes.

To illustrate what such metamodel classes look like, consider the following set of interrelated entity classes that I used in my earlier post :

@Entity public class Customer {
  @Id int custId;
  String name;
  ...
  @OneToMany(mappedBy="customer") Set<Order> orders;
  ...
}

@Entity public class Order {
  @Id int orderId;
  ...
  @ManyToOne Customer customer;
  @OneToMany(mappedBy="order") Set<LineItem> items;
  ...
}

@Entity public class LineItem {
  @Id int id;
  @ManyToOne Order order;
  @ManyToOne Product product;
  ...
}

@Entity public class Product {
  @Id int productId;
  String name;
  String productType;
  ...
}

The corresponding metamodel classes look like this:

import javax.persistence.metamodel.\*;
@TypesafeMetamodel
public class Customer_ {
    public static volatile Attribute<Customer, Integer> custId;
    public static volatile Attribute<Customer, String> name;
    public static volatile Set<Customer, Order> orders;
    ...
}

import javax.persistence.metamodel.\*;
@TypesafeMetamodel
public class Order_ {
    public static volatile Attribute<Order, Integer> orderId;
    public static volatile Attribute<Order, Customer> customer;
    public static volatile Set<Order, LineItem> items;
   ...
}

import javax.persistence.metamodel.\*;
@TypesafeMetamodel
public class LineItem_ {
    public static volatile Attribute<LineItem, Integer> id;
    public static volatile Attribute<LineItem, Order> order;
    public static volatile Attribute<LineItem, Product> product;
    ...
}

import javax.persistence.metamodel.\*;
@TypesafeMetamodel
public class Product_ {
    public static volatile Attribute<Product, Integer> productId;
    public static volatile Attribute<Product, String> name;
    public static volatile Attribute<Product, String> productType;
    ...
}


The advantage of using generated metamodel classes, rather than using the javax.persistence.metamodel API directly, is that it makes the writing of typesafe queries very easy, as I'll describe in the sections below.  The API is otherwise much in the spirit of that in the Public Draft.  It improves over this earlier API in that it provides a more "semantic" factorization of interfaces as well as the ability to browse the constitute parts of a criteria query object.

For the sake of providing a more direct comparison with our earlier version, I'm going to take what I wrote about our earlier version of the criteria API and translate sample queries to the new criteria API.


Writing Queries with the New Criteria API

The core interfaces of the criteria API are the CriteriaQuery interface, which provides the methods that construct the constituent parts of a criteria query (select clauses, where clauses, etc.) and the QueryBuilder interface—which serves as the factory for criteria query objects and for the operations that are used to construct expressions and predicates.

The QueryBuilder interface is obtained from the EntityManager or EntityManagerFactory.   Using the QueryBuilder object, you create an "empty" CriteriaQuery object:

EntityManager em = ... ;
QueryBuilder qb = em.getQueryBuilder();
CriteriaQuery q = qb.create();


As before, the specification of query roots is the first step in constructing a query.  These correspond to the range variables defined in a SQL FROM clause, and specify the domain objects on which the query is based.  The from method of the CriteriaQuery interface is used to add a query root to a CriteriaQuery instance. The from method is additive.  The addition of further query roots, like additional range variables in JPQL, creates a cartesian product with the existing roots.

CriteriaQuery q = qb.create();
Root<Customer> customer = q.from(Customer.class);


Query roots are instances of the Root interface.  Notice that the Root variable customer is parameterized by the type of the entity that it represents. 

Given one or more query roots, the query domain can be modified using join operations.   The argument to the join method is a metamodel attribute which captures both the source of the join and the join target.  For an entity relationship or for an element collection this attribute is of type javax.persistence.metamodel.Collection, javax.persistence.metamodel.Set, javax.persistence.metamodel.List, or javax.persistence.metamodel.Map. When performing a join to an embeddable, it is of type javax.persistence.metamodel.Attribute. 

For example, to modify the above query to operate over customers and their orders, we would add:

Join<Customer,Order> order = customer.join(Customer_.orders);

The argument to the join method here is of type Set<Customer, Order>.   The typesafe parameterization of the join method insures that it is not possible to construct an invalid join from the customer root.

When writing queries using the typesafe API in this way, in general the only places that you will use the attributes of metamodel classes is to specify navigation (joins in the from-clause, and path navigation in the other clauses of the query).  From a typing point of view, these objects play the role of Java member literals (if only the Java language had member literals!) in providing type information.


The SELECT and WHERE Clauses

Every criteria query needs a select clause.  Every non-trivial query has a where clause.

Continuing our earlier example, a query to return all customers ordering products of type 'printer' would look something like this with JPQL:

SELECT c.name
FROM Customer c JOIN c.orders o JOIN o.items i
WHERE i.product.productType = 'printer'


The criteria API equivalent is the following:

QueryBuilder qb = em.getQueryBuilder();
CriteriaQuery q = qb.create();
Root<Customer> customer = q.from(Customer.class);
Join<Order, LineItem> item = customer.join(Customer_.orders).join(Order_.items);
q.where(qb.equals(item.get(Item_.product).get(Product_.productType), "printer"))
 .select(customer.get(Customer_.name));


There are a couple of things to note:

The argument to the where method is a boolean expression (of type Expression<Boolean>).  As mentioned above, the QueryBuilder interface serves as a factory for such expressions.

In this particular example, the first argument to the equals method is a Path instance.  The get method is used to traverse a path.  Like the join method, it relies on use of a metamodel attribute to achieve typesafe navigation.  In this case, the path is derived from invoking get(Item_.product) on the item object and is then further chained to navigate to the productType attribute.  

The where method returns the modified CriteriaQuery instance.  If the where method is applied again, the restrictions are replaced.   If the where method is invoked without an argument, they are removed.

The select method also returns the modified CriteriaQuery instance.  If the select method is applied again, the select list is replaced.  If the select method is invoked without an argument, they are removed (and the query will not be executable).  


Coverage

As with the version presented in the Public Draft, the new criteria API is intended to provide support for all the functionality of JPQL.

In the following sections we examine how we might rewrite some of the JPQL examples in my earlier blog.  I'm just going to show the JPQL queries here and assume the existence of the obvious managed classes and their corresponding metamodel classes.


Navigation

 JPQL:

SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
WHERE e.contactInfo.address.zipcode = '95054'
    AND p.phonetype = PhoneType.OFFICE


Using the criteria API, this query can be written as follows.

CriteriaQuery q = qb.create();
Root<Employee> e = q.from(Employee.class);
Join<Employee, ContactInfo> c = e.join(Employee_.contactInfo);
Join<ContactInfo, Phone> p = c.join(ContactInfo_.phones);
q.where(qb.equal(c.get(ContactInfo_.address).get(Address_.zipcode), "95054"),
        qb.equal(p.get(Phone_.phonetype), PhoneType.OFFICE))
  .select(p.get(Phone_.billedTo)).distinct(true);



Maps

JPQL:

SELECT p
FROM PictureCategory c JOIN c.photos p
WHERE c.name = 'birds' AND KEY(p) LIKE '%egret%'

Using the criteria API:

CriteriaQuery q = qb.create();
Root<PictureCategory> c = q.from(PictureCategory.class);
MapJoin<PictureCategory, String, Photo> p = c.join(PictureCategory_.photos);
q.where(qb.equal(c.get(PictureCategory_.name), "birds"),
        qb.like(p.key(), "%egret%"))
 .select(p);

As in the JPQL query, a variable referring to a map type corresponds to the map value.  Notice that the MapJoin instance is parameterized on the type of the source entity, and the types of the map key and map value.

JPQL:

SELECT v.location.street, ENTRY(i)
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054'
   AND KEY(i).director = 'Hitchcock' AND VALUE(i) > 0

Using the criteria API:

CriteriaQuery q = qb.create();
Root<VideoStore> v = q.from(VideoStore.class);
MapJoin<VideoStore, Movie, Integer> i = v.join(VideoStore_.videoInventory);
q.where(qb.equal(v.get(VideoStore_.location).get(Address_.zipcode), "95054"),
        qb.equal(i.key().get(Movie_.director), "Hitchcock"),
        qb.gt(i, 0))
 .select(v.get(VideoStore_.location).get(Address_.street), i.entry());



Ordered Lists

JPQL:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing' AND INDEX(e) < 5


Using the criteria API:

CriteriaQuery q = qb.create();
Root<Department> d = q.from(Department.class);
ListJoin<Department, Employee> e =  d.join(Department_.members);
q.where(qb.equal(d.get(Department_.name), "Marketing),
        qb.lt(e.index, 5))
 .select(e);


Notice that the index operation here is properly typed as applying to a ListJoin instance.  Instead of joining from Employee to Department, as in the JPQL query,  we therefore join from Department to Employee.  From a database point of view, of course, the two are identical.


Restricted Polymorphism

JPQL:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name = 'Marketing' AND TYPE(e) IN (PartTimeEmployee, Contractor)


In the criteria query, entity class objects are used to specify entity type arguments to the in method.  The in method supports use of a builder pattern, which this query illustrates:

CriteriaQuery q = qb.create();
Root<Employee> e = q.from(Employee.class);
Join<Employee, Department> d = e.join(Employee_.dept);
q.where(qb.equal(d.get(Department_.name), "Marketing"),
        qb.in(e.type()).value(PartTimeEmployee.class).value(Contractor.class))
  .select(e);


Case Expressions

This query shows the use of the general form of case expressions.

SELECT c, CASE WHEN c.annualSpending > 10000 THEN 'Premier'
               WHEN c.annualSpending >  5000 THEN 'Gold'
               WHEN c.annualSpending >  2000 THEN 'Silver'
               ELSE 'Bronze'
          END
FROM Customer c

CriteriaQuery q = qb.create();
Root<Customer> c = q.from(Customer.class);
Expression<Integer> annualSpending = c.get(Customer_.annualSpending);
q.select(c, qb.selectCase()
              .when(qb.gt(annualSpending, 10000),  "Premier")
              .when(qb.gt(annualSpending, 10000),  "Gold")
              .when(qb.gt(annualSpending, 10000),  "Silver")
              .otherwise("Bronze")); 



This one uses a NULLIF expression:

SELECT AVG(NULLIF(e.salary, -99999))
FROM Employee e

CriteriaQuery q = qb.create();
Root<Employee> e = q.from(Employee.class);
q.select(qb.avg(qb.nullif(e.get(Employee_.salary), -99999)));


Subqueries

The following JPQL query returns those customers whose unpaid balance is less than half of the average.  This makes use of a non-correlated subquery.

SELECT goodCustomer
FROM Customer goodCustomer
WHERE goodCustomer.balanceOwed < (
         SELECT AVG(c.balanceOwed)/2.0 FROM Customer c)


Using the criteria API, the corresponding query can be written as shown below.  There are Root objects for both the subquery and the containing query.  The subquery method of the CriteriaQuery interface creates a Subquery instance.  This instance is typed according to its expected result type.

CriteriaQuery q = qb.create();
Root<Customer> goodCustomer = q.from(Customer.class);
Subquery<Double> sq = q.subquery(Double.class);
Root<Customer> c = sq.from(Customer.class);
q.where(qb.lt(goodCustomer.get(Customer_.balanceOwed),
            sq.select(qb.toDouble(qb.quot(qb.avg(c.get(Customer_.balanceOwed)),
                                    2.0)))))
 .select(goodCustomer);


The following JPQL query selects those employees that make more than all of the managers in their department.  This has a correlated subquery, as it uses the emp range variable of the containing query.

SELECT emp
FROM Employee emp
WHERE emp.salary > ALL (
    SELECT m.salary
    FROM Manager m
    WHERE m.department = emp.department)


This is the criteria API equivalent.

CriteriaQuery q = qb.create();
Root<Employee> emp = q.from(Employee.class);
Subquery<BigDecimal> sq = q.subquery(BigDecimal.class);
Root<Manager> m = sq.addRoot(Manager.class);
sq.select(m.get(Manager_.salary))
  .where(qb.equal(m.get(Manager_.dept), emp.get(Employee_.dept)));
q.select(emp)
 .where(qb.gt(emp.get(Employee_.salary), qb.all(sq)));



String-based Use of the Criteria API

In addition to the typesafe API, the criteria API also supports string-based navigation, as did the API of the Public Draft.  As I mentioned, the Expert Group was somewhat divided over the need for string-based support, but many members felt strongly that some developers would prefer it.

As you might have already inferred from the examples above, the main difference in the string-based usage of the criteria API is that strings are used to specify joins and path navigation.

The following queries, taken from those above, show how a criteria query would be written to use the methods that take strings rather than metamodel types as arguments:

CriteriaQuery q = qb.create();
Root<Employee> e = q.from(Employee.class);
Join<Employee, ContactInfo> c  =  e.join("contactInfo");
Join<ContactInfo, Phone> p = c.join("phones");
q.where(qb.equal(c.get("address").get("zipcode"), "95054"),
        qb.equal(p.get("phonetype"), PhoneType.OFFICE))
  .select(p.get("billedTo")).distinct(true);


CriteriaQuery q = qb.create();
Root<VideoStore> v = q.from(VideoStore.class);
MapJoin<VideoStore, Movie, Integer> i = v.join("videoInventory");
q.where(qb.equal(v.get("location").get("zipcode"), "95054"),
        qb.equal(i.key().get("title"), "Vertigo"),
        qb.gt(i, 0))
 .select( v.get("location").get("street"));

If  you prefer to use raw types rather than the parameterized types shown above, you can do so, however this will result in compiler warnings unless @SuppressWarnings("unchecked") is used:

CriteriaQuery q = qb.create();
Root e = q.from(Employee.class);
Join c = e.join("contactInfo");
Join p = c.join("phones");
q.where(qb.equal(c.get("address").get("zipcode"), "95054"),
        qb.equal(p.get("phonetype"), PhoneType.OFFICE))
  .select(p.get("billedTo")).distinct(true);



Send Us Your Feedback


This draft of the spec contains some important changes, particularly with regard to the Criteria API, and can be downloaded here.  As always, we welcome your feedback and suggestions.   If you would like to send feedback to the expert group, you can reach us at jsr-317-pfd-feedback@sun.com.

thanks!


Tuesday Nov 25, 2008

Java Persistence 2.0 Public Draft: Criteria API

Java Persistence 2.0 Public Draft (Part II) Probably the most significant of the new features introduced in the JPA 2.0 Public Draft is the Criteria API.  This is a non-string-based API for the dynamic construction of object-based queries.

Criteria queries are constructed in terms of query definition objects.  As with JPQL static and dynamic queries, criteria query definition objects are passed to the EntityManager createQuery method to create Query objects—and then executed using the methods of the Query API.

Loosely speaking, a QueryDefinition object can be thought of as a set of nodes corresponding to the semantic constructs of the query:
  • domain objects, which correspond to the range variables and other identification variables of the JPQL FROM clause
  • where clause predicates, which comprise one or more conditional expression objects
  • select clauses, which comprise one or more "select item" objects
  • order-by and group-by items
  • subqueries
  • and so on...

Constructing a Query Definition

The QueryBuilder interface is the factory for QueryDefinition objects.  A QueryBuilder instance is obtained from either the EntityManager or the EntityManagerFactory.  For example,

EntityManager em = ... ; 
QueryBuilder queryBuilder = em.getQueryBuilder();
QueryDefinition qdef = queryBuilder.createQueryDefinition();

Using the query builder, you can create either an "empty" QueryDefinition object or a QueryDefinition object with a root entity class.  The specification of query roots is the first step in constructing a query definition.  Query roots correspond to the range variables of SQL.  They specify the domain objects on which the query is based and which are not reachable by navigation or join. Query root are instances of the DomainObject interface.  In the simple case a query definition has a single root, so the QueryBuilder interface has a convenience method to support this.

DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);

This method returns the DomainObject instance corresponding to the root.  It is semantically equivalent to the following in which the addRoot method is used to add a query root to an empty QueryDefinition object:

QueryDefinition qdef = queryBuilder.createQueryDefinition();
DomainObject customer = qdef.addRoot(Customer.class);

The DomainObject interface extends the QueryDefinition interface, and QueryDefinition operations applied to domain objects operate on the query definition as a whole.  Thus, for example, if you invoke the where method on a domain object, the query definition is modified to include the specified restriction predicate.

A query with a single root entity is assumed to select entities of that type, unless the select method of the QueryDefinition interface is used to specify otherwise.  Thus, the DomainObject customer in the query above represents a complete query definition.  We can pass it to the createQuery method and execute the resulting query, causing all instances of the Customer class to be returned:

Query q = em.createQuery(customer);
List myCustomers = q.getResultList();

The addition of further query roots, like additional range variables in JPQL, has the effect of inducing a cartesian product.

Given one or more query roots, a query domain can be further refined by the addition of other domain objects through join operations.   The argument to the join method is the name of the attribute in the referencing class.  The result is the added DomainObject instance.  This, like the domain objects that are roots, again references the underlying query definition object.

For example, to modify the above query definition to operate over customers and their orders, we would add:

DomainObject order = customer.join("orders");

When executed, this query returns all customers that have orders.  Note further that because the variables order and customer are referencing the same underlying query definition, the following calls to the EntityManager createQuery method are semantically equivalent:

em.createQuery(customer);
em.createQuery(order);


Defining the SELECT and WHERE Clauses

Assume we have the following classes:

@Entity public class Customer {
  @Id int custId;
  String name;
  ...
  @OneToMany(mappedBy="customer") Set<Order> orders;
  ...
}

@Entity public class Order {
  @Id int orderId;
  ...
  @ManyToOne Customer customer;
  @OneToMany(mappedBy="order") Set<LineItem> items;
  ...
}

@Entity public class LineItem {
  @Id int id;
  @ManyToOne Order order;
  @ManyToOne Product product;
  ...
}

@Entity public class Product {
  @Id int productId;
  String name;
  String productType;
  ...
}

Using JPQL, a query to return all customers ordering products of type 'printer' would look something like this:

SELECT c.name
FROM Customer c JOIN c.orders o JOIN o.items i
WHERE i.product.productType = 'printer'

To construct a semantically equivalent query using the Criteria API, we proceed as follows:

1)  Create a query definition object.  This query is rooted in the Customer class, so we again write:

DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);

2)  Join Customer to Order and Order to LineItem.  Since the result of the join method is also a DomainObject, we chain the method invocations:

DomainObject item = customer.join("orders").join("items");

3)  Define the select list of the query and the restrictions over the results.  These modify the underlying query definition object, and can thus be defined in either order.  Let's assume we're defining the restrictions first:

customer.where(item.get("productType").equal("printer"));

The argument to the where method is a conditional predicate.  In this example, the arguments to the condition are a PathExpression instance (derived from invoking get("productType") on the item DomainObject) and the string "printer".  The where method returns this, so it supports method chaining.

The select method of the QueryDefinition interface modifies the query definition to specify the query result.  It also returns this:

customer.select(customer.get("name"));

Putting it all together, and using method chaining we have:

DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);
DomainObject item = customer.join("orders").join("items");
customer.where(item.get("productType").equal("printer"))
        .select(customer.get("name"));

While this example of course just illustrates the basics, the Criteria API provides support for all the functionality of JPQL.

In the following sections we examine how we might rewrite the JPQL examples in part I of this blog.  I'm just going to reproduce the JPQL queries here.  To see the entity classes, please refer to part I.


Navigation

Using JPQL:

SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo.phones p
WHERE e.contactInfo.address.zipcode = '95054'
    AND p.phonetype = PhoneType.OFFICE

Or, equivalently:

SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
WHERE e.contactInfo.address.zipcode = '95054'
    AND p.phonetype = PhoneType.OFFICE

This query navigates through the contactInfo embeddable in the FROM clause and again in the WHERE clause.  Because navigation in the FROM clause has the semantics of inner join, the Criteria API requires the use of the join method to construct the query domain.  The where method uses a compound predicate.

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject p = e.join("contactInfo").join("phones");
e.where(e.get("contactInfo").get("address").get("zipcode").equal("95054")
        .and(p.get("phonetype").equal(PhoneType.OFFICE)))
 .selectDistinct(p.get("billedTo"));


Maps

Here's the first query over maps:

SELECT p
FROM PictureCategory c JOIN c.photos p
WHERE c.name = 'birds' AND KEY(p) LIKE '%egret%'

DomainObject c = queryBuilder.createQueryDefinition(PictureCategory.class);
DomainObject p = c.join("photos");
p.where(c.get("name").equal("birds").and(p.key().like("%egret%")))
 .select(p);

Note that, as in the JPQL query, a variable referring to a map type corresponds to the map value.

Here are the next two queries:

SELECT v.location.street
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054' AND KEY(i).title = 'Vertigo' AND VALUE(i) > 0

and

SELECT v.location.street, ENTRY(i)
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054' AND KEY(i).director = 'Hitchcock' AND VALUE(i) > 0

Using the Criteria API, these queries are written as follows:

DomainObject v = queryBuilder.createQueryDefinition(VideoStore.class);
DomainObject i = v.join("videoInventory");
v.where(v.get("location").get("zipcode").equal("95054")
         .and(i.key().get("title").equal("Vertigo"))
         .and(i.value().greaterThan(0)))
 .select(v.get("location").get("street"));

and

DomainObject v = queryBuilder.createQueryDefinition(VideoStore.class);
DomainObject i = v.join("videoInventory");
v.where(v.get("location").get("zipcode").equal("95054")
         .and(i.key().get("director").equal("Hitchcock"))
         .and(i.value().greaterThan(0)))
  .select(v.get("location").get("street"), i.entry());
 

Ordered Lists

Here's the query over ordered lists:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing' AND INDEX(e) < 5


DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject d = e.join("dept");
e.where(d.get("name").equal("Marketing").and(e.index().lessThan(5)));


Non-polymorphic Queries

The following two examples illustrate non-polymorphic queries:

Example 1:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name = 'Marketing' AND TYPE(e) IN (PartTimeEmployee, Contractor)

In the criteria query, entity class objects are used to specify entity type arguments to the in method:

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject d = e.join("dept");
e.where(d.get("name").equal("Marketing")
           .and(e.type().in(PartTimeEmployee.class, Contractor.class)));


Example 2:

SELECT e
FROM Employee e
WHERE TYPE(e) IN :empTypes

In the criteria query below, the param method of the QueryDefinition interface defines the parameter name.  As with JPQL, this parameter must be bound to a collection of entity class objects for the query execution.  Note that in a criteria query the parameter name is not prefixed with a colon (:).

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
e.where(e.type().in(e.param("empTypes")));

Example 3:

This query uses a TYPE expression to return the entity class:

SELECT TYPE(e), e.name
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing'

As in the JPQL query, the entity class object is returned in the result of the criteria query:

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject d = e.join("dept");
e.where(d.get("name").equal("Marketing"))
 .select(e.type(), e.get("name"));


Case Expressions

These are the case expression examples.  The Criteria API does not currently support update and delete operations.

SELECT c, CASE WHEN c.annualSpending > 10000 THEN 'Premier'
               WHEN c.annualSpending >  5000 THEN 'Gold'
               WHEN c.annualSpending >  2000 THEN 'Silver'
               ELSE 'Bronze'
          END
FROM Customer c


DomainObject c = queryBuilder.createQueryDefinition(Customer.class);
c.select(c, c.case().when(c.get("annualSpending").greaterThan(10000))
                    .then("Premier")
                    .when(c.get("annualSpending").greaterThan(5000))
                    .then("Gold")
                    .when(c.get("annualSpending").greaterThan(2000))
                    .then("Silver")
                    .else("Bronze"));

This one uses a NULLIF expression:

SELECT AVG(NULLIF(e.salary, -99999))
FROM Employee e

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
e.select(e.avg(e.nullif(e.get("salary"), -999999)));


Subqueries

The criteria API supports the use of both correlated and non-correlated subqueries, as does JPQL.   Like a top-level query, a subquery is constructed through the creation and modification of a QueryDefinition object.

The following JPQL query returns those customers whose unpaid balance is less than half of the average:

SELECT goodCustomer
FROM Customer goodCustomer
WHERE goodCustomer.balanceOwed < (
           SELECT AVG(c.balanceOwed)/2.0 FROM Customer c)

Using the criteria API, the corresponding query can be written as shown below.  There are QueryDefinition objects for both the subquery and the containing query.

DomainObject goodCustomer = queryBuilder.createQueryDefinition(Customer.class);
DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);
goodCustomer.where(goodCustomer.get("balanceOwed)
                     .lessThan(customer.select(customer.get("balancedOwed")
                                                       .dividedBy(2.0)
                                                       .avg())));


In a correlated subquery, the subquery and the containing query share variables.

The following JPQL query selects those employees that make more than all of the managers in their department:

SELECT emp
FROM Employee emp
WHERE emp.salary > ALL (
    SELECT m.salary
    FROM Manager m
    WHERE m.department = emp.department)


Here's the criteria API equivalent.  I'm using QueryDefinition variables to make this very explicit, although like the earlier queries, this query can be written without them.

QueryDefinition qOuter = queryBuilder.createQueryDefinition();
DomainObject emp = qOuter.addRoot(Employee.class);

QueryDefinition qSubq = queryBuilder.createQueryDefinition();
DomainObject mgr = qSubq.addRoot(Manager.class);

qSubq.select(mgr.get("salary"))
     .where(mgr.get("department").equal(emp.get("department")));

qOuter.where(emp.get("salary").greaterThan(qSubq.all()));


Current Status


Our work in the Expert Group in designing the Criteria API involved numerous iterations and considerable debate as to the best approach, and we welcome your feedback on the results.  Not yet reflected in the draft is our intention to provide additional and/or alternative ways to obtain QueryBuilder objects to support the construction of QueryDefinition objects without the need for an entity manager or entity manager factory in order to support disconnected usage scenarios.  Since the provider's implementation of the QueryDefinition object is required to be serializable, this would also allow for QueryDefinition objects to be passed from a remote tier.

When you download the draft and review the spec, you'll notice that it flags a number of open issues and items that are in need of developer feedback.   Please feel free to send me and the expert group your thoughts at  jsr-317-pdr-feedback@sun.com.    Thanks!



Thursday Nov 13, 2008

Java Persistence 2.0 Public Draft

Java Persistence 2.0 Public Draft The Java Persistence 2.0 Public Draft is scheduled to be released this week, so I thought it would be a good time to review some of the new features that you can expect to find.

If you're familiar with the first draft of the JPA 2.0 specification (the Early Draft), you know that it focused primarily on the area of O/R mapping improvements.  The Public Draft builds on these improvements, and provides ways to leverage them through extensions to the Java Persistence Query Language and through the new Criteria API.

Because there's a lot to cover, this will be a 2-part entry.  In this part, we'll examine some of the new JPQL functionality.  Part 2 will look at the Criteria API.

The functionality added in the Early Draft to support element collections, nested embeddable classes, embeddables having entity relationships, generalized maps, and ordered lists necessitated some changes to the JPQL syntax to make queries over these mapping types easy to write.  Further, we've also added some other requested (and much needed) functionality to the language.

Navigation

As you might expect, we've extended the dot (".") navigation syntax to handle embeddables with relationships and embeddables of embeddables.

Let's suppose we have an Employee entity, with a ContactInfo embeddable class used for contact information, and a set of phones referenced by the ContactInfo embeddable.  The basic structure of the classes might look something like this:

@Entity public class Employee {
  @Id int empId;
  String name;
  @ManyToOne Department dept;
  ContactInfo contactInfo;

  ...
}

@Embeddable public class ContactInfo {
  Address address; 
  @OneToMany Set<Phone> phones;
  ...
}

@Embeddable public class Address {
  String street;
  String city;
  String zipcode;
  ...
}

@Entity public class Phone {
  @Id int phoneId;
  String areaCode;
  String localNumber;
  String internalExtension;
  PhoneType phoneType;
  @ManyToOne Account billedTo;
  ...
}
 
If I want to query for the accounts to which the office phones for employees at Sun's Santa Clara campus (zipcode 95054) are billed, I might write a query like this:

SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo.phones p
WHERE e.contactInfo.address.zipcode = '95054' AND p.phonetype = PhoneType.OFFICE

In this query, we're navigating across the ContactInfo embeddable in the FROM clause to the Phones relation, as well as navigating into the ContactInfo embeddable in the WHERE clause to extract the zipcode.

Note that in the FROM clause, navigation behaves like an inner join. Thus, for example, in the following query, if there is no contact information specified for some employees, those employees will not appear in the query result.

SELECT e
FROM Employee e JOIN e.contactInfo.phones p

Identification variables can reference embeddables in the FROM clause, and JOIN can also be used in the FROM clause to navigate over embeddables, so we could also have written this query as follows:

SELECT e
FROM Employee e JOIN e.contactInfo c JOIN c.phones p

   
Maps

To support our generalized map functionality, we've added the KEY and VALUE operators for extracting map keys and map values.  By default, an identification value that refers to an association of type Map denotes the map value, so, strictly speaking, the VALUE operator serves mainly the purpose of documentation.

For example, suppose we have a map from photo name to file name:
 
@Entity public class PictureCategory {
    @Id String name;
    @ElementCollection Map<String, String> photos;
    ...
}

To search for my egret photos,  I can write:

SELECT VALUE(p)
FROM PictureCategory c JOIN c.photos p
WHERE c.name = 'birds' AND KEY(p) LIKE '%egret%'

The following query is equivalent:

SELECT p
FROM PictureCategory c JOIN c.photos p
WHERE c.name = 'birds' AND KEY(p) LIKE '%egret%'

Let's look at another example.  This one keys the map on an entity type:

@Entity public class VideoStore {
  @Id int id;
  String name;
  Address location;
  @ElementCollection Map<Movie, Integer> videoInventory;
  ...
}

@Entity public class Movie {
  @Id int id;
  String title;
  String director;
  @ManyToMany Set<Actor> stars;
  ...
}

In the language of the spec, KEY(p) and VALUE(p) are "general identification variables"—which means that they can be used for further navigation. Thus, to search for the movie Vertigo near Sun's Santa Clara campus, I can write:

SELECT v.location.street
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054' AND KEY(i).title = 'Vertigo' AND VALUE(i) > 0

Map entries don't support navigation, but they can be selected.  The result is returned as an instance of type java.util.Map.Entry.

SELECT v.location.street, ENTRY(i)
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054' AND KEY(i).director = 'Hitchcock' AND VALUE(i) > 0


Ordered Lists

Ordered lists were another frequently-requested feature for which we added O/R mapping support in the Early Draft.

JPA 1.0 supported List as a collection type, but this support did not include support for maintaining a persistent ordering.  In the JPA 2.0 Early Draft, we added the ability to specify the OrderColumn annotation, which means that the persistence provider is required to maintain the list ordering using a separate column when you manipulate the order of the list.  The JPQL INDEX operator allows you to query over the ordering.

For example, suppose a department manager wants to maintain a rank ordering of employees for the purpose of issuing raises.  We might have:

@Entity public class Employee {
  @Id int empId;
  String name;
  @ManyToOne Department dept;
  ...
}

@Entity
public class Department {
  @Id int deptId;
  String name;
  @OneToMany(mappedBy="dept") @OrderColumn List<Employee> members;
  ...
}

The following query retrieves the top employees in the marketing department.

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing' AND INDEX(e) < 5


Non-polymorphic Queries

Another feature that we've added to JPQL is support for non-polymorphic queries.

The TYPE operator allows you to select an entity's type and to restrict a query to one or more entity types.

Thus, if we have an Employee hierarchy with FullTimeEmployee, PartTimeEmployee, and Contractor subtypes, we can write queries such as the following:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name = 'Marketing' AND TYPE(e) IN (PartTimeEmployee, Contractor)

PartTimeEmployee and Contractor are entity names.  (Recall that a entity name, by default, corresponds to the unqualified name of the entity class.)

The IN operator has also been extended to accept collection-valued parameters. Thus, you can also write the following:

SELECT e
FROM Employee e
WHERE TYPE(e) IN :empTypes

The argument that should be passed to this query is a collection of class objects.

An entity type can also be returned as the query result, as in the following.

SELECT TYPE(e), e.name
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing'

When the query is executed, the employee type is returned as a class object.


Case Expressions

Another addition to the query language is support for case expressions.  Both simple and general forms of CASE expressions are supported, as with SQL.  We now also support the NULLIF and COALESCE operators.

Here's an example of the CASE operator.  The value of the first CASE clause whose WHEN predicate is satisfied is returned.

SELECT c, CASE WHEN c.annualSpending > 10000 THEN 'Premier'
               WHEN c.annualSpending >  5000 THEN 'Gold'
               WHEN c.annualSpending >  2000 THEN 'Silver'
               ELSE 'Bronze'
          END
FROM Customer c

This example illustrates the simple form of CASE in a bulk UPDATE statement:

UPDATE EMPLOYEE e
SET e.salary = CASE e.rating WHEN 1 THEN e.salary \* 1.1
                             WHEN 2 THEN e.salary \* 1.05
                             ELSE e.salary \* 1.001
               END

The NULLIF operator is useful particularly when the database encoding for missing or inapplicable information is represented in ways other than by nulls.  You may run across this when mapping to legacy databases.  Using NULLIF allows you to easily convert such values to nulls in queries.  If the arguments to NULLIF are equal, NULLIF returns null; otherwise it returns the value of the first argument.

Here's an example.  Let's suppose that salaries are represented as integer values and that missing salaries are encoded by -99999.

SELECT AVG(NULLIF(e.salary, -99999))
FROM Employee e


Scalar Expressions in the SELECT clause

While JPA 1.0 supported scalar expressions in the WHERE clause, it did not support them in the SELECT clause.    As the above queries illustrate, this frequently-requested feature has now also been added.


When you download the draft and review the spec, you'll notice that it flags a number of open issues and items that are in need of developer feedback.   Please feel free to send me and the expert group your thoughts at  jsr-317-pdr-feedback@sun.com.    Thanks!



Friday May 02, 2008

Java Persistence 2.0 Early Draft Available

Java Persistence 2.0 Early Draft We've just released the Early Draft of  the Java Persistence 2.0 specification, so I thought it would be a good time to report on some of the work we've been doing in the JSR 317 Expert Group.

For those of you who are not already familiar with JSR 317 and some of the areas we're planning to address, these are some of the highlights of the JSR-317 agenda:
  • Expanded O/R mappings
  • Improved domain modeling capabilities
  • Additions to the Java Persistence query language
  • An API for criteria queries
  • Standardization of configuration hints
  • Additional contracts for detached entities and extended persistence contexts
  • Support for validation via integration with the work of JSR 303

One of my own goals for Java Persistence 2.0 is what I think of in terms of "solidifying the standard".  Java Persistence 1.0 was a great start and has gotten an extremely positive reception from the community, but—naturally enough for a 1.0 release—it lacks some important features, which has caused users to rely on non-portable vendor extensions.

Java Persistence 2.0 is intended to flesh out the standard with a number of important features requested from the developer community and to give you access to richer functionality while reducing the need for dependence on non-portable code or metadata.

The Early Draft focuses on the areas of object/relational mapping, domain modeling improvements, and some enhancements to the EntityManager and Query APIs.  After JavaOne, we'll be shifting over to the query language area as the focus of our next draft.

Many of the improvements you'll see have been strongly motivated by feedback and requests we have received from developers.  You, the users of this technology, have a significant  influence over our decisions and affect the prioritization of our work by the input and feedback you provide us.


Here are some of the highlights of what we've added in terms of O/R mapping and domain modeling improvements:

Better support for modeling using common Java language datatypes, including embeddable types (e.g., non-entity dependent object types such as Address) and collections of basic types such as String, Integer, etc.  In Java Persistence 1.0, explicitly-mapped collection types (i.e., collections that could be mapped into their own relational tables) were limited to entities, and you either had to store collections of embeddables and basic types in serialized form or rely on vendor-specific metadata.  For embeddable types, in additional to support for collections of embeddables, we've also added support for multiple levels of embedding and for the use of relationships in embeddable classes.

Another frequently requested feature has been support for persistently ordered lists.  Java Persistence 1.0 supports the use of a @OrderBy functionality that allows you to retrieve relationships that are ordered by various attributes as they are retrieved from the database.  This is a very natural approach from a relational point of view.  However, in Java Persistence 2.0 we've also added an @OrderColumn which the persistence provider must use to maintain whatever ordering you assign.  While some of us have a few misgivings about this feature from a database point of view, we have received so many requests for it that we have now added it to the spec.

We've also made support for Maps much more general.  In Java Persistence 1.0, the value of a map needs to be an entity and the key either its primary key or another of its attributes.  In Java Persistence 2.0, the map key and map value can each independently be a basic type, an embeddable, or an entity. This therefore gives support for the modeling of ternary relationships in the database as well as for association tables with additional state.

We've also expanded the supported relational mapping options for existing relationship types.  Unidirectional one-to-many relationships can now be mapped via foreign key mappings (in additional to the join table mappings that are currently supported);  and one-to-one and many-to-one relationships can be mapped via join tables.

Also in terms of relationship modeling, we've now also added support for an "orphan deletion" facility for one-to-many and one-to-one relationships—another feature for which we've gotten quite a few requests.

One of the points of confusion among developers using Java Persistence 1.0 has been how to map database relationships that have overlapping primary and foreign keys.  We've improved this area with the addition of what we've termed "derived identities".  This mechanism allows you to specify the correspondence between relationships (mapped with foreign keys) and primary key attibutes.

Finally,  we've given more flexibility in the combination of access types, both within an entity hierarchy and within a single entity class.


We've also added a number of enhancements to the programmatic APIs:

We have expanded the concurrency control options to include support for pessimistic as well as optimistic locking.  This gives you the ability to grab upfront database locks on selected entities and associated entity data.  In conjunction, we have expanded the APIs that can be used for locking.  Both pessimistic and optimistic locks can now be obtained via the find and refresh methods (in addition to the lock method), as well as through Java Persistence query language queries (both statically and dynamically).

Standardized hints and properties have also been added in support of locking and for other configuration.  Further standardization of hints and properties will occur as we go forward.

Other additions to the programmatic APIs include basic support for second-level cache control as well as a number of additional methods to facilitate tooling.


Send us your input:

When you read the draft, you'll see that there are a number of "Open Issues" that are flagged in the draft, as well as other "Notes to readers" soliciting feedback.   Please send your feedback and comments on this Early Draft and further feature requests to jsr-317-edr-feedback@sun.com.


 

About

Linda DeMichiel is the Specification Lead for Java EE 7 and for Java Persistence 2.1, and a member of the Java Platform, Enterprise Edition team at Oracle. She was formerly the Specification Lead for JPA 2.0 and for EJB 3.0.

Search

Top Tags
Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today