Jump to: navigation, search

Difference between revisions of "EclipseLink/Release/2.5/JPA21"

(Simple Result Set example)
(In parameter Example)
Line 63: Line 63:
 
=== In parameter Example ===
 
=== In parameter Example ===
  
Procedure definition:
+
Stored procedure definition in MySQL:
  
 
     CREATE PROCEDURE Update_Address_Postal_Code (new_p_code_v VARCHAR(255), old_p_code_v VARCHAR(255))
 
     CREATE PROCEDURE Update_Address_Postal_Code (new_p_code_v VARCHAR(255), old_p_code_v VARCHAR(255))

Revision as of 08:04, 14 May 2013

EclipseLink JPA 2.1

This page contains a summary of the major features supported in EclipseLink that implements the JPA 2.1 (JSR 338) specification requirements. The features and examples on this page do not represent a complete list. For more information, please see: the JSR 338 page.


Bulk Update

Until JPA 2.1, performing deletes or updates was not available using the Criteria API. Through the addition of CriteriaUpdate/CriteriaDelete classes, support for bulkupdate/delete queries has now been added.

Update Example

The following example will update the salary and status, of all Employees who make less than 10000$, and give them a raise.

  CriteriaUpdate<Employee> q = cb.createCriteriaUpdate(Employee.class);
  Root<Employee> emp = q.from(Employee.class);
  .set(e.get(Employee_.salary), cb.prod(e.get(Employee_.salary), 1.1f))
  .set(e.get(Employee_.status), "full_time")
  .where(cb.lt(emp.get(Emploee_.salary), 10000));


The following Java Persistence query language update statement is equivalent.

  UPDATE Employee e SET e.salary = e.salary * 1.1, e.status = "full_time" WHERE e.salary < 10000


Delete Example

The folowwing example deletes all the PhoneNumbers that are no longer in service

  CriteriaDelete<PhoneNumber> q = cb.createCriteriaDelete(PhoneNumber.class);
  Root<PhoneNumber> p = q.from(PhoneNumber.class);
  q.where(cb.equal(p.get(PhoneNumber_.status), "out_of_service"),


The following Java Persistence query language delete statement is equivalent.

  DELETE FROM PhoneNumber p
    WHERE p.status = 'out_of_service'

Stored Procedures

JPA specification 2.1 has introduced support for executing Stored Procedure calls. This includes a new StoredProcedureQuery API and Named Stored Procedure Queries (pre-existing portions of code on the database).

All the stored procedure examples below assume stored procedures already exist on the DB. Stored procedure creation is performed differently on different Databases. All the following example Stored procedure creation is using MySQL syntax (unless otherwise specified).


Simple Result Set example

Stored procedure definition in MySQL:

   CREATE PROCEDURE getIds() BEGIN SELECT ID FROM CUSTOMER ORDER BY ID ASC; END !

Execution of Stored procedure:

   StoredProcedureQuery spq = em.createStoredProcedureQuery("getIds", Customer.class);
   List customers = spq.getResultList();

Alternatively, users can call spq.execute() directly (which is what getResultList() will call behind the scenes). The execute method will return a boolean indicating true if a result set is returned and false otherwise.

   boolean result = spq.execute();
   if (result == true) {
       customers = spq.getResultList();
   } else {
       // Handle the false for no result set returned, e.g.
       throw new RuntimeException("No result set(s) returned from the stored procedure"); 
   }

In parameter Example

Stored procedure definition in MySQL:

   CREATE PROCEDURE Update_Address_Postal_Code (new_p_code_v VARCHAR(255), old_p_code_v VARCHAR(255))
     BEGIN 
       UPDATE ADDRESS SET P_CODE = new_p_code_v WHERE P_CODE = old_p_code_v; 
     END

Build the query:

   StoredProcedureQuery spq = em.createStoredProcedureQuery("Update_Address_Postal_Code");
   spq.registerStoredProcedureParameter("new_p_code_v", String.class, ParameterMode.IN);
   spq.registerStoredProcedureParameter("old_p_code_v", String.class, ParameterMode.IN);

Execute the query:

   spq.setParameter("new_p_code_v", "123 NEW");
   spq.setParameter("old_p_code_v", "321 OLD");
   int updateCount = spq.executeUpdate();

Alternatively, the user could call the execute method directly (also note the parameters can be chained):

   spq.setParameter("new_p_code_v", "123 NEW").setParameter("old_p_code_v", "321 OLD").execute();
   int updateCount = spq.getUpdateCount();

OUT parameter Example

Procedure definition:

   CREATE PROCEDURE Read_Address_City (address_id_v INTEGER, OUT city_v VARCHAR(255))
     BEGIN 
       SELECT CITY INTO city_v FROM ADDRESS WHERE (ADDRESS_ID = address_id_v); 
     END

Build the query:

   StoredProcedureQuery query = em.createStoredProcedureQuery("Read_Address_City");
   query.registerStoredProcedureParameter("address_id_v", Integer.class, ParameterMode.IN);
   query.registerStoredProcedureParameter("city_v", String.class, ParameterMode.OUT);

Execute the query:

   boolean resultSet = query.setParameter("address_id_v", "1").execute();
   
   if (resultSet) {
       // Result sets must be processed first through getResultList() calls.
   } 
   
   // Once the result sets and update counts have been processed, output parameters are available for processing.
   String city = (String) query.getOutputParameterValue("city_v");

Ref cursor Example

Build the query:

   StoredProcedureQuery query = em.createStoredProcedureQuery("Read_Using_Sys_Cursor", Employee.class);
   query.registerStoredProcedureParameter("f_name_v", String.class, ParameterMode.IN);
   query.registerStoredProcedureParameter("p_recordset", void.class, ParameterMode.REF_CURSOR);

Execute the query:

   query.setParameter("f_name_v", "Fred");                
   boolean execute = query.execute();
   List<Employee> employees = (List<Employee>) query.getOutputParameterValue("p_recordset");

Named Stored Procedure Example

Named stored procedures are those that are specified through metadata and uniquely identified by name.

Procedure definition:

   CREATE PROCEDURE Read_Address (address_id_v INTEGER)
     BEGIN 
       SELECT ADDRESS_ID, STREET, CITY, COUNTRY, PROVINCE, P_CODE FROM ADDRESS WHERE (ADDRESS_ID = address_id_v); 
     END

Annotation Example

   @NamedStoredProcedureQuery(
       name = "ReadAddressByID",
       resultClasses = Address.class,
       procedureName = "Read_Address",
       parameters = {
           @StoredProcedureParameter(mode=IN, name="address_id_v", type=Integer.class)
       }
   )
   public Address() {
       ....
   }

XML Example

   <named-stored-procedure-query name="ReadAddressByID" procedure-name="Read_Address">
       <parameter name="address_id_v" mode="IN" class="Integer"/>
       <result-class>Address</result-class>
   </named-stored-procedure-query>

Execution

   EntityManager em = createEntityManager();
   em.createNamedStoredProcedureQuery("ReadAddressByID").setParameter("address_id_v", 1).getSingleResult();

A more complex example with multiple result sets using sql result set mappings

Procedure definition:

   CREATE PROCEDURE Read_Multiple_Result_Sets ()
     BEGIN 
       SELECT E.*, S.* FROM EMPLOYEE E, SALARY S WHERE E.EMP_ID = S.EMP_ID; 
       SELECT A.* FROM ADDRESS A; 
       SELECT (t1.BUDGET/t0.PROJ_ID) AS BUDGET_SUM, t0.PROJ_ID, t0.PROJ_TYPE, t0.PROJ_NAME, t0.DESCRIP, t0.LEADER_ID, t0.VERSION, t1.BUDGET, t2.PROJ_ID AS SMALL_ID,
        t2.PROJ_TYPE AS SMALL_DESCRIM, t2.PROJ_NAME AS SMALL_NAME, t2.DESCRIP AS SMALL_DESCRIPTION, t2.LEADER_ID AS SMALL_TEAMLEAD, t2.VERSION AS SMALL_VERSION FROM
        PROJECT t0, PROJECT t2, LPROJECT t1 WHERE t1.PROJ_ID = t0.PROJ_ID AND t2.PROJ_TYPE='S'; 
       SELECT t0.EMP_ID, t0.F_NAME, t0.L_NAME, COUNT(t2.DESCRIPTION) AS R_COUNT FROM EMPLOYEE t0, RESPONS t2, SALARY t1 WHERE ((t1.EMP_ID = t0.EMP_ID) AND (t2.EMP_ID 
        = t0.EMP_ID)) GROUP BY t0.EMP_ID, t0.F_NAME, t0.L_NAME; 
     END

Build the query:

This is one example (of many) on how to configure such a query. Queries and result set mappings can be defined solely in annotations or xml or a mix of both. All the metadata can be defined on a single class or split up across many.

   @NamedStoredProcedureQuery(
       name="ReadUsingMultipleResultSetMappings",
       procedureName="Read_Multiple_Result_Sets",
       resultSetMappings={"EmployeeResultSetMapping", "AddressResultSetMapping", "ProjectResultSetMapping", "EmployeeConstructorResultSetMapping"}
   )
   
   @SqlResultSetMappings({
       @SqlResultSetMapping(
           name = "EmployeeResultSetMapping",
           entities = {
               @EntityResult(entityClass = Employee.class)
           }
       ),
       @SqlResultSetMapping(
           name = "EmployeeConstructorResultSetMapping",
           classes = { 
               @ConstructorResult(
                   targetClass = EmployeeDetails.class,
                   columns = {
                       @ColumnResult(name="EMP_ID", type=Integer.class),
                       @ColumnResult(name="F_NAME", type=String.class),
                       @ColumnResult(name="L_NAME", type=String.class),
                       @ColumnResult(name="R_COUNT", type=Integer.class)
                   }
               )
           }
       )
   })
   public Employee(){
       ....
   }
   @SqlResultSetMapping(
       name = "ProjectResultSetMapping",
       columns = {
           @ColumnResult(name = "BUDGET_SUM")
       },
       entities = {
           @EntityResult(
               entityClass = Project.class
           ),
           @EntityResult(
               entityClass = SmallProject.class,
               fields = {
                   @FieldResult(name = "id", column = "SMALL_ID"),
                   @FieldResult(name = "name", column = "SMALL_NAME"),
                   @FieldResult(name = "description", column = "SMALL_DESCRIPTION"),
                   @FieldResult(name = "teamLeader", column = "SMALL_TEAMLEAD"),
                   @FieldResult(name = "version", column = "SMALL_VERSION")
               },
               discriminatorColumn="SMALL_DESCRIM"
           )
       }
   )
   public Project() {
       ....
   }
   @SqlResultSetMapping(
       name = "AddressResultSetMapping",
       entities = {
           @EntityResult(entityClass = Address.class)
       }
   )
   public Address() {
       ....
   }

Execute the query:

   StoredProcedureQuery spq = createEntityManager().createNamedStoredProcedureQuery("ReadUsingMultipleResultSetMappings");
           
   // Read the first result set mapping --> Employee
   List employeeResults = spq.getResultList();
   
   // Read second result set mapping --> Address
   assertTrue("Address results not available", spq .hasMoreResults());
   List addressResults = spq.getResultList();
           
   // Read third result set mapping --> Project
   assertTrue("Projects results not available", spq .hasMoreResults());
   List projectResults = spq.getResultList();
           
   // Read fourth result set mapping --> Employee Constructor Result
   assertTrue("Employee constructor results not available", spq .hasMoreResults());
   List employeeConstructorResults = spq.getResultList();
           
   // Verify there as no more results available
   assertFalse("More results available", spq.hasMoreResults());

JPQL function

The SQL spec and many databases have SQL functions that are not covered by the JPA specification. With the latest JPA specification the ability to call generic SQL functions was added to the JPQL syntax. The function keyword may be used to invoke predefined functions or used defined functions.

   SELECT e
     FROM Employee e
     WHERE FUNCTION(‘isLongTermEmployee’, e.startDate)

CDI Entity Listeners

Entity Listeners now support the Contexts and Dependency Injection API (CDI) when used inside a Java EE container. This support allows entity listeners to use CDI to inject objects and also provides support for @PostConstruct and @PreDestroy method calls.

CDI Example

The following example shows how a SessionBean can be injected into an EntityListener

 public class LoggerEntityListener {
   
   @EJB
   protected LoggerBean logger;
   
   @PrePersist
   public void prePersist(Object object) {
       logger.log("prepersist", object);
   }
   
   @PostPersist
   public void postPersist(Object object){
       logger.log("postpersist", object);
   }
   
   @PreDestroy
   public void preDestroy(){
       logger.close();
   }
   
   @PostConstruct
   public void postConstruct(){
       logger.initialize();
   }
 }
 @Entity
 @EntityListeners({LoggerEntityListener.class})
 public class MyLoggedEntity {
   ...
 }

Treat

Allows relationship joins to be treated as a subclass of the join type.

Converters

Provides control over the conversion from an attribute type and value to the corresponding database type and value

DDL generation

In previous versions for JPA, although DDL generation was present it was not standardized or required. JPA 2.1 has added standardized provider DDL generation and made DDL generation a requirement.

Entity Graphs

Entity graphs are a means to specify the structure of a graph of entities using entity model metadata. This entity graph consists of representations of attributes and in the case of multi-node entity graphs additional entity graphs to represent the related entities. An entity graph can be specified through annotations:

   @NamedEntityGraph(
       name="ExecutiveProjects"
       attributeNodes={
           @NamedAttributeNode("address"),
           @NamedAttributeNode(value="projects", subgraph="projects")
       },
       subgraphs={
           @NamedSubgraph(
               name="projects",
               attributeNodes={@NamedAttributeNode("properties")}
           ),
           @NamedSubgraph(
               name="projects",
               type=LargeProject.class,
               attributeNodes={@NamedAttributeNode("executive")}
           )
       }
   )

and later retrieved by name:

   EntityGraph employeeGraph = em.getEntityGraph("ExecutiveProjects");

Entity graphs can be created dynamically from scratch:

   EntityGraph employeeGraph = em.createEntityGraph(Employee.class);
   employeeGraph.addAttributeNodes("address");
   employeeGraph.addSubgraph("projects").addAttributeNodes("properties");
   employeeGraph.addSubgraph("projects", LargeProject.class).addAttributeNodes("executive");

or created from an existing named entity graph:

   EntityGraph employeeGraph = em.createEntityGraph("ExecutiveProjects");
   employeeGraph.addSubgraph("period").addAttributeNodes("startDate");

Once constructed or retrieved the entity graphs can then be used as templates for certain EntityManager operations like load and fetch. For instance applying the entity graph as a fetch graph through a query hint will cause EclipseLink to only load those attributes present in the entity graph and unlisted attributes would become fetchType=LAZY.

   EntityGraph employeeGraph = em.getEntityGraph("ExecutiveProjects");
   Employee result = (Employee) em.createQuery("Select e from Employee e").setHint("javax.persistence.fetchgraph", employeeGraph).getResultList().get(0);
   PersistenceUnitUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil();
   assertFalse(util.isLoaded(result, "firstName"));
   assertFalse(util.isLoaded(result, "department"));
   assertTrue(util.isLoaded(result, "projects"));

The entity graph can also be used to force and entity subgraph to be loaded at query time with the query hint "javax.persistence.loadgraph" . When a load graph is applied all listed attributes will be loaded by the query and any unlisted attributes will be loaded based on their mapping fetchType settings.

   EntityGraph employeeGraph = em.getEntityGraph("ExecutiveProjects");
   Employee result = (Employee) em.createQuery("Select e from Employee e").setHint("javax.persistence.loadgraph", employeeGraph).getResultList().get(0);
   PersistenceUnitUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil();
   assertTrue(util.isLoaded(result, "firstName"));
   assertFalse(util.isLoaded(result, "department"));
   assertTrue(util.isLoaded(result, "projects"));