Custom Association Views

One of the concepts I covered last week was an entity object’s default query, the all-columns query that would be created in a default view object for that entity object. I also talked about one place where the default query is used, even if you don’t create a default view object: entity object fault-in. But there’s another place that this possibly inefficient query gets used, at least by default: Whenever your business logic traverses an association accessor. Let’s suppose that you have two entity objects, Departments and Employees. Suppose, in particular, that Employees has attributes corresponding to every column in the EMPLOYEES table (that’s 15 columns total). The default query for Employees is:
SELECT Employees.EMPLOYEE_ID, Employees.FIRST_NAME, Employees.LAST_NAME, Employees.EMAIL, Employees.PHONE_NUMBER, Employees.HIRE_DATE, Employees.JOB_ID, Employees.SALARY, Employees.COMMISSION_PCT, Employees.MANAGER_ID, Employees.DEPARTMENT_ID, Employees.CREATED_BY, Employees.CREATED_DATE, Employees.MODIFIED_BY, Employees.MODIFIED_DATE FROM EMPLOYEES Employees

Of course, view objects you base on this entity object should usually have much shorter queries; it’s rare that you would really need to query all this data. But now, let’s consider what happens when you traverse an association, EmpDeptFkAssoc, that associates Departments with Employees. This could happen in any number of ways, such as by calling the association’s accessor method on a Departments entity object instance, or by using a validation rule on Departments or one of its attributes that traverses the association or needs to retrieve associated entities. EmpDeptFkAssoc has DepartmentId (from Departments) as its source attribute, and EmployeeId (from Employees) as its destination attribute. Its “association WHERE clause,” therefore, is :Bind_DepartmentId = Employees.EMPLOYEE_ID. The Departments instance’s DepartmentId value is passed into the :Bind_DepartmentId parameter, which is then appended to a query– Employees’ default query. That query gets executed to pull in any not-yet-queried rows from the database whenever you traverse the association accessor. This is probably not what you want. Executing the full default query (with an appended WHERE clause) is massive overkill unless your business logic actually requires all the attributes from

your rule is that only department 90 can contain employees with the JobId AD_VP. you can use a new 11g feature called Custom View Objects. there’s really a lot going on behind the scenes:      The framework creates a a query collection (essentially. The framework adds the rows to the entity cache (if they’re not already present). even if new rows need to be retrieved. WHERE clause.. a collection of rows from the entity cache) associated with both the query.JOB_ID FROM EMPLOYEES Employees And specify EmpJobsView (on the Tuning page of an association’s editor) as EmpDeptFkAssoc’s custom view object for Employees. Built-in validation rules know how to process these row sets. The framework and returns the row set (typed to RowIterator) When the application first attempts to access rows in the row set. The framework creates a row set (essentially a pointer to a query collection plus default iterator) that uses the query collection.EMPLOYEE_ID. Employees. with the following query: SELECT Employees. as discussed in last week’s post. That way.Employees. create a view object called EmpJobsView. which.JOB_ID FROM EMPLOYEES Employees WHERE :Bind_DepartmentId = Employees.EMPLOYEE_ID. Association Accessor Row Sets When your application first traverses an association to a side with cardinality * or 1. Employees. If. If you use a custom view object and then try to access an attribute it doesn’t populate. allowing them to be seen in the row set. you’ll trigger fault-in. But what happens to the row set when the validation rule or custom method is done with it? . and parameter value (the unique identifier of the WHERE clause and parameter is called the query collection’s “filter”.*. you *certainly* don’t need to query all data about all employees in your departments. Of course. and adds both the previously existing and the new rows to the query collection. or you can process thems yourself if you call them from inside an entity object method. You can. is undesirable when you can avoid it. The framework queries the associated rows using queries like the above.DEPARTMENT_ID Much better. you don’t want to get too aggressive with this. say. for example. In this case. when you traverse the association. based on Employees.the only query that will be executed will be the following: SELECT Employees.

which is already pointing to the last row. if it happens many. You can select the option “Retain Association Accessor Rowset” (in the Tuning section of an entity object editor’s General page) to tell instances of entity object to retain row sets created when they traverse the association. This behavior is called “association consistency. If you’re always creating new row sets. If you only rarely traverse an association more than once from any given entity object instance. ready for garbage collection. it’s released completely.doStuff(). When you traverse the association again. but there’s no row set left that uses the query collection. the first time you an association.hasNext() returns false. or even the underlying query collection. it can add up.” and in the vast majority of . emp. it notifies all the entity cache’s query collections. But what happens when you create a new entity instance that matches the filter of the query collection? By default. of course–but if you retrieve association accessors from lots of individual entity object instances. then the second call to getEmployees() above will retireve the same row set.By default. they will likely repeatedly change the row during one application session. On the other hand. this makes sense: once you’re done processing the row set created by the association. while it doesn’t need to re-retrieve the rows from the database. One caution here: When a new row set is created. but if you are retaining them. keeping hold of the retrieved row set starts making more sense than requiring the application to construct it again and again. it still does need to find the query collection with the matching filter and create a new row set based on it. disappear. Association Consistency As above. If you’re retaining row sets and need to cycle through them. many times. holding it around will take up memory that you don’t need. triggering validation that uses the association each time). its “current row” pointer always points to the slot before the first row. ending the loop). This shouldn’t be overstated–finding the query collection and creating a row set object is not a terribly expensive operation–but again. whenever a new entity object instance is created. This doesn’t mean that the underlying entity object instances. the first call to next() retrieves the first row. if you know your application is likely to traverse the association from a given row many times (for example. data is retrieved from the database and inserted in a query collection. it can add up. so you can traverse its rows with code like: RowIterator rowSet = getEmployees(). and each QC checks to see if it should be inserted based on its filter.next(). explicitly call the method RowIterator. and inserts it if so. Probably not much memory. } You start before the first row. while (rowSet. you can always count on starting before the first row.hasNext()) { EmployeesImpl emp = rowSet.reset(). and so on until you’re pointing to the last row (when rowSet.

suppose your application allows users to insert a large number of employees simultaneously. you’d have to post the new entity object instance to the database. and then traverse EmpDeptFkAssoc from Department 90. These rows are going to get posted to the database as soon as they are created anyway.cases. } super. and will requery its employees the next time the association is traversed. event). and re-execute the association’s query. you might want to do something like the following: In Departments Create a transient. TransactionEvent event) { if (operation == DML_INSERT) { getDepartment(). For example. In EmployeesImpl’s doDML() Method Do the following: protected void doDML(int operation. } In DepartmentsImpl’s getEmployees() Method Do the following: public RowIterator getEmployees() { RowSet employees = (RowSet) getAttributeInternal(EMPLOYEES). after the new employee is posted. . you want it to show up immediately. setEmployeesIsDirty(false).executeQuery(). employees. Instead. This means no posting that wouldn’t have occurred anyway. You don’t want to hit the database every time you insert a new entity object instance into the cache. If it weren’t for association consistency. So rather than notify and check every query collection each time every one of these rows is created.doDML(operation. EmployeesIsDirty. with a default value of false. and one requery instead of many notifications. to see the new row. After all. if (getEmployeesIsDirty()) { employees. } } That call to setAssociationConsistent() keeps that row set’s query collection from being notified when a new employee is created. But there are a few cases–a very few cases–where you don’t need to see the new row immediately. if you create a new employee with DepartmentId 90. and that the submit button for the page allowing the insert does a database commit. Boolean attribute.setAssociationConsistent(false). the relevant department is marked as having a dirty employees accessor.setEmployeesIsDirty(true). it’s what you want.

That’s it for associations. I’ll talk about tuning your view objects. if you’re being really clever about this. Next week. .Of course. you’ll generalize this behavior and push it up into declaratively customizable framework classes.

Sign up to vote on this title
UsefulNot useful