Database application development is a feature of JBuilder Professional and Enterprise. Distributed application development is a feature of JBuilder Enterprise.
JBuilder makes it easy to write a custom resolver for your data when you are accessing data from a custom data source, such as EJB, application servers, SAP, BAAN, IMS, OS/390, CICS, VSAM, DB2, etc.
The retrieval and update of data from a
data source, such as an Oracle or Sybase server, is isolated to two key
interfaces: providers and resolvers. Providers retrieve data from a data source into a StorageDataSet
. Resolvers save changes back to a data source. By cleanly isolating the retrieval and updating of data to two interfaces, it is easy to create new provider/resolver components for new data sources. JBuilder currently provides implementations for standard JDBC drivers that provide access to popular databases such as support for Oracle, Sybase, Informix, InterBase, DB2, MS SQL Server, Paradox, dBASE, FoxPro, Access, and other popular databases. These include:
An example project with a custom provider and resolver is located in the JBuilder /samples/DataExpress/CustomProviderResolver directory of your JBuilder installation. The sample file TestApp.java is an application with a frame that contains a JdbTable
and a JdbNavToolBar
. Both visual components are connected to a TableDataSet
component where data is provided
from a custom Provider (defined in the file
ProviderBean.java), and data is saved with a custom Resolver (defined in the file ResolverBean.java).
This sample application reads from and saves changes to the text file
data.txt, a simple non-delimited text file. The structure of data.txt is
described in the interface file DataLayout.java.
A tutorial describing how to write a custom ProcedureResolver
is available in the
"Tutorial: saving changes with a ProcedureResolver" topic in this chapter.
If you have not specifically instantiated a QueryResolver
component when resolving data changes back to the data source, the built-in resolver logic creates a default QueryResolver
component for you.
This topic explores using the QueryResolver
to customize the resolution process.
The QueryResolver
is a DataExpress package component which implements the SQLResolver
interface.
It is this SQLResolver
interface which is used by the
ResolutionManager
during the process of resolving changes back to the database. As its name implies, the ResolutionManager
class manages the resolving phase.
Each StorageDataSet
has a resolver
property. If this property has not been set when you call the Database.saveChanges()
method, it creates a default QueryResolver
and attempts to save the changes for a particular DataSet
.
QueryResolver
component to your application using the JBuilder visual design tools:
Database
object, and a QueryDataSet
object. See "Querying a database" for how to do this.
Frame
file in the content pane. Click the Design tab to display the UI designer.
QueryResolver
component from the Data Express tab of the component palette.
QueryResolver
object.
Connect the QueryResolver
to the appropriate DataSet
. To do this, use the Inspector to set the resolver
property of the StorageDataSet
, for example queryDataSet1
, to the appropriate QueryResolver
, which is, by default, queryResolver1
.
You can connect the same QueryResolver
to more than one DataSet
if the different DataSet
objects share the same event handling. If each DataSet
needs custom event handling, create a separate QueryResolver
for each StorageDataSet
.
You control the resolution process by intercepting Resolver
events. When the QueryResolver
object is selected in the content pane, the Events tab of the Inspector displays its events. The events that you can control (defined in the ResolverListener
interface) can be grouped into three categories of:
Notification that an action has been performed:
Conditional errors that have occurred. These are internal errors, not server errors.
When the resolution manager is about to perform a delete, insert, or update action, the corresponding event notification from the first set of events (deletingRow
, insertingRow
, and updatingRow
) is generated. One of the parameters passed with the notification to these events is a ResolverResponse
object. It is the responsibility of the event handler (also referred to as the event listener
) to determine whether or not the action is appropriate and to return one of the following (ResolverResponse
) responses:
resolve()
instructs the resolution manager to continue resolving this row
skip()
instructs the resolution manager to skip this row and continue with the rest
If the event's response is resolve()
(the default response), then one of the second set of events (deletedRow
, insertedRow
or updatedRow
) is generated as appropriate. No response is expected from these events. They exist only to communicate to the application what action has been performed.
If the event's response is skip()
, the current row is not resolved and the resolving process continues with the next row of data.
If the event terminates the resolution process, the inserting
method gets called, which in turn calls response.abort()
. No error event is generated because error events are wired to respond to internal errors. However, a generic ResolutionException
is thrown to cancel the resolution process.
If an error occurs during the resolution processing, for example, the server did not allow a row to be deleted, then the appropriate error event (deleteError
, insertError
, or updateError
) is generated. These events are passed the following:
DataSet
involved in the resolving
DataSet
that has been filtered to show only the affected rows
Exception
which has occurred
ErrorResponse
object.
Exception
ErrorResponse
responses:
If the event handler throws a DataSetException
, it is treated as a ResolverResponse.abort()
. In addition, it triggers the error event described above, passing along the user's Exception
.
For an example of resolver events, see ResolverEvents.jpr and associated files in the /samples/DataExpress/ResolverEvents directory of your JBuilder installation. In the ResolverEvents application,
QueryResolver
object which takes control of the resolution process.
deletingRow
event.
insertingRow
event and a ResolverResponse
of abort()
.
ListControl
. This demonstrates how to access both the new information as well as the prior information during the resolution process.
This topic discusses custom data resolvers, and how they can be used as resolvers
for a TableDataSet
and any DataSet
derived from TableDataSet
. The main method to implement is resolveData()
. This method collects the changes to a StorageDataSet
and resolves these changes back to the source.
In order to resolve data changes back to a source,
StorageDataSet
is blocked for changes in the provider during the resolution process. This is done by calling the methods:
Locate changes in the data by creating a DataSetView
for each of the
inserted, deleted, and updated rows. That is accomplished using the following method
calls:
StorageDataSet.getInsertedRows(DataSetView);
StorageDataSet.getDeletedRows(DataSetView);
StorageDataSet.getUpdatedRows(DataSetView);
It is important to note that
Close each of the DataSetView
s
after the data has been resolved, or if an exception occurs during resolution. If the DataSetView
s are not closed, the
StorageDataSet
retains references to it, and such a view will never be garbage collected.
Errors can be handled in numerous ways, however the DataSet
must be told to
change the status of the changed rows. To do this,
RowStatus.PENDING_RESOLVED:
. The code to mark the current row this way is:
DataSet.markPendingStatus(true);
Call this method for each of the inserted, deleted, and updated rows that is being resolved.
Call one or more of the following methods to reset the RowStatus.PENDING_RESOLVED bit. Which methods are called depends on the error handling approach
markPendingStatus(false);
The markPendingStatus
method resets the current row.
resetPendingStatus(boolean resolved);
This resetPendingStatus
method resets all the rows in the DataSet
.
resetPendingStatus(long internalRow, boolean resolved);
This resetPendingStatus
method resets
the row with the specified internalRow
id.
Reset the resolved
parameter, using of one of the resetPendingStatus
methods, to true for rows whose changes were actually made to the data source.
When the PENDING_RESOLVED bit is reset, the rows retain the status of recorded changes. The rows must be reset and resolved so that
DataSet
.
The row changes that were not made will clear the PENDING_RESOLVED bit, however, the changes are still recorded
in the DataSet
.
Some resolvers will choose to abandon all changes if there are any errors. In fact, that is the default behavior of QueryDataSet
. Other resolvers may choose to commit certain changes, and retain the failed changes for error messages.
Master-detail resolution presents some issues to be considered.
If the source of the data has referential integrity rules,
the DataSet
s may have to be resolved in a certain order. When using JDBC, JBuilder provides the SQLResolutionManager
class. This class ensures the master data set resolves its inserted rows before enabling the detail data set to resolve its inserted row, and also ensures that detail data sets resolve their deleted rows before the deleted rows of the master data set are resolved. For more information on resolving master-detail relationships, see the "Saving changes in a master-detail relationship" topic in this chapter.