Normally, persistence-capable classes in TJDO are backed in the database by base
tables.
Each class has a corresponding base table, plus one additional base table for
each normal (i.e. non-inverse) Collection
or Map
field.
It is possible to define, via JDO metadata, that a class is to be backed by a database view rather than a base table. Views in JDO are useful for leveraging some SQL capabilities that would otherwise not be accessible through the JDO interface. The most common uses for views are:
GROUP BY
mechanism, or other SQL
features inaccessible through JDO.Instances of view objects behave very much differently than normal JDO instances. View objects:
Extent
.
However, an Extent
of view objects can be queried to further
filter or order the view data.identity-type="nondurable"
on the
<class>
element.
Non-durable identity in JDO means that each instance has a unique ID, but
that ID is not guaranteed to be the same from one access to the next, nor can
objects be retrieved by ID (getObjectById()).
Also, instances of view objects do not employ the "fetch-on-demand" strategy
normally used for fields of persistent objects, nor is the default fetch group
used; all persistent fields are always loaded.
The InstanceCallbacks jdoPostLoad()
and jdoPreClear()
will be called at the expected times, but jdoPreStore()
and jdoPreDelete()
will never be called as they do not apply.
A class is defined to use a view by including a view-definition
extension in its JDO metadata.
The text content of the extension element should contain the SQL CREATE
VIEW
statement(s) needed to create the view.
For example:
public class AvgSalaryByManager { private Manager manager; private double avgSalary; ... } <class name="AvgSalaryByManager" identity-type="nondurable"> <extension vendor-name="triactive" key="view-definition" value=" CREATE VIEW AVG_SALARY_BY_MANAGER ( MANAGER_ID, AVG_SALARY ) AS SELECT MANAGER_ID, AVG(SALARY) FROM EMPLOYEE GROUP BY MANAGER_ID" /> </class>
If necessary, the view definition text can contain multiple SQL statements separated by semi-colons. This might be needed e.g. if a view were defined in terms of other sub-views.
Given that a view definition makes direct SQL reference to other JDO base tables, it helps to know a little about how TJDO maps persistence-capable classes to database tables (see also schema management). The strategy is fairly simple:
Defining views obviously requires making direct reference to table and column names of JDO-managed base tables. However, the actual table and column names generated by TJDO can vary for a variety of reasons, including:
Therefore, view definitions support a rudimentary "macro" mechanism in order to
substitute (at view creation time) the actual generated table/column names into
the CREATE VIEW
statement(s).
For example, the preferred way to write the above view example would be:
<class name="AvgSalaryByManager" identity-type="nondurable"> <extension vendor-name="triactive" key="view-definition" value=" CREATE VIEW {this} ( {this.manager}, {this.avgSalary} ) AS SELECT {Employee.manager}, AVG({Employee.salary}) FROM {Employee} GROUP BY {Employee.manager}" /> </class>
A macro consists of a list of one, two, or three identifiers, separated by periods, and enclosed in braces {}. The supported macros are:
Macro | Substituted Text |
---|---|
{this} | Name of the view being defined |
{this.fieldName} | Column name for field fieldName in the view being defined |
{className} | Table name for class className |
{className.this} | ID column name for class className |
{className.fieldName} | Column name for field fieldName, or table name if field is a Collection or Map |
{className.collectionField.owner} | Owner column name for Collection field collectionField |
{className.collectionField.element} | Element column name for Collection field collectionField |
{className.mapField.owner} | Owner column name for Map field mapField |
{className.mapField.key} | Key column name for Map field mapField |
{className.mapField.value} | Value column name for Map field mapField |
In substitution macros, className can be a fully-qualified class
name, or an unqualified name if the class is within the same package as the
view being defined.
An "import" mechanism is also available, which works similarly to the way
declareImports()
works in a JDO query.
Imports are declared using an additional view-imports extension.
For example, if class Employee were in a different package we could
write:
<class name="AvgSalaryByManager" identity-type="nondurable"> <extension vendor-name="triactive" key="view-imports" value=" import com.triactive.jdo.examples.Employee;" /> <extension vendor-name="triactive" key="view-definition" value=" CREATE VIEW {this} ..." />
and still use the unqualified class name Employee
in substitution
macros.
Depending on the database vendor, a given view may need to be defined by different SQL statements. The definition given by the view-definition extension constitutes the default, or "generic" definition of that view. Additional vendor-specific view definitions can be provided by including additional extensions of the form view-definition-vendorID. TJDO currently recognizes the following vendor IDs:
Vendor-specific definitions are frequently needed e.g. for Oracle, whose outer join syntax differs drastically from most others. For example:
<class name="EmployeeCountByManager" identity-type="nondurable"> <extension vendor-name="triactive" key="view-definition" value=" <!-- The "generic" definition. --> CREATE VIEW {this} ( {this.managerName}, {this.employeeCount} ) AS SELECT MGR.{Employee.name}, COUNT(EMP.{Employee.this}) FROM {Employee} AS EMP INNER JOIN {Employee} AS MGR ON EMP.{Employee.manager} = MGR.{Employee.this} GROUP BY EMP.{Employee.manager}" /> <extension vendor-name="triactive" key="view-definition-oracle" value=" <!-- The Oracle-specific definition. --> CREATE VIEW {this} ( {this.managerName}, {this.employeeCount} ) AS SELECT MGR.{Employee.name}, COUNT(EMP.{Employee.this}) FROM {Employee} EMP, {Employee} MGR WHERE EMP.{Employee.manager} = MGR.{Employee.this} GROUP BY EMP.{Employee.manager}" />