|
Using View Objects in Entity Objects for an Efficient Existence Check
Here's a quick overview of using view objects
inside entity objects. Let's say you want to implement an efficient existence
check for a particular entity. You can do it using the
findByPrimaryKey() method on the entity's definition object,
but one thing to consider is that when you find an entity by primary key, all
of its attributes are brought into the cache. If you don't want this, you can
do a quick check using a SQL statement in a view object that you make use of
from your entity object's code. Several good reasons to use a
design-time-created View Object instead of dynamically creating the SQL
statement at runtime are: - You incur no
runtime overhead to describe the structure of a query for a design-time VO
- The SQL that you're using in your application
is neatly encapsulated into the view object instead of appearing as strings in
your Java code
- BC4J makes optimal use/reuse
of the PreparedStatement, important if you'll be executing the SQL over and
over
- Takes less
code.
In a package
x.y.z, I have an entity Employee. Since the
findByPrimaryKey() method lives on the
EntityDefImpl "entity definition object", which plays the
role in the BC4J framework of a "home" or "factory" for an entity object, I
choose to have a custom EmployeeDefImpl.java class on the
Java panel of the Employee. This way I can add additional "factory" level
methods there. In the EmployeeDefImpl.java class I add my
method: /* In File: EmployeeDefImpl.java
* -------
* Custom existence check method for Employee entities
*/
public boolean exists(DBTransaction t, Number id) {
boolean foundIt = false;
ViewObject vo = getEmployeeExistsView(t);
vo.setWhereClauseParam(0,id);
vo.setForwardOnly(true);
vo.executeQuery();
foundIt = (vo.first() != null);
/*
* If we didn't find it in the database, try checking against any new employees in cache
*/
if (!foundIt) {
Iterator iter = getAllEntityInstancesIterator(t);
while (iter.hasNext()) {
EmployeeImpl e = (EmployeeImpl)iter.next();
/*
* NOTE: If you allow your primary key attribute to be modifiable
* then you should also check for entities with post-state
* of Entity.STATUS_MODIFIED here as well.
*/
if (e.getPostState() == Entity.STATUS_NEW && e.getEmpno().equals(id)) {
foundIt = true;
break;
}
}
}
return foundIt;
}
This entity definition method makes use of a view object
to check for the existence of an employee in the database. If it doesn't find
the employee in the database, as a second check it iterates over the Employee
entities presently in the cache, and checks if among any of the entities that
have the STATUS_NEW status, whether we have a match. If, as
is common, our primary key attribute is only Updatable While
New, then we only look at the STATUS_NEW entity
instances since if they had any other status, we would have found them in the
database in the previous step. However, if the primary key attribute is
updateable, then this cache iteration should also consider the entities with
STATUS_MODIFIED as well to account for entity instances
whose primary key has been updated in the current transaction, but not yet
posted. My x.y.z.EmployeeExistsView is defined
as an expert-mode view object with no related entities. You can do that by
clicking (Next>) enough times in the New View Object
wizard to get to the Query panel, and then paste in a
statement like this: select null as X from emp where empno = :0
To further optimize the network traffic, I set the
Max Fetch Size to 1 since this query will never return
more than 1 row. This saves a network round trip to go decide whether there are
more rows to fetch or not. There are two ways you can use a view
object from within an entity object context: - You can call
getDBTransaction().createViewObject("x.y.z.EmployeeExistsView"),
use it, then call remove() on the view object instance you
create, or - You can call
getDBTransaction().getRootApplicationModule() and then call
findViewObject("SomeViewInstanceName")
on the root application module to reuse an existing, well-known view object
instance. If that call returns null you can call
createViewObject("SomeViewInstanceName","x.y.z.EmployeeExistsView")
on that root application module which you then will be able to find by name the
2nd and subsequent times.
The second
approach -- only available using BC4J 9.0.3 and above -- has the benefit that
the BC4J framework can keep the JDBC prepared statement around and reuse it
over and over for different existence checks, differing each time only in the
value of the bind variable that you set to be looked up. The code for the
helper method getEmployeeExistsView(...) looks like
this: private static final String EMP_EXISTS_USAGENAME = "x_y_z_Employee_Exists";
private static final String EMP_EXISTS_DEFNAME = "x.y.z.EmpExistenceCheck";
/* In file: EmployeeDefImpl.java
* -------
* Helper method to find (or create the first time) an expert-mode
* view object that tests whether or not an employee exists
*/
private ViewObject getEmployeeExistsView(DBTransaction t) {
ApplicationModule root = t.getRootApplicationModule();
ViewObject vo = root.findViewObject(EMP_EXISTS_USAGENAME);
if (vo == null) {
vo = root.createViewObject(EMP_EXISTS_USAGENAME,EMP_EXISTS_DEFNAME);
}
return vo;
}
With this code in place, we can leverage the new
exists() method on the Employee entity definition object
from anywhere we want to test for the existence of an
Employee as follows. - If
our code is inside the
EmployeeImpl.java file, we can
write:EmployeeDefImpl home = (EmployeeDefImpl)getDefinitionObject();
if (!home.exists(getDBTransaction(),value)) {
throw new ValidationException("Employee {0} does not exist!","12345",new Object[]{value});
}
- If our code is in some other
*Impl.java file, we can do:EmployeeDefImpl home = (EmployeeDefImpl)EmployeeImpl.getDefinitionObject();
if (!home.exists(getDBTransaction(),value)) {
throw new ValidationException("Employee {0} does not exist!","12345",new Object[]{value});
}
Tip:
In case it comes in
handy, another way of finding the definition object for an entity in a generic
way is to use: EmployeeDefImpl home = (EmployeeDefImpl)EntityDefImpl.findDefObject("x.y.z.Employee");
Here we saw a use for an existence check, but any
kind of adhoc SQL that you need to do as part of your business logic can be
code using similar techniques. The difference would be that the view object
would likely get directly used from your entity implementation class instead of
the usage in the entity definition implementation class as
we've illustrated here. It just so happens that I created my entity
and my existence-check view object in the same x.y.z
package, but there is no rule that says they must be in the same package. You
can organize all of your BC4J components into packages in the way that works
best for you. Note:
We're considering implementing a generic,
efficient existence check as a base BC4J framework feature for a future
release.
|