Skip Headers
Oracle® Application Development Framework Developer's Guide
10g Release 3 (10.1.3.0)

Part Number B28967-02
Go to Documentation Home
Home
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

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

3 Building and Using Application Services

This chapter describes how to build and use application services in JDeveloper

This chapter includes the following sections:

3.1 Introduction to Business Services

Oracle recommends developing the model portion of an application using TopLink to persist POJO (plain old Java objects) for your business services, EJB session beans to implement a session facade, and how to expose the functionality through a data control. Oracle JDeveloper includes several wizards to quickly and easily create your model project.

Refer to Chapter 19, "Advanced TopLink Topics" for additional information on using TopLink ADF.

For detailed information on Oracle TopLink, refer to the complete Oracle TopLink Developer's Guide and Oracle TopLink Javadoc.

Tip:

Most teams have their own respective source control management (SCM) procedures, policies, and common philosophies towards what constitutes a transaction or unit of work for the SCM system. In the absence of a policy, you should group logical changes into a transaction, and also commit your changes when you need to share your modifications with another member of your team. In general, it is not advisable to commit changes when they do not compile cleanly or pass the unit test created for them.

3.2 Implementing Services with EJB Session Beans

A session bean exposes the functionality of the business layer to the client.

Note:

While you can expose methods on a TopLink entity directly as a business service, this is not the best practice for a Web application. This model will work for basic CRUD functionality, but even simple operations that include interactions between business layer objects require custom code that becomes difficult and unwieldy.

The most common use of a session bean is to implement the session facade J2EE design pattern. A session facade is a session bean that aggregates data and presents it to the application through the model layer. Session facades have methods that access entities as well as methods that expose services to clients. Session beans have a transactional context via the container, so they automatically support basic CRUD functionality.

Figure 3-1 Session Facade Functionality

This diagram shows Session Facade functionality

3.2.1 How to Create a Session Bean

To create a session bean, use the Create Session Bean wizard. This wizard is available from the New Gallery, in the Business Tier category.

The Create Session Bean wizard offers several options, such as EJB version, stateful and stateless sessions, remote and/or local interfaces, container- or bean-managed transactions (CMT or BMT), and choosing how to implement session facade methods. When you create a session bean for a TopLink project, you must choose an EJB 3.0 version session bean and a stateless session. You should also choose container-managed transactions (CMT), as bean-managed transactions (BMT) are beyond the scope of this book. The other options in the Create Session Bean wizard are discussed below.

3.2.1.1 Remote and Local Interfaces

The type of interface required depends on the client. If the client is running in the same virtual machine (VM), a local interface is usually the best choice. If the client runs on a separate VM, a remote interface is required. Most Web applications (JSF/JSP/Servlet) have the client and service running in the same VM, so a local interface is the best practice. Java clients (ADF Swing) run in a separate VM and require a remote interface.

3.2.1.2 Generating Session Facade Methods

A session facade contains core CRUD methods for transactions as well as methods to access entities. To generate session facade methods, select the checkbox for Generate Session Facade Methods in the Create Session Bean wizard, and use the following page to specify which methods to generate. JDeveloper automatically detects all the entities in the project and allows you to choose which entities and methods you want to create session facade methods for.

You can generate session facade methods for every entity in the same project, which can be useful for testing purposes, but is often too much clutter in a single session bean. Session beans are often tailored to a specific task, and contain no more information than is required for that task. Use the tree control to explicitly choose which methods to generate.

Figure 3-2 Selecting Session Facade Methods

This image shows the Session Facade Methods dialog

3.2.2 What Happens When You Create a Session Bean

The session bean class contains session-wide fields and service methods. When you create a session bean, JDeveloper generates the bean class and a separate file for the local and/or remote interfaces. The remote interface is the name of the session bean, for example, SRAdminFacade.java, while the bean class is appended with Bean.java and the local interface is appended with Local.java. You should not need to modify the interface files directly, so they are not visible in the Application Navigator. To view the interface files, use the System Navigator or the Structure Pane.

Example 3-1 SRAdminFacade.java Interface

package oracle.srdemo.model;
import java.util.List;
import javax.ejb.Local;
import oracle.srdemo.model.entities.ExpertiseArea;
import oracle.srdemo.model.entities.Product;
import oracle.srdemo.model.entities.User;
import oracle.toplink.sessions.Session;
 
@Local
public interface SRAdminFacade {
  Object mergeEntity(Object entity);
  Object persistEntity(Object entity);
  Object refreshEntity(Object entity);

  void removeEntity(Object entity);
  
  List<ExpertiseArea> findExpertiseByUserId(Integer userIdParam);
 
  ExpertiseArea createExpertiseArea(Product product, User user, Integer prodId, 
                                    Integer userId, String expertiseLevel, 
                                    String notes);
 
  Product createProduct(Integer prodId, String name, String image, 
                        String description);
 
  List<User> findAllStaffWithOpenAssignments();
 
  User createUser(Integer userId, String userRole, String email, 
                  String firstName, String lastName, String streetAddress, 
                  String city, String stateProvince, String postalCode, 
                  String countryId);
                  
  void updateStaffSkills(Integer userId, List<Integer> prodIds);
}

Example 3-2 SRAdminFacadeBean.java Bean Class

package oracle.srdemo.model;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import javax.ejb.Stateless;
import oracle.srdemo.model.entities.ExpertiseArea;
import oracle.srdemo.model.entities.Product;
import oracle.srdemo.model.entities.User;
import oracle.toplink.sessions.Session;
import oracle.toplink.sessions.UnitOfWork;
import oracle.toplink.util.SessionFactory;
 
@Stateless(name="SRAdminFacade")
public class SRAdminFacadeBean implements SRAdminFacade {
  private SessionFactory sessionFactory;
 
  public SRAdminFacadeBean() {
    this.sessionFactory = 
        new SessionFactory("META-INF/sessions.xml", "SRDemo");
  }
  
  /**
   * Constructor used during testing to use a local connection
   * @param sessionName
   */
  public SRAdminFacadeBean(String sessionName) {
    this.sessionFactory = 
        new SessionFactory("META-INF/sessions.xml", sessionName);
 
  }  
 
  public Object mergeEntity(Object entity) {
    UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
    Object workingCopy = uow.readObject(entity);
    if (workingCopy == null)
      throw new RuntimeException("Could not find entity to update");
    uow.deepMergeClone(entity);
    uow.commit();
 
    return workingCopy;
  }
 
  public Object persistEntity(Object entity) {
    UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
    Object newInstance = uow.registerNewObject(entity);
    uow.commit();
 
    return newInstance;
  }
 
  public Object refreshEntity(Object entity) {
    Session session = getSessionFactory().acquireUnitOfWork();
    Object refreshedEntity = session.refreshObject(entity);
    session.release();
 
    return refreshedEntity;
  }
 
  public void removeEntity(Object entity) {
    UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
    Object workingCopy = uow.readObject(entity);
    if (workingCopy == null)
      throw new RuntimeException("Could not find entity to update");
    uow.deleteObject(workingCopy);
    uow.commit();
  }
 
  private SessionFactory getSessionFactory() {
    return this.sessionFactory;
  }
  
  public List<ExpertiseArea> findExpertiseByUserId(Integer userIdParam) {
    List<ExpertiseArea> result = null;
    
    if (userIdParam != null){
      Session session = getSessionFactory().acquireSession();
      Vector params = new Vector(1);
      params.add(userIdParam);
      result = (List<ExpertiseArea>)session.executeQuery("findExpertiseByUserId", 
ExpertiseArea.class, params);
      session.release();      
    }
 
    return result;
  }
 
  public ExpertiseArea createExpertiseArea(Product product, User user, 
                                           Integer prodId, Integer userId, 
                                           String expertiseLevel, 
                                           String notes) {
    UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
    ExpertiseArea newInstance = (ExpertiseArea)uow.newInstance(ExpertiseArea.class);
    
    if (product == null) {
        product = (Product)uow.executeQuery("findProductById", Product.class, prodId);
    }
    
    if (user == null){
        user = (User)uow.executeQuery("findUserById", User.class, userId); 
    }
    
    newInstance.setProduct(product);
    newInstance.setUser(user);
    newInstance.setProdId(prodId);
    newInstance.setUserId(userId);
    newInstance.setExpertiseLevel(expertiseLevel);
    newInstance.setNotes(notes);
    uow.commit();
 
    return newInstance;
  }
 
  public Product createProduct(Integer prodId, String name, String image, 
                               String description) {
    UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
    Product newInstance = (Product)uow.newInstance(Product.class);
    newInstance.setProdId(prodId);
    newInstance.setName(name);
    newInstance.setImage(image);
    newInstance.setDescription(description);
    uow.commit();
 
    return newInstance;
  }
 
  public List<User> findAllStaffWithOpenAssignments() {
    Session session = getSessionFactory().acquireSession();
    List<User> result = 
      (List<User>)session.executeQuery("findAllStaffWithOpenAssignments", User.class);
      session.release();
    return result;
  }
 
  public User createUser(Integer userId, String userRole, String email, 
                         String firstName, String lastName, 
                         String streetAddress, String city, 
                         String stateProvince, String postalCode, 
                         String countryId) {
    UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
    User newInstance = (User)uow.newInstance(User.class);
    newInstance.setUserId(userId);
    newInstance.setUserRole(userRole);
    newInstance.setEmail(email);
    newInstance.setFirstName(firstName);
    newInstance.setLastName(lastName);
    newInstance.setStreetAddress(streetAddress);
    newInstance.setCity(city);
    newInstance.setStateProvince(stateProvince);
    newInstance.setPostalCode(postalCode);
    newInstance.setCountryId(countryId);
    uow.commit();
 
    return newInstance;
  }
  
  public void updateStaffSkills(Integer userId, List<Integer> prodIds) {
    List<Integer> currentSkills;
    
    if (userId != null) {
        List<ExpertiseArea> currentExpertiseList =  findExpertiseByUserId(userId);
        currentSkills = new ArrayList(currentExpertiseList.size());
        
        //Look for deletions
        for(ExpertiseArea expertise:currentExpertiseList){
            Integer prodId = expertise.getProdId();
            currentSkills.add(prodId);
    
            if (!prodIds.contains(prodId)){
                removeEntity(expertise);
            }
        }
        
        //Look for additions
        for (Integer newSkillProdId: prodIds){
            if(!currentSkills.contains(newSkillProdId)){
                //need to add
                this.createExpertiseArea(null, null,newSkillProdId,userId,"Qualified",null);
            }
        }
    }
  }
}

3.2.3 What You May Need to Know When Creating a Session Bean

Typically you create one session facade for every logical unit in your application. A task could be defined in a large scope, by a role for instance, such as creating a session facade for administrative client operations and another session facade for customer client operations.How you create and name your session facades can facilitate UI development, so tailoring your session facades toward a particular task and using names that describe the task is a good practice.

When you generate session facade methods, a findAll() method is created by default for each entity. If you do not want to generate this method, deselect it in the tree control on the Session Facade Options page.

When creating or editing session facade methods, you cannot select both TopLink and EJB entities. If the project is enabled for TopLink entities, only those entities will be available as session facade methods. Support for combining TopLink and EJB entities in a single session facade is planned for a future release.

3.2.4 How to Update an Existing Session Bean With New Entities

New session beans can be created at any time using the wizard. However, you may have an existing session bean that already contains custom implementation code that you want to update with new persistent data objects or methods.

To update an existing session bean, right click on the session bean and choose Edit Session Facade. Use the Session Facade Options dialog to select the entities and methods to expose. Note that if you have created new entities, the Session Facade Options dialog will display new entities in the same project, but cannot detect entities in different projects.

3.3 Creating Classes to Map to Database Tables

The TopLink map (.mwp file) contains the information required to represent database tables as Java classes. You can use the Create TopLink Map wizard or the Mapping editor to create this data, or manually code the file using Java and the TopLink API.

Use this information, or metadata, to pass configuration information into the run-time environment. The run-time environment uses the information in conjunction with the persistent entities (Java objects or EJB entity beans) and the code written with the TopLink API, to complete the application.

Figure 3-3 TopLink Metadata

This is a diagram of TopLink metadata interactions

Descriptors

Descriptors describe how a Java class relates to a data source representation. They relate object classes to the data source at the data model level. For example, persistent class attributes may map to database columns.

TopLink uses descriptors to store the information that describes how an instance of a particular class can be represented in a data source (see Section 3.4, "Mapping Classes to Tables"). Most descriptor information can be defined by TopLink, then read from the project XML file at run time.

Persistent Classes

Any class that registers a descriptor with a TopLink database session is called a persistent class. TopLink does not require that persistent classes provide public accessor methods for any private or protected attributes stored in the database.

3.3.1 How to Create Classes

To automatically create Java classes from your database table, use the Create Java Objects from Tables wizard. With this wizard you can create the following:

  • Java class for each table

  • TopLink map

  • Mapped attributes for each tables' columns

Figure 3-4 Create Java Objects from Tables Wizard

welcome screen from Creating Java Objects from Tables wizard

After creating the initial Java classes and TopLink mappings, use the Mapping editor to customize the information. Refer to the Oracle JDeveloper online help for additional information.

3.3.2 What Happens when you Create a Class

After completing the Create Java Objects from Tables wizard JDeveloper creates a TopLink map and adds it to the project.

Figure 3-5 Navigation Window

welcome screen from Creating Java Objects from Tables wizard

The wizard will also create TopLink descriptor and mappings for each Java attribute (as defined by the structure and relationships in the database).

Figure 3-6 Structure Window

welcome screen from Creating Java Objects from Tables wizard

3.3.3 What You May Need to Know

After creating a Java class from a database table, you can modify the generated TopLink descriptor and mappings. This section includes information on the following:

3.3.3.1 Associating Descriptors with Different Database Tables

The Create Java Objects from Tables wizard will associate the TopLink descriptor with a specific database table.

Use the Multitable Info tab in the Mapping editor (as shown in Figure 3-7) to associate an amendment method with a descriptor.

Figure 3-7 Sample Multitable Info Tab

welcome screen from Creating Java Objects from Tables wizard

3.3.3.2 Using Amendment Methods

You can associate a static Java method to be called when a descriptor is loaded at run time. This method can amend the run-time descriptor instance through the descriptor Java code API. Use this method to make some advanced configuration options that may not be currently supported by the TopLink.

The Java method must have the following characteristics:

  • Be public static.

  • Take a single parameter of type oracle.toplink.descriptors.ClassDescriptor.

Use the After Load tab in the Mapping editor (as shown in Figure 3-8) to associate an amendment method with a descriptor.

Figure 3-8 Sample After Load Tab

welcome screen from Creating Java Objects from Tables wizard

3.3.3.3 Modifying the Generated Code

When using the Create Java Objects from Tables wizard, Oracle JDeveloper automatically generates the basic code for your Java classes.

Example 3-3 Sample Generated Java Class

package mypackage;
  import java.util.ArrayList;
  import java.util.List;

public class Address {
  /**Map employeeCollection <-> mypackage.Employee
   * @associates <{mypackage.Employee}>
   */

  private List employeeCollection;
  private Long addressId;
  private String pCode;
...

3.4 Mapping Classes to Tables

One of the greatest strengths of TopLink is its ability to transform data between an object representation and a representation specific to a data source. This transformation is called mapping and it is the core of a TopLink project.

A mapping corresponds to a single data member of a domain object. It associates the object data member with its data source representation and defines the means of performing the two-way conversion between object and data source.

TopLink uses the metadata produced by Mapping editor to describe how objects and beans map to the data source. This approach isolates persistence information from the object model—developers are free to design their ideal object model and DBAs are free to design their ideal schema.

3.4.1 Types of Mappings

Within ADF, TopLink supports relational and object-relational mappings.

  • Relational Mappings – Mappings that transform any object data member type to a corresponding relational database (SQL) data source representation in any supported relational database. Relational mappings allow you to map an object model into a relational data model.

  • Object-Relational Mappings – Mappings that transform certain object data member types to structured data source representations optimized for storage in specialized object-relational databases such as Oracle Database. Object-relational mappings allow you to map an object model into an object-relational data model.

3.4.2 Direct Mappings

You can create the following direct mappings in TopLink:

  • Direct-to-field mappings – Map a Java attribute directly to a database field.

  • Type conversion mappings – Map Java values with simple type conversions, such as character to string.

  • Object type mappings – Use an association to map values to the database.

  • Serialized object mappings – Map serializable objects, such as multimedia objects, to database BLOB fields.

  • Transformation mappings – Allow you to create custom mappings where one or more fields can be used to create the object be stored in the attribute.

3.4.3 How to Create Direct Mappings

To map create Java classes directly to database tables, select the Java attribute in the TopLink Map – Structure window. Oracle JDeveloper displays a list of the available mappings for the selected attribute (as shown in Figure 3-9).

Figure 3-9 Mapping Editor

welcome screen from Creating Java Objects from Tables wizard

You can also use TopLink Automap feature to automatically map the attributes in a specific Java class or package. Refer to the Oracle JDeveloper online help for more information.

3.4.4 What Happens when you Create a Direct Mapping

Example 3-4 illustrates the Java code that Oracle JDeveloper generates when you create a direct-to-field direct mapping. In this example, the description attribute of the Products class maps directly to a field on the database table.

Example 3-4 Java Code for a Direct Mapping

...
package oracle.srdemo.model;
public class Products {

  private String description;

    public String getDescription() {
      return this.description;
    }

    public void setDescription(String description) {
      this.description = description;
    }
}

3.4.5 What You May Need to Know

Use the Mapping editor to customize the TopLink mappings. Some common customizations for direct mappings include:

  • Specifying the mapping as "read only." These mappings will not be included during update or delete operations.

  • Using custom get and set methods.

  • Defining a default value. This value will be used if the actual field in the database is null.

Figure 3-10 shows the General tab of a direct-to-field mapping in the Mapping editor. Each direct mapping (see Section 3.4.2, "Direct Mappings") may have additional, specific options as well. Refer to the Oracle JDeveloper online help for more information.

Figure 3-10 Sample DIrect-to-Field Mapping

welcome screen from Creating Java Objects from Tables wizard

3.5 Mapping Related Classes with Relationships

Relational mappings define how persistent objects reference other persistent objects. TopLink provides the following relationship mappings:

Do not confuse relational mappings with object-relational mappings. Object-relational mappings let you map an object model into an object-relational data model, such as Oracle Database. TopLink can create the following mappings:

Although the Oracle TopLink runtime supports these mappings, they must be created in Java code – you cannot use the Mapping editor.

3.5.1 How to Create Relationship Mappings

Similarly to direct mappings (see Section 3.4.3, "How to Create Direct Mappings"), to map create Java classes directly to database tables, select the Java attribute in the TopLink Map – Structure window.

Relationship mappings contain a Table Reference tab in the Mapping editor to define (or create) relationships on the database tables.

Figure 3-11 Sample Table Reference Tab

image of the Table Reference tab.

Refer to the Oracle JDeveloper online help for more information.

3.5.2 What Happens when you Create a Relationship

Example 3-5 illustrates the Java code that Oracle JDeveloper generates when you create a direct-to-field direct mapping. In this example, the address attribute of the ServiceRequest class has a one-to-one relationship to another class, User (that is, each ServiceRequest was created by one User)

Example 3-5 Java Code for a Relationship Mapping

package oracle.srdemo.model;

  
    /**Map createdBy <-> oracle.srdemo.model.Users
     * @associates <{oracle.srdemo.model.Users}>
     */
  private ValueHolderInterface createdBy;

    public Users getCreatedBy() {
      return (Users)this.createdBy.getValue();
    }

    public void setCreatedBy(Users createdBy) {
      this.createdBy.setValue(createdBy);
    }

3.5.3 What You May Need to Know

Use the Mapping editor to customize the TopLink mappings. Some common customizations for relationship mappings include:

  • Specifying the mapping as "read only." These mappings will not be included during update or delete operations.

  • Using custom get and set methods.

  • Defining a default value. This value will be used if the actual field in the database is null.

  • Using indirection. When using indirection, TopLink uses an indirection object as a placeholder for the referenced object: TopLink defers reading the dependent object until you access that specific attribute.

  • Configuring private or independent relationships. In a private relationship, the target object is a private component of the source object; destroying the source object will also destroy the target object. In an independent relationship, the source and target objects exist independently; destroying one object does not necessarily imply the destruction of the other.

  • Specifying bidirectional relationship in which the two classes in the relationship reference each other with one-to-one mappings

Figure 3-12 shows the General tab of a one-to-one mapping in the Mapping editor. Use the Table Reference tab (see Figure 3-13) to define the foreign key reference for the mapping. Each direct mapping (see Section 3.5, "Mapping Related Classes with Relationships") may have additional, specific options as well. Refer to the Oracle JDeveloper online help for more information.

Figure 3-12 Sample One-to-One Mapping, General Tab

welcome screen from Creating Java Objects from Tables wizard

Figure 3-13 Sample One-to-One Mapping, Table Reference Tab

Sample One-to-One Mapping, Table Reference Tab

3.6 Finding Objects by Primary Key

TopLink provides a predefined finder (findByPrimaryKey) that takes a primary key as an Object. This finder is defined at runtime – not in the Mapping editor

Example 3-6 Executing a Primary Key Finder

{
    Employee employee = getEmployeeHome().findByPrimaryKey(primraryKey);
}

3.7 Querying Objects

To query objects, you can create a TopLink Named query then create a data control for the class specified in the query. This will expose the TopLink query to the data control.

A named query is a TopLink query that you create and store for later retrieval and execution. Named queries improve application performance because they are prepared once and they (and all their associated supporting objects) can be efficiently reused thereafter making them well suited for frequently executed operations.

You can create the following queries:

3.7.1 How to Create a Query

You can create TopLink Named Queries by using the TopLink expression builder, SQL expressions, or EJB QL expressions. Using the Mapping editor (see Figure 3-14), you can configure queries at the descriptor- or session-level.

Figure 3-14 Named Queries Tab

welcome screen from Creating Java Objects from Tables wizard

3.7.2 What You May Need to Know

3.7.2.1 Using a Query By Example

A query by example enables you to specify query selection criteria in the form of a sample object instance that you populate with only the attributes you want to use for the query. To define a query by example, provide a ReadObjectQuery or a ReadAllQuery with a sample persistent object instance and an optional query by example policy.

With ADF, a TopLink query by example performs only in-memory querying.

3.7.2.2 Sorting Query Results

You cannot configure the sort criteria of a TopLink query from Oracle JDeveloper. You must write a Java method, using descriptor amendment method. See Section 3.3.3.2, "Using Amendment Methods" for more information.

3.8 Creating and Modifying Objects with a Unit of Work

A database transaction is a set of operations (create, read, update, or delete) that either succeed or fail as a single operation. The database discards, or rolls back, unsuccessful transactions, leaving the database in its original state.

In TopLink, transactions are contained in the unit of work object. You acquire a unit of work from a session and using its API, you can control transactions directly or through a Java 2 Enterprise Edition (J2EE) application server transaction controller such as the Java Transaction API (JTA).

The unit of work isolates changes in a transaction from other threads until it successfully commits the changes to the database. Unlike other transaction mechanisms, the unit of work automatically manages changes to the objects in the transaction, the order of the changes, and changes that might invalidate other TopLink caches.

The unit of work manages these issues by calculating a minimal change set, ordering the database calls to comply with referential integrity rules and deadlock avoidance, and merging changed objects into the shared cache. In a clustered environment, the unit of work also synchronizes changes with the other servers in the coordinated cache.

3.8.1 How to Create a Unit of Work

Example 3-7 shows how to acquire a unit of work from a client session object.

Example 3-7 Acquiring a Unit of Work

public UnitOfWork acquireUnitOfWork() {

  Server server = getServer();

  if (server.hasExternalTransactionController()) {
    return server.getActiveUnitOfWork();
  server.acquireUnitOfWork();

}

3.8.1.1 Creating Objects with Unit of Work

When you create new objects in the unit of work, use the registerObject method to ensure that the unit of work writes the objects to the database at commit time.

The unit of work calculates commit order using foreign key information from one-to-one and one-to-many mappings. If you encounter constraint problems during a commit transaction, verify your mapping definitions. The order in which you register objects with the registerObject method does not affect the commit order.

Example 3-8 and Example 3-9 show how to create and persist a simple object (without relationships) using the clone returned by the unit of work registerObject method.

Example 3-8 Creating an Object: Preferred Method

UnitOfWork uow = session.acquireUnitOfWork();
    Pet pet = new Pet();
    Pet petClone = (Pet)uow.registerObject(pet);
    petClone.setId(100);
    petClone.setName("Fluffy");
    petClone.setType("Cat");
uow.commit();

Example 3-9 shows a common alternative.

Example 3-9 Creating an Object: Alternative Method

UnitOfWork uow = session.acquireUnitOfWork();
    Pet pet = new Pet();
    pet.setId(100);
    pet.setName("Fluffy");
    pet.setType("Cat");
    uow.registerObject(pet); 
uow.commit();

Both approaches produce the following SQL:

INSERT INTO PET (ID, NAME, TYPE, PET_OWN_ID) VALUES (100, 'Fluffy', 'Cat', NULL)

Example 3-8 is preferred: it gets you into the pattern of working with clones and provides the most flexibility for future code changes. Working with combinations of new objects and clones can lead to confusion and unwanted results.

3.8.1.2 Typical Unit of Work Usage

TopLink uses the unit of work as follows:

  1. The client application acquires a unit of work from a session object.

  2. The client application queries TopLink to obtain a cache object it wants to modify, and then registers the cache object with the unit of work.

  3. The unit of work registers the object according to the object's change policy.

    By default, as each object is registered, the unit of work accesses the object from the session cache or database and creates a backup clone and working clone. The unit of work returns the working clone to the client application.

  4. The client application modifies the working object returned by the unit of work.

  5. The client application (or external transaction controller) commits the transaction.

  6. The unit of work calculates the change set for each registered object according to the object's change policy.

    By default, at commit time, the unit of work compares the working clones to the backup clones and calculates the change set (that is, determines the minimum changes required). The comparison is done with a backup clone so that concurrent changes to the same objects will not result in incorrect changes being identified. The unit of work then attempts to commit any new or changed objects to the database.

    If the commit transaction succeeds, the unit of work merges changes into the shared session cache. Otherwise, no changes are made to the objects in the shared cache. If there are no changes, the unit of work does not start a new transaction.

Figure 3-15 The Life Cycle of a Unit of Work

The life cycle of a unit of work

Example 3-10 shows the default life cycle in code.

Example 3-10 Unit of Work Life Cycle

// The application reads a set of objects from the database 
Vector employees = session.readAllObjects(Employee.class);

// The application specifies an employee to edit
. . .
Employee employee = (Employee) employees.elementAt(index);

try {
    // Acquire a unit of work from the session
    UnitOfWork uow = session.acquireUnitOfWork();

    // Register the object that is to be changed. Unit of work returns a clone
    // of the object and makes a backup copy of the original employee
    Employee employeeClone = (Employee)uow.registerObject(employee);

    // Make changes to the employee clone by adding a new phoneNumber. 
    // If a new object is referred to by a clone, it does not have to be
    // registered. Unit of work determines it is a new object at commit time
    PhoneNumber newPhoneNumber = new PhoneNumber("cell","212","765-9002");
    employeeClone.addPhoneNumber(newPhoneNumber);

    // Commit the transaction: unit of work compares the employeeClone with
    // the backup copy of the employee, begins a transaction, and updates the
    // database with the changes. If successful, the transaction is committed
    // and the changes in employeeClone are merged into employee. If there is an
    // error updating the database, the transaction is rolled back and the
    // changes are not merged into the original employee object
    uow.commit();
} catch (DatabaseException ex) {

    // If the commit fails, the database is not changed. The unit of work should
    // be thrown away and application-specific action taken
}
// After the commit, the unit of work is no longer valid. Do not use further

3.8.2 What Happens when you Modify a Unit of Work

In Example 3-11, a Pet is read prior to a unit of work: the variable pet is the cache copy clone for that Pet. Inside the unit of work, register the cache copy to get a working copy clone. We then modify the working copy clone and commit the unit of work.

Example 3-11 Modifying an Object

// Read in any pet
Pet pet = (Pet)session.readObject(Pet.class);
UnitOfWork uow = session.acquireUnitOfWork();
    Pet petClone = (Pet) uow.registerObject(pet);
    petClone.setName("Furry");
uow.commit();

In Example 3-12, we take advantage of the fact that you can query through a unit of work and get back clones, saving the registration step. However, the drawback is that we do not have a handle to the cache copy clone.

If we wanted to do something with the updated Pet after the commit transaction, we would have to query the session to get it (remember that after a unit of work is committed, its clones are invalid and must not be used).

Example 3-12 Modifying an Object: Skipping the Registration Step

UnitOfWork uow = session.acquireUnitOfWork();
    Pet petClone = (Pet) uow.readObject(Pet.class);
    petClone.setName("Furry");
uow.commit();

Both approaches produce the following SQL:

UPDATE PET SET NAME = 'Furry' WHERE (ID = 100)

Take care when querying through a unit of work. All objects read in the query are registered in the unit of work and therefore will be checked for changes at commit time. Rather than do a ReadAllQuery through a unit of work, it is better for performance to design your application to do the ReadAllQuery through a session, and then register in a unit of work only the objects that need to be changed.

3.8.2.1 Deleting Objects

To delete objects in a unit of work, use the deleteObject or deleteAllObjects method. When you delete an object that is not already registered in the unit of work, the unit of work registers the object automatically.

When you delete an object, TopLink deletes the object's privately owned child parts, because those parts cannot exist without the owning (parent) object. At commit time, the unit of work generates SQL to delete the objects, taking database constraints into account.

3.8.2.1.1 Explicitly Deleting Objects from the Database

If there are cases where you have objects that will not be garbage collected through privately owned relationships (especially root objects in your object model), then you can explicitly tell TopLink to delete the row representing the object using the deleteObject API, as shown in Example 3-13.

Example 3-13 Explicitly Deleting

UnitOfWork uow = session.acquireUnitOfWork();
    pet petClone = (Pet)uow.readObject(Pet.class);
    uow.deleteObject(petClone);
uow.commit();

The preceding code generates the following SQL:

DELETE FROM PET WHERE (ID = 100)

3.8.3 What You May Need to Know

The TopLink unit of work is a powerful transaction model. In addition to the items listed in this section, you should review the "Understanding TopLink Transactions" chapter in the Oracle TopLink Developer's Guide.

3.8.3.1 Unit of Work and Change Policy

The unit of work tracks changes for a registered object based on the change policy you configure for the object's descriptor. If there are no changes, the unit of work will not start a new transaction.

Table 3-1 lists the change policies that TopLink provides.

Table 3-1 TopLink Change Policies

Change Policy Applicable to...

Deferred Change Detection Policy

Wide range of object change characteristics.

The default change policy.

Object-Level Change Tracking Policy

Objects with few attributes or with many attributes and many changed attributes.

Attribute Change Tracking Policy

Objects with many attributes and few changed attributes.

The most efficient change policy.

The default change policy for EJB 3.0 or 2.x CMP on OC4J.


3.8.3.2 Nested and Parallel Units of Work

You can use TopLink to create the following:

3.8.3.2.1 Nested Unit of Work

You can nest a unit of work (the child) within another unit of work (the parent). A nested unit of work does not commit changes to the database. Instead, it passes its changes to the parent unit of work, and the parent attempts to commit the changes at commit time. Nesting units of work lets you break a large transaction into smaller isolated transactions, and ensures that:

  • Changes from each nested unit of work commit or fail as a group.

  • Failure of a nested unit of work does not affect the commit or rollback transaction of other operations in the parent unit of work.

  • Changes are presented to the database as a single transaction.

3.8.3.2.2 Parallel Unit of Work

You can modify the same objects in multiple unit of work instances in parallel because the unit of work manipulates copies of objects. TopLink resolves any concurrency issues when the units of work commits the changes.

3.9 Interacting with Stored Procedures

You can provide a StoredProcedureCall object to any query instead of an expression or a SQL string, but the procedure must return all data required to build an instance of the class you query.

Example 3-14 A Read-All Query with a Stored Procedure

ReadAllQuery readAllQuery = new ReadAllQuery();
call = new StoredProcedureCall();
call.setProcedureName("Read_All_Employees");
readAllQuery.setCall(call);
Vector employees = (Vector) session.executeQuery(readAllQuery);

Using a StoredProcedureCall, you can access the following:

Note:

You no longer need to use DatabaseQuery method bindAllParameters when using a StoredProcedureCall with OUT or INOUT parameters. However, you should always specify the Java type for all OUT and INOUT parameters. If you do not, be aware of the fact that they default to type String.

3.9.1 Specifying an Input Parameter

In Example 3-15, you specify the parameter POSTAL_CODE as an input parameter using the StoredProcedureCall method addNamedArgument, and you can specify the value of the argument using method addNamedArgumentValue.

Example 3-15 Stored Procedure Call with an Input Parameter

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("CHECK_VALID_POSTAL_CODE");
call.addNamedArgument("POSTAL_CODE");
call.addNamedArgumentValue("L5J1H5");
call.addNamedOutputArgument(
    "IS_VALID",    // procedure parameter name
    "IS_VALID",    // out argument field name
    Integer.class  // Java type corresponding to type returned by procedure
);
ValueReadQuery query = new ValueReadQuery();
query.setCall(call);
Number isValid = (Number) session.executeQuery(query);

The order in which you add arguments must correspond to the order in which you add argument values. In Example 3-16, the argument NAME is bound to the value Juliet and the argument SALARY is bound to the value 80000.

Example 3-16 Matching Arguments and Values in a Stored Procedure Call

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("CHECK_VALID_POSTAL_CODE");
call.addNamedArgument("NAME");
call.addNamedArgument("SALARY");
call.addNamedArgumentValue("Juliet");
call.addNamedArgumentValue(80000);

3.9.2 Specifying an Output Parameter

Output parameters enable the stored procedure to return additional information. You can use output parameters to define a readObjectQuery if they return all the fields required to build the object.

In Example 3-17, you specify the parameter IS_VALID as an output parameter using the StoredProcedureCall method addNamedOutputArgument.

Example 3-17 Stored Procedure Call with an Output Parameter

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("CHECK_VALID_POSTAL_CODE");
call.addNamedArgument("POSTAL_CODE");
call.addNamedOutputArgument(
    "IS_VALID",    // procedure parameter name
    "IS_VALID",    // out argument field name
    Integer.class  // Java type corresponding to type returned by procedure
);
ValueReadQuery query = new ValueReadQuery();
query.setCall(call);
query.addArgument("POSTAL_CODE");
Vector parameters = new Vector();
parameters.addElement("L5J1H5");
Number isValid = (Number) session.executeQuery(query,parameters);

Note:

Not all databases support the use of output parameters to return data. However, because these databases generally support returning result sets from stored procedures, they do not require output parameters.

If you are using an Oracle database, you can make use of TopLink cursor and stream query results.

3.9.3 Specifying an Input / Output Parameter

In Example 3-18, you specify the parameter LENGTH as an input/output parameter and specify the value of the argument when it is passed to the stored procedure using the StoredProcedureCall method addNamedInOutputArgumentValue. If you do not want to specify a value for the argument, use method addNamedInOutputArgument.

Example 3-18 Stored Procedure Call with an Input/Output Parameter

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("CONVERT_FEET_TO_METERs");
call.addNamedInOutputArgumentValue(
    "LENGTH",          // procedure parameter name
    new Integer(100),  // in argument value
    "LENGTH",          // out argument field name
    Integer.class      // Java type corresponding to type returned by procedure
)
ValueReadQuery query = new ValueReadQuery();
query.setCall(call);
Integer metricLength = (Integer) session.executeQuery(query);

3.9.4 Using an Output Parameter Event

TopLink manages output parameter events for databases that support them. For example, if a stored procedure returns an error code that indicates that the application wants to check for an error condition, TopLink raises the session event OutputParametersDetected to allow the application to process the output parameters.

Example 3-19 Stored Procedure with Reset Set and Output Parameter Error Code

StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("READ_EMPLOYEE");
call.addNamedArgument("EMP_ID");
call.addNamedOutputArgument(
    "ERROR_CODE",    // procedure parameter name
    "ERROR_CODE",    // out argument field name
    Integer.class  // Java type corresponding to type returned by procedure
);
ReadObjectQuery query = new ReadObjectQuery();
query.setCall(call);
query.addArgument("EMP_ID");
ErrorCodeListener listener = new ErrorCodeListener();
session.getEventManager().addListener(listener);
Vector args = new Vector();
args.addElement(new Integer(44));
Employee employee = (Employee) session.executeQuery(query, args);

3.9.5 Using a StoredFunctionCall

You use a StoredProcedureCall to invoke stored procedures defined on databases that support them. You can also use a StoredFunctionCall to invoke stored functions defined on databases that support them, that is, on databases for which the DatabasePlatform method supportsStoredFunctions returns true.

In general, both stored procedures and stored functions let you specify input parameters, output parameters, and input and output parameters. However, stored procedures need not return values, while stored functions always return a single value.

The StoredFunctionCall class extends StoredProcedureCall to add one new method: setResult. Use this method to specify the name (and alternatively both the name and type) under which TopLink stores the return value of the stored function.

When TopLink prepares a StoredFunctionCall, it validates its SQL and throws a ValidationException under the following circumstances:

  • If your current platform does not support stored functions

  • If you fail to specify the return type

In Example 3-20, note that the name of the stored function is set using StoredFunctionCall method setProcedureName.

Example 3-20 Creating a StoredFunctionCall

StoredFunctionCall functionCall = new StoredFunctionCall();
functionCall.setProcedureName("READ_EMPLOYEE");
functionCall.addNamedArgument("EMP_ID");
functionCall.setResult("FUNCTION_RESULT", String);
ReadObjectQuery query = new ReadObjectQuery();
query.setCall(functionCall);
query.addArgument("EMP_ID");
Vector args = new Vector();
args.addElement(new Integer(44));
Employee employee = (Employee) session.executeQuery(query, args);

3.9.6 Query Sequencing

With query sequencing, you can access a sequence resource using custom read (ValueReadQuery) and update (DataModifyQuery) queries and a preallocation size that you specify. This allows you to perform sequencing using stored procedures and allows you to access sequence resources that are not supported by the other sequencing types that TopLink provides.

3.10 Exposing Services with ADF Data Controls

The easiest way to bind services to a user interface is by using the ADF Data Control.

This section includes information on the following:

3.10.1 How to Create ADF Data Controls

To create an ADF data control from an EJB session bean, right-click a session bean in the Navigator and choose Create Data Control or drag a session bean onto the Data Control Palette.

Note:

J2EE developers who do not want to rely on Oracle-specific libraries may use managed beans instead of the ADF data control. This is more complex and beyond the scope of this book.

When you create a data control from an EJB 3.0 session bean, several XML files are generated and displayed in the Navigator. The generated files and the Data Control Palette are covered in the following sections.

3.10.2 Understanding the Data Control Files

When you create a data control, the following XML files are generated in the model

  • DataControls.dcx - data control definition file

  • <session_bean>.xml - structure definition file

  • ReadOnlyCollection.xml - design-time XML file

  • ReadOnlySingleValue.xml - design-time XML file

  • UpdateableCollection.xml - design-time XML file

  • UpdateableSingleValue.xml - design-time XML file

  • <entity_name>.xml - entity definition file, one per entity

How these files are related and used are covered in greater detail in Appendix A, "Reference ADF XML Files".

3.10.2.1 About the DataControls.dcx File

The DataControls.dcx file is created when you register data controls on the business services. The .dcx file identifies the Oracle ADF model layer adapter classes that facilitate the interaction between the client and the available business service. In the case of EJB, web services, and bean-based data controls, you can edit this file in the Property Inspector to add or remove parameters and to alter data control settings. For example, you can use the .dcx file to set global properties for various items, such as whether to turn on/off sorting.

3.10.2.2 About the Structure Definition Files

When you register a session bean as an Oracle ADF data control, an XML definition file is created in the Model project for every session bean. This file is commonly referred to as the structure definition file. The structure definition file has the same name as the session bean, but has a .xml extension.

A structure definition is made up of three types of objects:

  • Attributes

  • Accessors

  • Operations

3.10.2.3 About the Entity XML Files

When you create a data control, an XML file is generated for each entity (TopLink, EJB, or Java bean). These files are used for both ADF design-time and runtime. These files describe the structure of the class as well as UI hints, validators and labels for each attribute.

3.10.2.4 About the Design-time XML Files

Four files are generated solely for the design-time:

  • ReadOnlyCollection.xml

  • ReadOnlySingleValue.xml

  • UpdateableCollection.xml

  • UpdateableSingleValue.xml

These files are referenced by MethodAccessor definitions as the CollectionBeanClass which describes the available operations. Typically you do not edit this file by hand, but you could customize items on the Data Control Palette.

3.10.3 Understanding the Data Control Palette

Client developers use the Data Control Palette to create databound HTML elements (for JSP pages), databound Faces elements (for JSF JSP pages), and databound Swing UI components (for ADF Swing panels). The Data Control Palette comprises two selection lists:

  • Hierarchical display of available business objects, methods, and data control operations

  • Dropdown list of appropriate visual elements that you can select for a given business object and drop into your open client document

Additionally, web application developers use the Data Control Palette to select methods provided by the business services that can be dropped onto the data pages and data actions of a page flow.

The Palette is a direct representation of the XML files examined in the previous sections, so by editing the files, you can change the elements contained in the Palette.

The hierarchical structure of the business services displayed in the Data Control Palette is determined by which business services you have registered with the data controls in your model project. The palette displays a separate root node for each business service that you register.

Figure 3-16 Data Control Palette

image of the data control palette

3.10.3.1 Overview of the Data Control Business Objects

The root node of the Data Control Palette represents the data control registered for the business service. Proceeding down the hierarchy from the root data control node, the palette represents bean-based business services as constructors, attributes, accessors or operations:

  • Constructors - Createable types are contained within the Constructors node. These types call the default constructor for the object.

  • Attributes - such as bean properties, which can define simple scalar value objects, structured objects (beans), or collections.

  • Accessors - get() and set() methods.

  • Operations - such as bean methods, which may or may not return a value or take method parameters. For Web Services, the Data Control Palette displays only operations.

For more information on using the Data Control Palette, see Chapter 5, "Displaying Data on a Page". For more information on the Data Control files and how they related to each other, see Appendix A, "Reference ADF XML Files".

3.10.3.2 Refreshing ADF Data Controls After Modifying Business Services

After you have already created the data control definition for your Model project, you may decide to update the data control after modifying your business services. Refreshing the data control definition makes the latest business service changes available to the ADF application.The action you take to refresh the data control definition depends upon the type of change to the model project.

3.10.3.2.1 Viewing modified data controls in the Data Control Palette:

If the palette is not yet displayed, select the View menu and choose Data Control Palette. If the palette is already displayed, right-click in the palette and choose Refresh.

3.10.3.2.2 Refreshing a data control definition for business services you have modified

In the model project, define the new properties of the bean or other business service you want to create. Compile the .java file to regenerate the business service's metadata in its corresponding .xml file. If the modified business service is bean-based (such as an EJB session bean), right-click the bean's .xml file and choose Refresh.

Note: In the case of ADF Business Components, the data control definition is automatically updated whenever you make changes to your ADF BC project files.

3.10.3.2.3 Removing a data control definition for business services that have been removed:

To To remove a data control definition, in the view project, select the DataBindings.dcx file and in the Structure window, select the data control node that represents the business service that no longer appears in your Model project. Right-click the data control node and choose Delete.

JDeveloper updates the data control definition file (DataBindings.dcx) in the Model project. The DataBindings.dcx file identifies the Oracle ADF model layer adapter classes that facilitate the interaction between the client and the available business services.

3.10.3.2.4 Updating a data control after renaming or moving a business services

In the model project, if you rename your business service or move it to a new package, you must update the reference to the model project in the client's data control definition.

In the view project, select the DataBindings.dcx file. In the Structure window, select the data control node that represents the moved business service. In the Property Inspector, edit the Package attribute to supply the new package name.