Skip Headers
Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers
10g (10.1.3.1.0)

Part Number B25947-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Master Index
Master Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

6.8 Working Programmatically with Entity Objects and Associations

While external client programs can access an application module and work with any view object in its data model, by design neither UI-based nor programmatic clients work directly with entity objects. In Chapter 7, "Building an Updatable Data Model With Entity-Based View Objects", you'll learn how to easily combine the flexible SQL-querying of view objects with the business logic enforcement and automatic database interaction of entity objects for an incredibly powerful application-building combination. The combination enables a fully updatable application module data model, designed to the needs of the current end-user tasks at hand, that shares the centralized business logic in your reusable domain business object layer.

However, it is important first to understand how view objects and entity objects can be used on their own before learning to harness their combined power. By learning about these objects in greater detail, you will have a better understanding of when you should use them alone and when to combine them in your own applications.

Since clients don't work directly with entity objects, any code you write that works programmatically with entity objects will typically be custom code in a custom application module class or in the custom class of another entity object. This section illustrates examples of working programmatically with entity objects and associations from within custom methods of an application module named SRService in the devguide.model package, manipulating the SRDemo entities you learned how to create earlier in the chapter.

6.8.1 Finding an Entity Object by Primary Key

To access an entity row, you use a related object called the entity definition. At runtime, each entity object has a corresponding entity definition object that describes the structure of the entity and manages the instances of the entity object it describes. After creating an SRService application module in the devguide.model package and enabling a custom Java class for it, imagine you wanted to write a method to return a specific service request's current status. It might look like the retrieveServiceRequestStatus() method like in the SRServiceImpl.java file shown in Example 6-5.

The example breaks down into these basic steps:

  1. Find the entity definition.

    You obtain the entity definition object for the devguide.model.entities.ServiceRequest entity by passing its fully qualified name to the static findDefObject() method on the EntityDefImpl class. The EntityDefImpl class in the oracle.jbo.server package implements the entity definition for each entity object.

  2. Construct a key.

    You build a Key object containing the primary key attribute that you want to look up. In this case, you're creating a key containing the single requestId value passed into the method as an argument.

  3. Find the entity object using the key.

    You use the entity definition's findByPrimaryKey() method to find the entity object by key, passing in the current transaction object, which you can obtain from the application module using its getDBTransaction() method. The concrete class that represents an entity object row is the oracle.jbo.server.EntityImpl class.

  4. Return some of its data to the caller.

    You use the getAttribute() method of EntityImpl to return the value of the Status attribute to the caller.

Example 6-5 Finding a ServiceRequest Entity Object by Key

// Custom method in SRServiceImpl.java
public String findServiceRequestStatus(long requestId) {
  String entityName = "devguide.model.entities.ServiceRequest";
  // 1. Find the entity definition for the ServiceRequest entity
  EntityDefImpl svcReqDef = EntityDefImpl.findDefObject(entityName);
  // 2. Create the key
  Key svcReqKey = new Key(new Object[]{requestId});
  // 3. Find the entity object instance using the key
  EntityImpl svcReq = svcReqDef.findByPrimaryKey(getDBTransaction(),svcReqKey);
  if (svcReq != null) {
    // 4. Return the Status attribute of the ServiceRequest
    return (String)svcReq.getAttribute("Status");
  }
  else {
    return null;
  }
}

Note:

The oracle.jbo.Key object constructor takes an Object array to support creating multiattribute keys, in addition to the more typical single-attribute value keys.

6.8.2 Accessing an Associated Entity Using the Accessor Attribute

In Section 6.3, "Creating and Configuring Associations", you learned that associations enable easy access from one entity object to another. Here's a simple method that helps illustrate what that means in practice. You can add a findServiceRequestTechnician() method that finds a service request, then accesses the associated User entity object representing the technician assigned to the request.

However, since this is the second method in the application module that will be finding a ServiceRequest entity object by ID, you might first want to refactor this functionality into the following retrieveServiceRequestById() helper method that you can then reuse anywhere in the application module that requires finding a service request by ID:

// Helper method in SRServiceImpl.java
private EntityImpl retrieveServiceRequestById(long requestId) {
  String entityName = "devguide.model.entities.ServiceRequest";
  EntityDefImpl svcReqDef = EntityDefImpl.findDefObject(entityName);
  Key svcReqKey = new Key(new Object[]{requestId});
  return svcReqDef.findByPrimaryKey(getDBTransaction(),svcReqKey);
}

Example 6-6 shows the code for findServiceRequestTechnician(). The example follows three basic steps:

  1. Find the ServiceRequest by ID.

    Using the retrieveServiceRequestById() helper method, it retrieves the ServiceRequest entity object by ID.

  2. Access the associated entity using the accessor attribute.

    In Section 6.3.1.1, "Changing Entity Association Accessor Names" above, you renamed the association accessor for the ServiceRequestsAssignedToUser association so that a ServiceRequest entity could access one of its two related User entity objects with the accessor name of TechnicianAssigned. Using the same getAttribute() method used to retrieve any entity attribute value, you can pass in the name of an association accessor and get back the entity object on the other side of the relationship.

  3. Return some of its data to the caller.

    Using the getAttribute() method on the returned User entity, it returns the assigned technician's name by concatenation his first and last names.

Notice that you did not need to write any SQL to access the related User entity. The relationship information captured in the ADF association between the ServiceRequest and User entity objects is enough to allow the common task of data navigation to be automated.

Example 6-6 Accessing an Associated Entity Using the Accessor Attribute

// Custom method in SRServiceImpl.java
public String findServiceRequestTechnician(long requestId) {
  // 1. Find the service request entity
  EntityImpl svcReq = retrieveServiceRequestById(requestId);
  if (svcReq != null) {
    // 2. Access the User entity object using the association accessor attribute
    EntityImpl tech  = (EntityImpl)svcReq.getAttribute("TechnicianAssigned");
    if (tech != null) {
      // 3. Return some of the User entity object's attributes to the caller
      return tech.getAttribute("FirstName")+" "+tech.getAttribute("LastName");
    }
    else {
      return "Unassigned";
    }
  }
  else {
    return null;
  }
} 

6.8.3 Updating or Removing an Existing Entity Row

Once you've got an entity row in hand, it's simple to update it or remove it. You could add a method like the updateRequestStatus() shown in Example 6-7 to handle the job. The example has three simple steps:

  1. Find the ServiceRequest by ID

    Use the retrieveServiceRequestById() helper method to retrieve the ServiceRequest entity object by ID.

  2. Set one or more attributes to new values.

    Use the EntityImpl class' setAttribute() method to update the value of the Status attribute to the new value passed in.

  3. Commit the transaction.

    Use the application module's getDBTransaction() method to accesses the current transaction object and call its commit() method to commit the transaction.

Example 6-7 Updating an Existing Entity Row

// Custom method in SRServiceImpl.java
public void updateRequestStatus(long requestId, String newStatus) {
  // 1. Find the service request entity 
  EntityImpl svcReq = retrieveServiceRequestById(requestId);
  if (svcReq != null) {
    // 2. Set its Status attribute to a new value
    svcReq.setAttribute("Status",newStatus);
    try {
      // 3. Commit the transaction
      getDBTransaction().commit();
    }
    catch (JboException ex) {
      getDBTransaction().rollback();
      throw ex;
    }
  }
}

The example for removing an entity row would be the same as this, except that after finding the existing entity, you would use the following line instead to remove the entity before committing the transaction:

// Remove the entity instead!
svcReq.remove();

6.8.4 Creating a New Entity Row

In addition to using the entity definition for finding existing entity rows, you can also use it to create new ones. Changing focus from service requests to products for a moment, you could write a createProduct()method like the one shown in Example 6-8 to accept the name and description of a new product, and return the new product ID assigned to it. Assume that the ProdId attribute of the Product entity object has been updated to have the DBSequence type discussed in Section 6.6.3.8, "Trigger-Assigned Primary Key Values from a Database Sequence", so that its value is automatically refreshed to reflect the value the ASSIGN_PRODUCT_ID trigger on the PRODUCTS table will assign to it from the PRODUCTS_SEQ sequence in the SRDemo application schema.

The example follows these steps:

  1. Find the entity definition.

    Use EntityDefImpl.findDefObject() to find the entity definition for the Product entity.

  2. Create a new instance.

    Use the createInstance2() method on the entity definition to create a new instance of the entity object.


    Note:

    The method name really has a 2 at the end. The regular createInstance() method has protected access and is designed to be customized by developers as described Section D.2.5, "EntityDefImpl Class" of Appendix D, "Most Commonly Used ADF Business Components Methods". The second argument of type AttributeList is used to supply attribute values that must be supplied at create time; it is not used to initialize the values of all attributes found in the list. For example, when creating a new instance of a composed child entity row using this API, you must supply the value of a composing parent entity's foreign key attribute in the AttributeList object passed as the second argument. Failure to do so results in an InvalidOwnerException.

  3. Set attribute values.

    Use the setAttribute() method on the entity object to assign values for the Name and Description attributes in the new entity row.

  4. Commit the transaction

    Call commit() on the current transaction object to commit the transaction.

  5. Return the trigger-assigned product ID to the caller

    Use getAttribute() to retrieve the ProdId attribute as a DBSequence, then call getSequenceNumber().longValue() to return the sequence number as a long value to the caller.

Example 6-8 Creating a New Entity Row

// Custom method in SRServiceImpl.java
public long createProduct(String name, String description) {
  String entityName = "devguide.model.entities.Product";
  // 1. Find the entity definition for the Product entity
  EntityDefImpl productDef = EntityDefImpl.findDefObject(entityName);
  // 2. Create a new instance of a Product entity
  EntityImpl newProduct = productDef.createInstance2(getDBTransaction(),null);
  // 3. Set attribute values
  newProduct.setAttribute("Name",name);
  newProduct.setAttribute("Description",description);
  try {
      // 4. Commit the transaction
      getDBTransaction().commit(); 
  }
  catch (JboException ex) {
    getDBTransaction().rollback();
    throw ex;
  }
  // 5. Access the database trigger assigned ProdId value and return it
  DBSequence newIdAssigned = (DBSequence)newProduct.getAttribute("ProdId");
  return newIdAssigned.getSequenceNumber().longValue();
}

6.8.5 Testing Using a Static Main Method

At this point, you are ready to test your custom application module methods. One common technique to build testing code into an object is to include that code in the static main() method. Example 6-9 shows a sample main() method you could add to your SRServiceImpl.java custom application module class to test the sample methods you wrote above. You'll make use of the same Configuration object you used in Section 5.7, "How to Create a Command-Line Java Test Client", to instantiate and work with the application module for testing.


Note:

The fact that this Configuration object resides in the oracle.jbo.client package suggests its use for accessing an application module as an application client, and a main() method is a kind of programmatic, command-line client, so this OK. Furthermore, even though it is not best practice to cast the return value of createRootApplicationModule() directly to an application module's implementation class, it's legal to do in this one situation since despite its being a client to the application module, the main() method's code resides right inside the application module implementation class itself.

A quick glance through the code shows that it's exercising the four methods created above to:

  1. Retrieve the status of service request 101

  2. Retrieve the name of the technician assigned to service request 101

  3. Set the status of service request 101 to illegal value "Reopened"

  4. Create a new product supplying a null product name

  5. Create a new product and display its newly assigned product ID

Example 6-9 Sample Main Method to Test SRService Application Module from the Inside

// Main method in SRServiceImpl.java
   public static void main(String[] args) {
     String        amDef = "devguide.model.SRService";
     String        config = "SRServiceLocal";
     ApplicationModule am = 
       Configuration.createRootApplicationModule(amDef,config);
     /* 
      * NOTE: This cast to use the SRServiceImpl class is OK since this
      * ----  code is inside a business tier *Impl.java file and not in a
      *       client class that is accessing the business tier from "outside".
      */
     SRServiceImpl service = (SRServiceImpl)am;
     // 1. Retrieve the status of service request 101
     String status = service.findServiceRequestStatus(101);
     System.out.println("Status of SR# 101 = " + status);
     // 2. Retrieve the name of the technician assigned to service request 101
     String techName = service.findServiceRequestTechnician(101);
     System.out.println("Technician for SR# 101 = " + techName);
     try {
       // 3. Set the status of service request 101 to illegal value "Reopened"
       service.updateRequestStatus(101,"Reopened");
     }
     catch (JboException ex) {
       System.out.println("ERROR: "+ex.getMessage());
     }
     long id = 0;
     try {
       // 4. Create a new product supplying a null product name
       id = service.createProduct(null,"Makes Blended Fruit Drinks");
     }
     catch (JboException ex) {
       System.out.println("ERROR: "+ex.getMessage());
     }
     // 5. Create a new product and display its newly assigned product id
     id = service.createProduct("Smoothie Maker","Makes Blended Fruit Drinks");
     System.out.println("New product created successfully with id = "+id);
     Configuration.releaseRootApplicationModule(am,true);
   }

Running the SRServiceImpl.java class calls the main() method in Example 6-9, and shows the following output:

Status of SR# 101 = Closed
Technician for SR# 101 = Bruce Ernst
ERROR: The status must be Open, Pending, or Closed
ERROR: JBO-27014: Attribute Name in Product is required
New product created successfully with id = 209

Notice that the attempt to set the service request status to "Reopened" failed due to the List Validator failing on the ServiceRequest entity object's Status attribute, shown in Figure 6-17. That validator was configured to allow only values from the static list Open, Pending, or Closed. Also notice that the first attempt to call createProduct() with a null for the product name raises an exception due to the built-in mandatory validation on the Name attribute of the Product entity object.


Note:

You may be asking yourself, "How would a client application invoke the custom service methods I've created in my SRService application module, instead of being called by a main() method in the same class?" You'll learn the simple steps to enable this in Section 8.4, "Publishing Custom Service Methods to Clients". You'll see that it's a straightforward configuration option involving the Client Interface page of the Application Module Editor