Oracle® Application Development Framework Developer's Guide 10g Release 3 (10.1.3.0) Part Number B28967-02 |
|
|
View PDF |
This chapter describes how to build and use application services in JDeveloper
This chapter includes the following sections:
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.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.
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.
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.
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.
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); } } } } }
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.
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.
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.
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.
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
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.
After completing the Create Java Objects from Tables wizard JDeveloper creates a TopLink map and adds it to the project.
The wizard will also create TopLink descriptor and mappings for each Java attribute (as defined by the structure and relationships in the database).
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:
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.
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.
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; ...
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.
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.
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.
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).
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.
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.
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.
Relational mappings define how persistent objects reference other persistent objects. TopLink provides the following relationship mappings:
Aggregate object mappings - Strict one-to-one mappings that require both objects to exist in the same database row.
One-to-one mappings - map a reference to another persistent Java object to the database.
One-to-many mappings - Map Java collections of persistent objects to the database.
Aggregate collection mappings also map Java collections of persistent objects to the database.
Many-to-many mappings use an association table to map Java collections of persistent objects to the database
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:
Object-Relational Structure Mapping – Map to object-relational aggregate structures (the Struct
type in JDBC and the OBJECT
type in Oracle Database)
Object-Relational Reference Mapping – Map to object-relational references (the Ref
type in JDBC and the REF
type in Oracle Database)
Object-Relational Array Mapping – Map a collection of primitive data to object-relational array data types (the Array type in JDBC and the VARRAY type in Oracle Database).
Object-Relational Object Array Mapping – Map to object-relational array data types (the Array type in JDBC and the VARRAY type in Oracle Database).
Object-Relational Nested Table Mapping – Map to object-relational nested tables (the Array type in JDBC and the NESTED TABLE type in Oracle Database)
Although the Oracle TopLink runtime supports these mappings, they must be created in Java code – you cannot use the Mapping editor.
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.
Refer to the Oracle JDeveloper online help for more information.
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); }
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.
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
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:
ReadAllQuery
ReadObjectQuery
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.
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.
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.
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.
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(); }
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.
TopLink uses the unit of work as follows:
The client application acquires a unit of work from a session object.
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.
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.
The client application modifies the working object returned by the unit of work.
The client application (or external transaction controller) commits the transaction.
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.
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
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.
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.
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.
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.
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. |
You can use TopLink to create the following:
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.
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 useDatabaseQuery
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
.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);
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.
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);
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);
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);
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.
The easiest way to bind services to a user interface is by using the ADF Data Control.
This section includes information on the following:
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.
When you create a data control, the following XML files are generated in the model
UpdateableSingleValue.xml - design-time XML file
How these files are related and used are covered in greater detail in Appendix A, "Reference ADF XML Files".
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.
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
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.
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.
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.
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".
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.
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.
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.
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.
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.