HOWTO: Implementing Collections of Value Objects for MVC Apps with BC4J
HOWTO: Implementing Collections of Value Objects for MVC Apps with BC4JAn
Oracle JDeveloper How To Document November 2002 Product VersionsThis
document was written for Oracle9i JDeveloper versions 9.0.2 and 9.0.3. Contents Overview Creating
a MVC Model Layer Using BC4J Writing
a Sample View Layer Client Conclusion
OverviewJ2EE developers talk a lot
about Value Objects. Value Objects, also known as Data Transfer Objects, are a
J2EE Design Pattern for grouping a set of related attributes together to shuttle
data between your "model" layer and "view" layer in a Model-View-Controller application
architecture. While sometimes used alone, typically developers work with collections
or lists of Value Objects, for example so that a JSP page in the View layer can
present a list of employee information retrieved by the Model layer. When
developers code value objects by hand, they typically model them as a simple JavaBean
like the following EmpValueObject class: package
test.common; import java.io.Serializable; // Typical Value Object Implementation
as a JavaBean public class EmpValueObject implements Serializable { String _name; Float
_salary; Integer _id; public String getName()
{ return _name; } public Float getSalary() {
return _salary; } public Integer getId() {
return _id; } public void setName(String
value) { _name = value; } public void setSalary(Float
value){ _salary = value; } public void setId(Integer value) {
_id = value; } public EmpValueObject(){} }
After creating the value objects, in most J2EE projects using
the MVC architecture, a developer will create a service interface. The service
interface isolates the View layer from the persistent business objects in the
Model, and provides a central point of access to the various collections of value
objects needed by the View layer. For a simple application working with employees,
this service interface might look like this: package
test.common; // Service interface to expose collections of value objects to
the View layer public interface EmpModel { // Developers frequently
use Vector, List, or Collection to return a set of Value Objects java.util.Vector
vectorOfEmpsInDept(int deptno); java.util.Collection collectionOfEmpsInDept(int
deptno); java.util.List listOfEmpsInDept(int
deptno); } Then some class provides an implementation of this
EmpModel interface for the View layer tier to use. With this
service interface in place, the usage pattern in the View layer code usually goes
like this: - Use a factory to obtain an instance of the
EmpModel
interface - Call a method on the
EmpModel interface
to return an appropriate Collection, List,
or Vector of value objects - Create an
Iterator
to iterate the value object instances The routine coding pattern
for this might look like the following code snippet: //------------------------------------------------------------- //
Routine code in the "View" layer to access a collection/list // of business
information from the "Model" layer //------------------------------------------------------------- EmpModel
empModel = EmpModelFactory.createEmpModel(); : List list = empModel.listOfEmpsInDept(10); Iterator
it = list.iterator(); while (it.hasNext()) { // Get the next
instance of our value object from the list EmpValueObject empValObj
= (EmpValueObject)it.next(); // Use getter methods on the value
object to access the data System.out.println(empValObj.getName()+","+empValObj.getId()+","+empValObj.getSalary()); }
The implementation of the method listOfEmpsInDept(int
deptno) usually contains code that interacts with an underlying Data Access
Object (another J2EE Design Pattern) that encapsulates the JDBC code to actually
retrieve the employee data from the database. So if we take a quick inventory
of the objects that the typical J2EE developer adopting a Model-View-Controller
architecture must create to put some business information on a web page, we have
the following: - A "model" class that provides methods to expose collections
of value objects to the View layer (
EmpModel) - A factory
object that returns an implementation of the model class (
EmpModelFactory)
- A value object class that holds the interesting attributes needed by
the view layer (
EmpValueObject, holding Name,
Id, and Salary) - A Data Access Object
class that encapsulates the JDBC code to query the employee name, id, and salary
from the underlying database, and
- If the user interface needs to present
a longer list of business information a "page at a time", then in addition a "value
list handler" class is usually required to manage these details.
Business Components for Java provides a ready-made implementation of all of the
above ingredients to make quick work of the task without having to hand code any
of these participant classes yourself. The next section explains how. Creating
a MVC Model Layer Using BC4JHere are the step by step instructions on
how to build this typical Model layer using BC4J. Once the model layer is built,
we'll see in the following section how to use this model layer from your MVC view
layer client. - Startup Oracle9i JDeveloper and create a new
empty project in a new workspace
- Select New Business Components Package...
from the right-mouse menu on your project.
-
Provide a name for
the package that your business components will live in, let's say test,
and set the connection name you want to use, then click (Finish). The
connection name should map to the SCOTT schema, containing the EMP table. -
We'll use a view object component to handle the value objects list of employee
information we need. Select the test package in the
System Navigator and choose New View Object... from its right-mouse menu.
-
Call the new view object EmpsInDepartment,
and click (Next>) until you get to "Step 5 of 6: Query" page. -
Select the
EmpsInDepartment view object in the System Navigator and choose
Edit EditEmpsInDepartment... from the right-mouse menu. -
Click
on the "Attribute Settings" tab. This shows you the attributes that will be included
in the value object's in the list produced by executing this view object's query.
Select the Id attribute in the poplist. Set its datatype
to Integer. Select the Salary attribute
in the poplist, and set its datatype to Float. Of course,
the default datatype for numbers ( oracle.jbo.domain.Number)
will work just fine as well, but here we illustrate that we have fine control
over the datatype of each attribute the value object. -
We can
also control which accessor method (or other custom methods) will appear on the
value object the client will see. Click on the "Client Row Methods" tab
and click the (>>) button to shuttle all of the accessor methods
into the selected list. Then click (Finish). -
We'll use an application module component to implement the service interface
for our value object collections needed by the view layer. Select the test
package in the System Navigator and choose New Application Module...
from its right-mouse menu. - Call the new application module
EmpModel,
and click (Next>) from the next page. On the "Data Model" panel of the
wizard, select the EmpsInDepartment view object and click the
(>) arrow button to shuttle it into the selected list. Then click (Finish>) With
these steps, we have created:
| Class/Interface | Description and Patterns That It Implements |
View Object EmpsInDepartment | Handles
JDBC data access, and manages our lists of value objects based on employee information
(Patterns: Value List Handler, Page-by-Page Iterator, Fast-Lane Reader, Data Access
Object) | Value Object EmpsInDepartmentRow |
Holds the Name, Id, and Salary attribute of an employee, and provides accessor
methods. (Pattern: Value Object) | Application
Module EmpModel | Exposes collections of value objects
to the "View" layer. (Pattern: Service Interface for View Layer) |
Later we'll see that BC4J provides a factory to easily get an instance
of our EmpModel and work with it. That factory is generic to
all application module classes, so it doesn't need to be specifically generated
for the EmpModel. First let's write some custom code
on our EmpsInDepartment view object to encapsulate the setting
of the bind variable. Expand the icon in the system navigator for the EmpsInDepartment
and double-click on the EmpsInDepartmentImpl.java class to
see it in the code editor. Even though this is generated code, the view object
"Impl" class is designed to have your custom code added right to it. Your custom
code does not get "blown away" when you revisit the re-entrant view object wizard
later to change declarative settings of the component. We add the following
method to the EmpsInDepartmentImpl.java file: //
Encapsulate the settings of this View Object's Where Clause public void setDepartmentNumber(int
deptno) { setWhereClauseParam(0,new Integer(deptno)); } Then,
we can expose the view object method to clients by visiting the View Object editor,
clicking on the "Client Methods" tab, shuttling this new setDepartmentNumber
method into the selected list, and pressing (Finish). Next let's
write some custom code in our EmpModel application module.
Expand the icon in the system navigator for the EmpModel
and double-click on the EmpModelImpl.java class to see it in
the code editor. First we add a private method to return a RowSet
of employees based on a deptno department number passed in.
We'll reuse this method in several of the example methods we're about to write:
/** * Return the RowSet
of Emps for department id passed in */ private RowSet rowsetForEmpsInDepartment(int
deptno) { EmpsInDepartment eid = getEmpsInDepartment(); //
Use custom method on the VO to encapsulate WHERE Clause details eid.setDepartmentNumber(10); eid.executeQuery(); //
ViewObject interface extends RowSet so we can just return this VO as RS return
eid; } As we hinted at above, BC4J provides an EmpsInDepartmentRow
value object for our use, along with a remoteable collection implementation called
RowSet. However, if the developer is used to using the standard
J2SE Vector, Collection, or List
interfaces, we'll illustrate how to use those as well. The BC4J-supplied
RowSet implementation works seamlessly with the BC4J-generated
value objects, so we can write a simple method like the following to return the
RowSet of EmpsInDepartmentRow value objects
to the view layer: /**
* Return a RowSet of EmpsInDepartmentRow instances */ public RowSet rowsetOfEmpsInDept(int
deptno) { return rowsetForEmpsInDepartment(deptno); } When
using Vector, Collection, or List,
you need to use a hand-written JavaBean as your value object like the EmpValueObject
we showed at the beginning of this article. The code to return a Vector, List,
and Collection of EmpValueObject instances looks like this:
/** * Return a Vector of
EmpValueObject instances */ public Vector vectorOfEmpsInDept(int deptno)
{ Vector v = new Vector(); RowSet rs = rowsetForEmpsInDepartment(deptno); while
(rs.hasNext()) { // Instantiate the hand-written value
object and add to vector v.add(empValObjectForEmpRow((EmpsInDepartmentRow)rs.next())); } return
v; }
/** * Return a list of EmpValueObject instances */ public
List listOfEmpsInDept(int deptno) { ArrayList al = new ArrayList(); RowSet
rs = rowsetForEmpsInDepartment(deptno); while (rs.hasNext()) { //
Instantiate the hand-written value object and add to list al.add(empValObjectForEmpRow((EmpsInDepartmentRow)rs.next())); } return
al; }
/** * Return a collection of EmpValueObject instances
*/ public Collection collectionOfEmpsInDept(int deptno) { //
The java.util.List interface extends java.util.Collection interface return
listOfEmpsInDept(deptno); } So, it takes a little more work
and an extra hand-written value object to do what BC4J provides for free, but
it's worth implementing all the permutations anyway for practice. Notice that
in the Vector, List, and Collection cases, we're using a helper method named empValObjectForEmpRow
which does the job of copying the data from the EmpsInDepartmentRow
value object that BC4J provides into the hand-written value object that can be
serialized as part of /**
* Create an EmpValueObject instance from an EmpsInDepartmentRow */ private
EmpValueObject empValObjectForEmpRow(EmpsInDepartmentRow empRow) { EmpValueObject
empValObj = new EmpValueObject(); empValObj.setName(empRow.getName()); empValObj.setSalary(empRow.getSalary()); empValObj.setId(empRow.getId()); return
empValObj; } Now that we've written our four public methods
on our EmpModel implementation class, we can expose these methods
to clients by visiting the Application Module editor for EmpModel and clicking
on the Client Methods tab. By holding down the [Ctrl] key while clicking,
we can multi-select the four method names in the list on the left: -
rowsetOfEmpsInDept -
vectorOfEmpsInDept -
listOfEmpsInDept -
collectionOfEmpsInDept and
then click on the (>) button to shuttle these methods into the Selected
list. When we click (Finish), the BC4J design time facilities will automatically
create a new EmpModel interface for us, containing these four
methods. So, we now have three interfaces in our project:
| Interface | Description | View Object
EmpsInDepartment | Exposing our custom setDepartmentNumber
method to clients... | Value Object EmpsInDepartmentRow |
Exposing our typesafe getter/setter methods for our employee data values to
clients... | Application Module EmpModel |
Exposing the four custom methods in our service interface for our view layer:
rowsetOfEmpsInDept, vectorOfEmpsInDept,
listOfEmpsInDept, collectionOfEmpsInDept...
| While we created our BC4J components in the test
package, BC4J automatically creates these interfaces in the test.common
package for us, emphasizing the fact that they are common interfaces that
can be used on both the client tier and the business tier. Another naming
pattern that BC4J uses to help us remember which tier our classes belong in is
the Impl suffix on classes in our project like: -
EmpsInDepartmentImpl.java
- our view object implementation class -
EmpsInDepartmentRowImpl.java
- our value object implementation class -
EmpModelImpl.java
- our service interface implementation class These classes should
never appear in client-side code. As we'll see in the next section, client code
should always refer to methods on the BC4J framework interfaces, or on the custom
extensions to those interfaces that the BC4J framework creates and manages as
we expose custom methods on View Objects, Value Objects (also known as View Object
Rows), and Application Modules. Writing a Sample
View Layer ClientWith our simple business tier functionality built, and
our custom methods exposed, we can concentrate on writing the client code that
makes use of it. While our example will be a simple console program that outputs
data to System.out, the techniques illustrated by this example
are valid for any Java client code. First we need to use a factory to obtain
an instance of our EmpModel service interface to work with.
BC4J provides several ways to acquire application modules, but the easiest is
to use the oracle.jbo.client.Configuration class. Configurations
are named lists of runtime parameters that affect how the client connects to the
business tier. By default our EmpModel application module will
get a configuration created named EmpModelLocal, with parameters
setup for the client to connect to the business tier as a set of local java classes.
If we later choose to deploy our application module as an EJB Session Bean, BC4J
with again by default create a configuration named EmpModel9iAS
with parameters set appropriately for a remote EJB-based connection from client
to business tier. Using the BC4J framework's Configuration class, and an appropriate
configuration name, you can easily get an instance of an application module like
this: import test.common.EmpModel;
: String _am = "test.EmpModel"; // Full name of application module String
_cf = "EmpModelLocal"; // Name of configuration EmpModel empModel
= (EmpModel)Configuration.createRootApplicationModule(_am,_cf); you
can use this instance of EmpModel as you need to, and then
when you are finished using it, you use a companion method on the Configuration
object to release your application module instance: Configuration.releaseRootApplicationModule(empModel,
true /* Remove AM instance? */); If you pass true
as the second argument to releaseRootApplicationModule, then
the application module instance you were working with will be destroyed. If instead
you pass false, the application module instance will be kept in a pool for subsequent
reuse in the same process. The following program illustrates using the
technique above to exercise all of the different custom methods we exposed above
to retrieve and iterate over: - A
java.util.List
of hand-written EmpValueObject value objects - A
java.util.Vector
of hand-written EmpValueObject value objects - A
java.util.Collection
of hand-written EmpValueObject value objects - A
oracle.jbo.RowSet
of BC4J-generated, typesafe EmpsInDepartmentRow value objects
All four of the above value object collections are retrieved by invoking
a custom method on the EmpModel service interface. Examples
5, 6, and 7 in the TestClient program then go on to illustrate
some of the BC4J value object collection functionality that is possible directly
using the base BC4J client interface oracle.jbo.ApplicationModule,
from which our EmpModel extends. Specifically, these
last three examples illustrate retrieving and iterating: - A
RowSet of BC4J-generated, typesafe EmpsInDepartmentRows
value objects, using findViewObject() - A
RowSet
of generic oracle.jbo.Row using the same custom method as in
(4) above. - A
RowSet of generic oracle.jbo.Row
using findViewObject() Here's the sample code: import
java.util.Collection; import java.util.Iterator; import java.util.List; import
java.util.Vector;
import oracle.jbo.ApplicationModule; import oracle.jbo.Row; import
oracle.jbo.RowSet; import oracle.jbo.RowSetIterator; import oracle.jbo.client.Configuration; import
oracle.jbo.domain.Number;
import test.common.EmpModel; import test.common.EmpValueObject; import
test.common.EmpsInDepartment; import test.common.EmpsInDepartmentRow;
public
class TestClient { public static void main(String[] args){ String
_am = "test.EmpModel"; // Full name of application module
// The only thing
that needs to change to run this same code // in a three-tier deployment is
the name of the configuration // you want to use. // // String
_cf = "EmpModel9iAS"; // Name of remote configuration
String
_cf = "EmpModelLocal"; // Name of configuration
//
Cast ApplicationModule to custom EmpModel interface to call custom methods EmpModel
empModel = (EmpModel)Configuration.createRootApplicationModule(_am,_cf);
p("1:
Retrieve/iterate List of User-Created Value Objects from AM Custom Method"); List
list = empModel.listOfEmpsInDept(10); Iterator it =
list.iterator(); while (it.hasNext()) { //
Here we use our hand-created EmpValueObject EmpValueObject
empValObj = (EmpValueObject)it.next(); p(empValObj.getName()+","+empValObj.getId()+","+empValObj.getSalary()); }
p("2:
Retrieve/iterate Vector of User-Created Value Objects from AM Custom Method"); Vector
v = empModel.vectorOfEmpsInDept(10); it = v.iterator(); while
(it.hasNext()) { EmpValueObject empValObj
= (EmpValueObject)it.next(); p(empValObj.getName()+","+
empValObj.getId()+","+
empValObj.getSalary()); } p("3:
Retrieve/iterate Collection of User-Created Value Objects from AM Custom Method"); Collection
coll = empModel.collectionOfEmpsInDept(10); it = coll.iterator(); while
(it.hasNext()) { // Here we use our hand-created
EmpValueObject EmpValueObject empValObj
= (EmpValueObject)it.next(); p(empValObj.getName()+","+empValObj.getId()+","+empValObj.getSalary()); } p("4:
Retrieve/iterate RowSet of Typesafe EmpsInDepartmentRows from AM Custom Method"); RowSet
rs = empModel.rowsetOfEmpsInDept(10); // Not strictly
needed since RowSet interface inherits from RowSetIterator //
and for convenience BC4J aggregates a default iterator "built-in" to the //
RowSet interface implementation object, but here we can create a secondary //
iterator to illustrate the parallel programming pattern with Vector, List, //
and Collection. Iterators can be named. Passing null says you don't care //
about the name RowSetIterator rsit = rs.createRowSetIterator(null); while
(rsit.hasNext()) { // Rather than needing
a user-created value object, we use the //
typesafe EmpsInDepartmentRow interface provided by BC4J //
Same typesafe benefits, same network traffic savings, no hand-coded //
value object to write for each query we use. BC4J does it for us. EmpsInDepartmentRow
empValObj = (EmpsInDepartmentRow)rsit.next(); p(empValObj.getName()+","+empValObj.getId()+","+empValObj.getSalary()); }
p("5:
Retrieve/iterate RowSet of Typesafe EmpsInDepartmentRows using findViewObject"); //
Cast to our custom interface EmpsInDepartment to call our custom VO methods EmpsInDepartment
eid = (EmpsInDepartment)empModel.findViewObject("EmpsInDepartment"); //
Use custom method on VO custom interface to encapsulate where clause details eid.setDepartmentNumber(10); eid.executeQuery(); //
EmpsInDepartment interface extends ViewObject interface which extends //
RowSet interface which extends RowSetIterator. So, here we show that we //
can use the iterator behavior that's built-in to RowSet for convenience //
instead of doing further casting while (eid.hasNext())
{ EmpsInDepartmentRow empValObj = (EmpsInDepartmentRow)rs.next(); p(empValObj.getName()+","+empValObj.getId()+","+empValObj.getSalary()); }
p("6:
Retrieve/iterate RowSet of Generic Rows from AM Method"); rs
= empModel.rowsetOfEmpsInDept(10); while (rs.hasNext())
{ // Rather than using a typesafe Row subinterface
like EmpsInDepartmentRow // above, here
we just use the base Row interface and call getAttribute() //
instead of typesafe getter methods Row
empRow = rs.next(); p(empRow.getAttribute("Name")+","+empRow.getAttribute("Id")+","+ empRow.getAttribute("Salary")); }
p("7:
Retrieve/iterate RowSet of Generic Rows using findViewObject"); //
Since findViewObject() is on the ApplicationModule interface, we //
illustrate here that we can invoke it without using our //
typesafe TestModule interface. Also, recall that ViewObject extends //
RowSet so if it's more clear for our code, we can directly use //
the RowSet interface here to refer to the ViewObject's default rowset //
that it provides (through aggregation). ApplicationModule
testAppModule = empModel; rs = testAppModule.findViewObject("EmpsInDepartment");
//
Rather than calling the encapsulated setDepartmentMethod on our //
custom EmpsInDepartment interface as we did above, here we show //
the client directly setting the where clause parameters. //
NOTE: Params are zero-based, so 0 is the first param in the SQL. rs.setWhereClauseParam(0,new
Integer(10)); rs.executeQuery(); while
(rs.hasNext()) { Row empRow = rs.next(); p(empRow.getAttribute("Name")+","+empRow.getAttribute("Id")+","+ empRow.getAttribute("Salary")); }
//
Release the AppModule (optionally back to the pool if we pass 'false' Configuration.releaseRootApplicationModule(empModel,true); } private
static void p(String m){System.out.println(m);} } As the example
illustrates, working with a BC4J RowSet uses the same familiar
coding patterns as working with a java.util.Collection, however
a BC4J RowSet interface is a collection with a few more tricks
up its sleeve. In fact, in addition to making it possible to use automatically-generated
value objects, a further benefit of using the BC4J RowSet is
that the value objects in a RowSet can be optionally fully
updateable. This is in sharp contrast to a java.util.Collection
of hand-created value objects, which are typically strictly read-only in nature
due to the complexity of keeping any updates made in sync with your business logic
tier. BC4J offers significant help here by automating this synchronization, just
by using a RowSet instead. Our EmpsInDepartment
view object created above was a simple, read-only collection of database query
results. However, if you were working with a RowSet produced
by a BC4J view object that you related to one or more entity objects at
design time, then that RowSet automatically becomes a collection
of fully-updateable value objects. This means that rather than having
to manage the updates to your business objects yourself, the BC4J framework allows
your client to simply call appropriate setter methods to modify the attributes
of a value object row in any way necessary, and then commit the changes. All subsequent
business logic enforcement and business object persistence is handled automatically.
ConclusionIn this brief article, we've seen
how easy it is to use the BC4J framework to create a model layer for a Model-View-Controller
application architecture, and how easy it is to access the various collections
of value objects of business information through a service interface from our
view layer. We've seen the best practice techniques allowing custom methods
on our application module to be exposed to clients in a way that allows our deployment
architecture to change at any time, without causing ripple effects to our application
code. This means your BC4J business tier can be deployed as simple Java classes
in your Servlet/Web tier, or as an EJB Session Facade in your EJB tier with the
flick of a switch and no client code changes. And finally, we've seen that
while possible to use BC4J in combination with "classic" collection classes/interfaces
like Vector, List, and Collection
from the java.util package together with hand-created value
objects, the BC4J framework offers more functional, more targeted value object
collection functionality that follows the same familiar programming paradigms.
The examples in this article have illustrated how BC4J's ViewObject,
RowSet, and Row interfaces can be used either
in a generic way or a typesafe way with automatically-generated custom interfaces,
providing a "collections of value objects" implementation that can optionally
be fully-updateable with no extra programming work by the developer.
|