Named queries have some nice properties. They are precompiled and therefore faster than their “normal” counterparts, encourage to use named parameters, make your code easier to read and avoid messing up your code with string concatenated queries. A nice addon is, that named queries are validated during the creation of the persistence unit. If you have a unit test (see unit test db schema and named queries), checking whether the entities represent a valid DB Schema, the named queries are validated, too. So, there will be syntactical as well as some static analysis (e.g. “exist all referenced entities?”) during the test phase, before your application is even packaged.
Named queries may be added via the annotation
@NamedQuery at any entity of your persistence unit. Unfortunately this is not the place where it is welcome, because entities are generally part of the API of an EJB application and should not contain any business logic. This makes the API vulnerable to frequent changes, which is undesirable. Surely, you may add an interface layer on top of your entities, so that your API has no compile time dependencies to them, but at runtime they have to be on the classpath since in contrast to session beans, RMI does not use proxy objects for parameters, but serialization and deserialization (in the case of remote beans). Furthermore this suppresses only the symptoms.
Another disadvantage is, that a named query can be assigned to an entity, only. There is no other managed class, which can be used for this purpose. Often queries refer to more than one entity or a bunch of queries has a common purpose, so that they should be grouped together.
Fortunately, like almost everything that can be configured via annotations in EJB 3.0 may be overriden via XML configurations. Although XML seems to be more and more discouraged for static configuration of Java code this comes in handy in this scenario. You may add an arbitrary count of XML-files to the
persistence.xml of your persistence unit. Every configuration file contains a group of JPA-Queries. The choice of groups is up to the developer. Your
persistence.xml should look like this:
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="myPersistenceUnit"> <jta-data-source>java:/myDataSource</jta-data-source> <!-- Named JPQL queries --> <mapping-file>myApp/jpql/products.xml</mapping-file> <mapping-file>myApp/jpql/clients.xml</mapping-file> </persistence-unit> </persistence>
myApp/jpql/products.xml contains the named queries:
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings version="1.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"> <named-query name="products.getProductsByClientId"> <query> SELECT product FROM Product product, IN(product.client) client WHERE client.id = :clientId </query> </named-query> <!-- place more queries here --> </entity-mappings>
The names of the queries are all in one global namespace, so that they should be prefixed in order to be assured that they are unique. The mapping files can be situated anywhere on the classpath but should be uniquely named (full path) since otherwise changes to the order of classpath entries can lead to unexpected behavior.
This blog post was inspired by Where to put named queries.