Oracle® Application Server TopLink Application Developer's Guide
10g Release 2 (10.1.2) Part No. B15901-01 |
|
Previous |
Next |
You can optimize certain read and write operations in an OracleAS TopLink application. To optimize reading, you can tune:
The amount of data read from the database
The way OracleAS TopLink queries data on the database
OracleAS TopLink provides the read optimization features listed in Table 10-2.
Table 10-2 Read Optimization Features
An application may ask the user to choose an element from a list. Because the list displays only a subset of the information contained in the objects, it is not necessary to query for all information for objects from the database.
Partial object reading and report query are two OracleAS TopLink features that optimize these types of operations. They enable you to query only the information required to display the list. The user can then select an object from the list.
Partial object reading is a query designed to extract only the required information from a selected record in a database, rather than all the information the record contains. Because partial object reading does not fully populate objects, you can neither cache nor edit partially-read objects. Also note that the primary key is required to re-query the object (so it can be edited, for example). OracleAS TopLink does not automatically include the primary key information in a partially populated object. If you want to edit the object, specify the primary key as a required partial attribute.
In Example 10-5, the query builds complete employee objects, even though the list displays only employee last names. With no optimization, the query reads employee data.
Example 10-5 No Optimization
/* Read all the employees from the database, ask the user to choose one and return it. This must read in all the information for all the employees.*/ List list; // Fetch data from database and add to list box. Vector employees = (Vector) session.readAllObjects(Employee.class); list.addAll(employees); // Display list box. .... // Get selected employee from list. Employee selectedEmployee = (Employee) list.getSelectedItem(); return selectedEmployee;
Example 10-6 demonstrates the use of partial object reading. It reads only the last name and primary key for the employees. This reduces the amount of data read from the database.
Example 10-6 Optimization Through Partial Object Reading
/* Read all the employees from the database, ask the user to choose one and return it. This uses partial object reading to read just the last name of the employees. Note that OracleAS TopLink does not automatically include the primary key of the object. If this is needed to select the object for a query, it must be specified as a partial attribute so that it can be included. In this way, the object can easily be read for editing. */ List list; // Fetch data from database and add to list box. ReadAllQuery query = new ReadAllQuery(Employee.class); query.addPartialAttribute("lastName"); /* OracleAS TopLink does not automatically include the primary key of the object. If this is needed to select the object for a query, it must be specified as a partial attribute so that it can be included.*/ query.addPartialAttribute("id"); // The next line avoids a query exception query.dontMaintainCache(); Vector employees = (Vector) session.executeQuery(query); list.addAll(employees); // Display list box. .... // Get selected employee from list. Employee selectedEmployee = (Employee)session.readObject(list.getSelectedItem()); return selectedEmployee;
Report query enables you to retrieve data from a set of objects and their related objects. Report query supports database reporting functions and features.
For more information, see "ReportQuery".
Example 10-7 demonstrates the use of report query to read only the last name of the employees. This reduces the amount of data read from the database compared to the code in Example 10-5, and avoids instantiating employee instances.
Example 10-7 Optimization Through Report Query
/* Read all the employees from the database, ask the user to choose one and return it. This uses the report query to read just the last name of the employees. It then uses the primary key stored in the report query result to read the real object.*/ List list; // Fetch data from database and add to list box. ExpressionBuilder builder = new ExpressionBuilder(); ReportQuery query = new ReportQuery (Employee.class, builder); query.addAttribute("lastName"); query.retrievePrimaryKeys(); Vector reportRows = (Vector) session.executeQuery(query); list.addAll(reportRows); // Display list box. .... // Get selected employee from list. ReportQueryResult result = (ReportQueryResult) list.getSelectedItem(); Employee selectedEmployee = (Employee) result.readobject(Employee.Class,session);
Although the differences between the unoptimized example (Example 10-5) and the report query optimization in Example 10-7 appear to be minor, report queries offer a substantial performance improvement.
The way your application reads data from the database affects performance. For example, reading a collection of rows from the database is significantly faster than reading each row individually.
A common performance challenge is to read a collection of objects that have a one-to-one reference to another object. This normally requires one read operation to read in the source rows, and one call for each target row in the one-to-one relationship.
To reduce the number of reads required, use join and batch reading. Example 10-8 illustrates the unoptimized code required to retrieve a collection of objects with a one-to-one reference to another object. Example 10-9 and Example 10-10 illustrate the use of joins and batch reading to improve efficiency.
Example 10-8 No Optimization
/* Read all the employees, and collect their address' cities. This takes N + 1 queries if not optimized. */ // Read all the employees from the database. This requires 1 SQL call. Vector employees = session.readAllObjects(Employee.class,new ExpressionBuilder().get("lastName").equal("Smith")); //SQL: Select * from Employee where l_name = 'Smith' // Iterate over employees and get their addresses. // This requires N SQL calls. Enumeration enum = employees.elements(); Vector cities = new Vector(); while(enum.hasMoreElements()) Employee employee = (Employee) enum.nextElement(); cities.addElement(employee.getAddress().getCity()); //SQL: Select * from Address where address_id = 123, etc }
Example 10-9 Optimization Through Joining
/* Read all the employees, and collect their address' cities. Although the code is almost identical because joining optimization is used it only takes 1 query. */ // Read all the employees from the database, using joining. // This requires 1 SQL call. ReadAllQuery query = new ReadAllQuery(); query.setReferenceClass(Employee.class); query.setSelectionCriteria(new ExpressionBuilder().get("lastName").equal("Smith")); query.addJoinedAttribute("address"); Vector employees = session.executeQuery(query); /* SQL: Select E.*, A.* from Employee E, Address A where E.l_name = 'Smith' and E.address_id = A.address_id Iterate over employees and get their addresses. The previous SQL already read all the addresses so no SQL is required. */ Enumeration enum = employees.elements(); Vector cities = new Vector(); while (enum.hasMoreElements()) { Employee employee = (Employee) enum.nextElement(); cities.addElement(employee.getAddress().getCity());
Example 10-10 Optimization Through Batch Reading
/* Read all the employees, and collect their address' cities. Although the code is almost identical because batch reading optimization is used it only takes 2 queries. */ // Read all the employees from the database, using batch reading. // This requires 1 SQL call, note that only the employees are read. ReadAllQuery query = new ReadAllQuery(); query.setReferenceClass(Employee.class); query.setSelectionCriteria(new ExpressionBuilder().get("lastName").equal("Smith")); query.addBatchReadAttribute("address"); Vector employees = (Vector)session.executeQuery(query); // SQL: Select * from Employee where l_name = 'Smith' // Iterate over employees and get their addresses. // The first address accessed will cause all the addresses to be read in a single SQL call. Enumeration enum = employees.elements(); Vector cities = new Vector(); while (enum.hasMoreElements()) { Employee employee = (Employee) enum.nextElement(); cities.addElement(employee.getAddress().getCity()); // SQL: Select distinct A.* from Employee E, Address A where E.l_name = 'Smith' and E.address_id = A.address_i }
Because the two-phase approach to the query (Example 10-9 and Example 10-10) accesses the database only twice, it is significantly faster than the approach illustrated in Example 10-8.
Joins offer a significant performance increase under most circumstances. Batch reading offers further performance advantage because it allows for delayed loading through valueholders, and has much better performance where the target objects are shared.
For example, if employees in Example 10-8, Example 10-9, and Example 10-10 live at the same address, batch reading reads much less data than joining, because batch reading uses a SQL DISTINCT
call to filter duplicate data. Batch reading is also available for one-to-many relationships, but joining is available only for one-to-one relationships.
OracleAS TopLink provides a high-level query mechanism. However, if your application requires a complex query, a direct SQL call may be the best solution.
For more information about executing SQL calls, see "Custom SQL".
Some application operations require information from several objects rather than from just one. This can be both difficult to implement, and resource intensive. Example 10-11 illustrates unoptimized code that reads information from several objects.
Example 10-11 No Optimization
/* Gather the information to report on an employee and return the summary of the information. In this situation a hashtable is used to hold the report information. Notice that this reads a lot of objects from the database, but uses very little of the information contained in the objects. This may take 5 queries and read in a large number of objects. */ public Hashtable reportOnEmployee(String employeeName) { Vector projects, associations; Hashtable report = new Hashtable(); // Retrieve employee from database. Employee employee = session.readObject(Employee.class, new ExpressionBuilder.get("lastName").equal(employeeName)); // Get all the projects affiliated with the employee. projects = session.readAllObjects(Project.class, "SELECT P.* FROM PROJECT P, EMPLOYEE E WHERE P.MEMBER_ID = E.EMP_ID AND E.L_NAME = " + employeeName); // Get all the associations affiliated with the employee. associations = session.readAllObjects(Association.class, "SELECT A.* FROM ASSOC A, EMPLOYEE E WHERE A.MEMBER_ID = E.EMP_ID AND E.L_NAME = " + employeeName); } report.put("firstName", employee.getFirstName()); report.put("lastName", employee.getLastName()); report.put("manager", employee.getManager()); report.put("city", employee.getAddress().getCity()); report.put("projects", projects); report.put("associations", associations); return report; }
To improve application performance in these situations, define a new read-only object to encapsulate this information, and map it to a view on the database. To set the object to be read-only, use the addDefaultReadOnlyClass()
API in the oracle.toplink.sessions.Project
class.
Example 10-12 Optimization Through View Object
CREATE VIEW NAMED EMPLOYEE_VIEW AS (SELECT F_NAME = E.F_NAME, L_NAME = E.L_NAME,EMP_ID = E.EMP_ID, MANAGER_NAME = E.NAME, CITY = A.CITY, NAME = E.NAME FROM EMPLOYEE E, EMPLOYEE M, ADDRESS A WHERE E.MANAGER_ID = M.EMP_ID AND E.ADDRESS_ID = A.ADDRESS_ID)
Define a descriptor for the EmployeeReport
class:
Define the descriptor normally, but specify tableName
as EMPLOYEE_VIEW
.
Map only the attributes required for the report. In the case of numberOfProjects
and associations, use a transformation mapping to retrieve the required data.
You can now query the report from the database as with any other OracleAS TopLink-enabled object.
Example 10-13 View the Report from Example 10-12
/* Return the report for the employee.*/ public EmployeeReport reportOnEmployee(String employeeName) { EmployeeReport report; report = (EmployeeReport) session.readObject(EmployeeReport.class, new ExpressionBuilder.get("lastName").equal(employeeName)); return report;}