< Day Day Up > |
Database and rowset controls enable you to access database data by using a simple relational object model. With database and rowset controls, you can access data from a single table, from multiple tables using joins, or directly using SQL statements. These controls simplify data access by using simple Java methods and value objects. Rowset and database controls provide similar capabilities: accessing a database. At their core , database and rowset controls are very similar; both use Java Database Connectivity (JDBC) to return content via a previously defined DataSource . The main difference between the two, as you saw in Chapter 3, is that rowset controls contain a significant amount of generated code for the purpose of accessing a database table with RowSet objects. Database controls contain only logic to manage a connection, and all database access methods must be written by a developer. An important point to remember about database-type controls is that they support only synchronous method calls. They cannot generate or accept callbacks. At first glance, not being able to accept callbacks appears to be a limitation. However, it's not so much a limitation as simply a side effect of how database and rowset controls are created. Database controls, and most controls created by wizards, are more like interfaces than classes and can't contain user code. The real power of these controls is in their capability to define custom methods and return values. Listing 6.1 shows a simple database control that returns scalar values, value objects, and arrays of value objects. Listing 6.1. hotelTableCtrl.jcxpackage controls; import com.bea.control.*; import java.sql.SQLException; /** * * @jc:connection data-source-jndi-name="wonderlandDS" */ public interface hotelTableCtrl extends DatabaseControl, com.bea.control.ControlExtension { static final long serialVersionUID = 1L; static public class HotelVO { public int id; public String name; public int rating; public int capacity; } /** * @jc:sql statement="SELECT CAPACITY FROM WONDERLAND.HOTEL where ID={id}" */ int getCapacityById(int id) throws SQLException; /** * @jc:sql statement= "SELECT ID, NAME,RATING,CAPACITY FROM WONDERLAND.HOTEL where ID={id}" */ HotelVO getByID(int id) throws SQLException; /** * @jc:sql statement="SELECT ID, NAME,RATING,CAPACITY FROM WONDERLAND.HOTEL" */ HotelVO[] getAll() throws SQLException; } Database Control BasicsCreating a database control is similar to creating a rowset control. To create a database control, follow these steps (shown in Figure 6.6):
Figure 6.6. Adding a rowset or database control.
Methods are added to a rowset control by dragging a method to the control from the Operations section of the Palette and then entering an appropriate name for the method. Figure 6.7 shows the properties for a rowset control. All properties are optional, with the exception of the statement property. Figure 6.7. Rowset control properties.
You must also enter a SQL statement for each method. There are two ways to do this:
The first method is preferred over the second primarily because it shows the method signature and SQL statement together. However, there's no syntax checking on the SQL statement, so it's a matter of personal preference as to which works best for you. Every method in a rowset or database control looks similar to this: /** * @jc:sql optional attributes statement="Parameterized SQL" */ returnType methodName(parameters) throws SQLException; Database controls, and their rowset cousins, define and expose methods by using the @jc:sql annotation. This annotation is a signal to the compiler that it should create a method implementation. Remember that .jcx files are actually annotated interfaces that return data based on the SQL provided in the statement property. There are a number of optional attributes as well. These optional attributes include number or results returned, for example, and are examined in more detail in "Selecting Data" later in this chapter. Statements can be simple, such as select count(*) from WONDERLAND.GUEST , or require parameter substitutions. Parameter substitution is the process by which a user variable is used in place of a variable in an SQL statement at runtime. Every method returns somethinga single simple value, an array of simple values, a single complex object, an array of complex objects, or a collection. Finally, all methods must throw a java.sql.SQLException . Selecting DataA powerful aspect of database and rowset controls is their capability to map data from a result set to Java variables and classes. One of the methods defined for the hotel table in the Casino example returns a single scalar integer value based on a supplied parameter value. The following code shows the connection between the defined parameter id and the select statement that uses it. At statement execution time, the where clause has all the values substituted and the results gathered for return. Table 6.2 shows a complete list of the SQLtoJava type mappings: /** * @jc:sql statement="SELECT CAPACITY FROM WONDERLAND.HOTEL where ID={id}" */ int getCapacityById(int id) throws SQLException;
This code example also shows a number of important features of database methods. Workshop assumes that because a simple defined scalar value is returned that the select statement will return only a single row. If the select statement returned multiple rows, only the first row would be returned to the caller. Workshop doesn't stop there, however; the name of the actual database column, the column type, and the Java equivalent are returned at runtime. This metadata is used to ensure that the SQL can be mapped to the return data. When executed, the return value is converted to the requested type, if this conversion is possible. You could just as easily have defined getCapacityById in this way: String getCapacityById(int id) throws SQLException; You can return the value as a string because integers can be converted to strings. Table 6.2. SQLtoJava Type Mappings
What about returning complex types? Listing 6.1 included HotelVO , an inner class used as a value object. This inner class is used as the return type of a select statement returning multiple columns of data. Assume for a moment that a where clause of a select statement returns a single complete row of data. You can return that data in its entirety by writing a method similar to the following: /** * @jc:sql statement="SELECT ID, NAME,RATING,CAPACITY FROM WONDERLAND.HOTEL where ID={id}" */ HotelVO getByID(int id) throws SQLException; At runtime, the return types and names of the defined return classin this case, HotelVO and the columns of the result are examined, and an appropriately populated HotelVO object is returned. Until now, you've examined select statements returning only a single row of data. However, more often than not, a select statement returns multiple rows of data. You can map this type of select statement to an array of value objects by simply specifying the return as a Java array, as shown here (note the brackets after HotelVO ): /** * @jc:sql statement="SELECT ID, NAME,RATING,CAPACITY FROM WONDERLAND.HOTEL" */ HotelVO [] getAll() throws SQLException; The returned result is then mapped to an array of objects. Note that if an array is not defined, and the select statement returns multiple rows of data, only the first row is returned.
In addition to arrays, there are two other mechanisms for returning data: iterators and hashmaps. Hashmaps are useful when the columns in a table might change, which would result in an exception if the return types were hard-coded. To code a hashmap method, you define the return type as java.util.HashMap . You then access the data by using the hashmap's get() method, as shown in this example: java.util.HashMap result; result = dbcontrol.findbyName... (); If (result != null) { String firstName = (String) result.get("FirstName"); } else { ... column name not found }
Iterators are similar to returning arrays, in that multiple results are returned. You define a method that returns an iterator (return type java.util.Iterator ) as shown here: /** * @jc:sql iterator-element-type="HotelVO" statement="SELECT ID, NAME,RATING,CAPACITY FROM WONDERLAND.HOTEL" */ java.util.Iterator getAll() throws SQLException; Callers then cast the iterator methods to type HotelVO as each result is processed . Parameter SubstitutionWebLogic Workshop generates a JDBC-prepared statement based on the statement attribute of the jc:sql annotation. The performance of a prepared statement comes from precompiling the statement as well as parameter substitution at execution time. WebLogic Workshop has rich support for parameter substitution in a where clause of an SQL statement. Parameter substitution in an SQL statement happens when a set of curly braces ( {} ) is encountered . Parameter substitution supports three styles of replacement:
Parameter substitution is based on a set of rules:
These rules are best illustrated by example. You've already seen an example of simple substitution, so the following paragraphs concentrate on indirect and wholesale substitution. Listing 6.1 defined an inner class, HotelVO , which included rating and capacity fields. You can use these fields as input, using indirect parameters via dot notation, as shown here: /** * @jc:sql statement="SELECT * FROM WONDERLAND.HOTEL where CAPACITY> {in.capacity} and rating={in.rating}" */ HotelVO[] getAllByCapacityAndRating(HotelVO in) throws SQLException; In general, the fields on the class being used must be declared public , or a com.bea.control.ControlException is thrown. If a class has an inner subclass, it too can be accessed with dot notation. For example, assuming a class b with a variable c , and b is within a class a , you could reference field c by entering a.b.c . In wholesale substitution, a substitution is made without error checking. For example, say you want to obtain the total number of records in a database table. The SQL statement could easily be written as select count(*) from someTable ; someTable is provided at runtime and the statement is executed directly rather than via a prepared statement. If a curly brace substitution encounters a :sql statement , the remainder of the SQL is added directly without error checking of any kind. You could achieve the required result by coding the method as follows : /** * @jc:sql statement="SELECT count(*) from {sql: tableName}" */ integer count(tableName) throws SQLException;
Updates, Inserts, and DeletesDeletes and updates are, for all practical purposes, the same as select statements. That is, method definition, parameter substitution, and so forth work the same way. A simple way to code an insert is to define an SQL insert statement that uses dot notation to complete the values, as shown here: /** * @jc:sql statement:: * INSERT into WONDERLAND.HOTEL (* "NAME","RATING","CAPACITY" * VALUES ({in.name}, {in.rating},{in.capacity}) * :: */ void insertByHotelVO(HotelVO in);
Testing a Database ControlYou can test a database control by generating a test Web service. Don't worry that the test harness is actually a Web service; you'll be driving it by using a simple Web page. To generate a test Web service for a database control (or any control, for that matter), select the control, right-click on it, and choose Generate Test JWS file. A new file is generated with Test.jws appended to the original control name. Select the new test Web service and run it by choosing Debug, Start from the WebLogic Workshop menu. Figures 6.9 to 6.12 show the sequence of steps for testing a control. Figure 6.9 shows starting a conversation. All Web service controls operate in the context of a conversation, although for database and rowset controls, it's not important. Figure 6.9. Starting a test conversation.
Figure 6.12. Examining the result.
Figure 6.10 shows the current conversation state. Click the Continue This Conversation link, and then select the method you'd like to test. Figure 6.10. Continuing a conversation.
Figure 6.11 shows several methods, each of which can be tested . Enter a value for the method's variables and click the button labeled with the method name. You then see an error page (if, for example, you entered invalid SQL) or the result of the call as well as a fair amount of background information about the call. Figure 6.11. Entering test values.
Figure 6.12 shows the result of testing the getCapacityById method. You might need to scroll down to find the returned result of your database call.
Custom Java ControlsCustom Java controls provide an excellent mechanism for encapsulating business logic, aggregating functionality, or creating reuseable logic. Often multiple database and rowset controls, EJBs, Web services, logging, and other controls are combined intelligently to create a simplified facade. To create a Java control, select a folder for the new control and choose File, New, Custom Java Control from the Workshop menu. Java controls are separated into an interface, which represents all the control's method signatures and is contained in a .java file, and its implementation, which is contained in a file ending in Impl with the extension jcs . For all practical purposes, custom Java controls are simple Java classes that are exposed in the Workshop environment and can use other controls to perform work. Adding methods to a custom Java control is as simple as dragging an operation from the Workshop Palette and entering the appropriate signature. After dragging a method to a control and setting its name, double-click to open the method in Workshop and update the method signature to match your specific needs. Adding a method to a custom Java control results in two changes to the source files. The first is a change in the Impl.jcs file. The following code shows a method that takes an integer and results in a string version: /** * @common:operation */ public String methodTakingInteger(int input) { return new Integer(input).toString(); } The second change is made to the .java file, and an equivalent entry is made there. The .java file actually represents an interface to the control rather than its implementation and is required to support callbacks. Normally, this file is maintained by Workshop instead of the developer editing it directly. Adding a callback is done the same way as adding a method, but it produces a different result. When a method is a definition in the .java file, an implementation skeleton is added in the Impl.jcs file. Callbacks result in a class definition being added to the .java file and a callback variable being added to the Impl.jcs . The following code represents a callback that passes a String : interface Callback { void timerFired(String time); } Remember that callbacks differ from other methods, in that they are actually implemented by the client, not the control developer. The callback interface informs Workshop that the client can implement this method. If a client wants to implement the callback, all you need to do is click on the method's definition, and a skeleton implementation is added to the client control. Firing a callback (so that a client control gets the method invocation) requires calling the callback method from a method within the control. Remember that adding a callback method results in a callback variable being added to the control. You use the callback object to fire the callback method. Any client control that has implemented the callback then gets the callback notification.
The following code shows a callback being fired : public void myTimer_onTimeout(long time) { callback.timerFired(new Integer((int) time).toString()); System.out.println("MyCustomControl:: timer fired"); myTimer.restart(); } Custom Java controls, more often than not, actually use other controls as vehicles to gather data, implement business logic, or communicate in some way with other systems, often acting as a facade. You can add a control to a custom Java control by dragging it from a folder within the same project or by clicking Add in Workshop's Data Palette, selecting a control, and then following the wizard steps.
Figure 6.13 shows the result of adding a Timer control to the custom Java control. Any type of control can be added to a custom Java control using one of these two methods. Figure 6.13. A custom control with a Timer control added.
|
< Day Day Up > |