This chapter introduces the SilverStream data access model and its supporting architecture. It has the following topics:
The SilverStream data access API is a multi-layered architecture providing classes and interfaces that allow you to access virtually any data source and display it using any Java or HTML component. The API provides a programming model that includes consistent data binding mechanisms for SilverStream forms, pages, and business objects, as well as for Java and HTML clients developed outside of SilverStream.
This diagram illustrates the SilverStream layered architecture.
To simplify data access programming, SilverStream provides a default data access model that uses pre-built components to implement these layers. For most cases, you simply bind the data cache object to a database table, add controls to your form or page, then bind them to a column in the AgcData, AgpData, or AgcDataPrimary. Once the objects are bound, SilverStream transparently manages the database connection, data retrieval, data caching, and data display. You program only the data navigation and manipulation.
Here is a summary of each layer:
The SilverStream components, such as forms and pages or external Java clients, that display data to the user or modify cached data supplied by a provider. For example, you can bind controls on a page or form to a column in a data cache object at design time and runtime.
Data cache objects
Cache collections of data for SilverStream form, page, or business objects that are organized as rows and columns. SilverStream populates and updates the data in these objects following the principles described in the section SilverStream data cache implementation.
Data cache objects implement the AgiRowCursor and AgiRowSetManager interfaces, which provide a consistent API regardless of the actual object that you are programming. When you program an object at this level, you need no knowledge about the underlying layers.
NOTE Data cache objects are generically referred to as AgxData objects.
This layer is responsible for communications between the data source and the client. It consists of server-side objects that implement the AgiDataSource (and optionally the AgiDataUpdateRequest) interfaces. The data provider layer should be transparent to most of your programming tasks, although you can extend it by using Data Source Objects.
For a description of data providers and Data Source Objects, see Using Data Source Business Objects.
External data sources
Supply the raw data for applications. SilverStream supports access to most relational databases by default.
Most SilverStream clients make use of the SilverStream data access architecture to read and write persistent data. This architecture provides an abstraction of a data model commonly used in relational databases, in which the data is represented as an ordered list of "rows," each row consisting of an ordered list of "columns."
The rows in a given list are usually (though not required to be) "homogeneous"; that is, each row in the list has the same number and types of columns. Each column has an associated name and data type, such as Integer, String, or Date, and any given column may be null. Although the SilverStream Data Access Model abstracts a relational database, it is not restricted to use with relational databases, and may be used to hold data from any kind of data provider.
A SilverStream client does not communicate directly with the ultimate data source. Instead, the client makes use of a data cache, which is a local collection of data rows, optimized for rapid navigation from row to row and for minimizing the use of shared server resources. The data cache will usually hold a local copy of the entire set of data rows the client is currently operating on.
As the client makes changes to the cached data, the changes are only reflected in the local cache. Changes are not returned to the data provider until the client explicitly requests it through an
updateRows() request. At any time, the client can refresh the contents of the data cache through a request to the data provider, possibly specifying query or data-ordering changes at the same time. When this happens, the current contents of the data cache are discarded and the cache is re-filled from the data provider.
Potentially there are different implementations of the data cache in SilverStream. Some implementations are provided by SilverStream or third-party providers, or you can write your own. The typical implementations are SilverStream data cache objects. All data cache implementations must implement a well-known set of interfaces on which the data clients depend. The data cache supports the following:
Row cursor model
Supports forward and backward navigation in the set of rows through a cursor that points to one row in the cache.
Provides methods for querying the number, names, and types of data columns.
Updates apply only to the cache until an explicit commit is performed.
Optimistic concurrency control
Updates are applied only if the original data for the row has not changed in the data provider.
Support for hierarchical data
A row may have "child rows" from the same or a different data provider.
Data cached on the client
Ensures that navigation and other operations are generally inexpensive.
A SilverStream data set is a dynamic data provider that accesses and updates data from a set of related tables in a relational database, and possibly from its associated full-text index. The SilverStream Designer automatically constructs a data set descriptor when you specify the tables and columns used by a form, page or business object. The SilverStream Designer stores a description of the data set in the SilverStream database associated with the form, view, page, or business object to which it is attached.
At runtime, when the form, view, page, or business object is used, a new SilverStream data set is constructed and initialized from the descriptor. The data set is then connected to the data cache in the form, page, view, or business object and acts as its data provider for the duration of its execution.
The SilverStream data set is a full-featured implementation of the SilverStream data provider interfaces, including support for facilities like full-text indexing, versioning, and pre-fetch of rows. At the same time the data set uses the standard data provider interface, which means that its capabilities can be duplicated by a custom-written data provider.
SilverStream provides other implementations of the data provider interfaces, including the following:
For more information about Enterprise Data Connectors, see the online Tools Guide.
SilverStream provides pre-built components that you can use to cache data and to which you can bind forms, form controls, pages, page controls, and business objects. This table describes the classes that provide the default data-caching functionality.
Provides a data set to forms, controls, and JavaBeans. You add AgcDatas at design time and can bind them to a database table or to a data source triggered business object. You can also bind other controls to these objects.
You can bind to these objects at design time and at runtime using the AgoBindingManager. For more information, see Data binding.
For information about binding an AgcData to a SwingJTable, see the online help page in the SilverStream API for AgcJTable.
For non-SilverStream clients you access the data set through a pass-through DSO. For more information see Using Data Source Business Objects.
The following summarizes the principles of the SilverStream data cache implementation:
Once you bind forms, pages, and/or controls to a data cache object, SilverStream populates the cache automatically. The SilverStream Server obtains data for the form or page based on the data set to which the form or page is bound.
The diagram that follows shows the flow of control when a form or a page using a SilverStream data set as a data provider is executed.
If the form or page has an initial data mode that is set to First Record, the server starts the query while downloading the form or page.
If the user of the form or page issues a Go To First record or Go To Next record command, the client issues a request to get 100 rows. Since the server has already started the fetch, it returns the first 100 rows.
This section describes how the SilverStream Server responds when you update the data cache by calling the
updateTransactionally()methods. For more information, see Working with the data cache.
If any part of the transaction fails, the entire transaction must fail, and the SilverStream Server requests that the database server roll back the entire transaction.
The order in which the user performs actions on a form or page determines the order in which SilverStream updates database tables.
For example, if the user inserts a row on the data cache object agcData1, then changes a row on agcData2, then deletes a row on agData, the three changes are submitted to the database in exactly that order (assuming that all three data cache objects are updated together through the containment hierarchy or through a call to
updateTransactionally(). The following is a more detail description of the process:
updateRows()on a subform that has its own child data objects--SilverStream sends only the changes from the subform and its descendants. SilverStream correctly preserves their order. SilverStream retains the order of all other changes on the parent form or sibling subforms when you call
updateRows()somewhere else. Changes that have already been submitted are not re-submitted.
updateRows()), SilverStream creates the row with its final values at the point in the sequence that you created it.
For example, if you insert a row on agcData1, then delete a row on agcData2, then modify the row on agcData1, SilverStream only sends two events to the server: the creation of the new row on agcData1 (with its modified values) then deletion of the row on agcData2.
makePrimaryVersion()on a row and then delete it, SilverStream never sends the
For example, if you modify a row in agcData1, then delete a row in agcData2, then modify the same row in agcData1 again, SilverStream only sends two events to the server: the modification of the row on agcData1 (with its final values) and then the deletion of the row on agcData2.
makePrimaryVersion()on a row happens immediately after whatever else is being done to the row, regardless of where in the sequence you make the
makePrimaryVersion()is the only call you make on the row, it happens at the point in the sequence where you called it.
For example, if you create a new row and then later call
makePrimaryVersion(), SilverStream makes the row the primary version immediately after creating it.
The process is slightly more complicated when you are updating tables in different databases.
updateRows()on the agData, SilverStream creates two separate transactions: a transaction for
db1and a transaction for db2.
Within each transaction, SilverStream puts the database modification in the order in which the user entered them on the form. For example, if the user inserted a row on agcData1, then changed a row on agcData2, then deleted a row on agData, you will get two transactions.
In the transaction on db1, the server performs the insert from agcData1 and then the delete from agData, since that is the order in which you made those changes. In the transaction on db2, SilverStream will do the modify from agcData2. However, SilverStream does not guarantee the order of the two transactions (that is, the update to db1 might occur before the update to db2, or vice versa).
SilverStream manages database concurrency using a strategy called optimistic concurrency control. Optimistic concurrency control means that SilverStream does not lock the records from access by other users while a SilverStream client uses them. When SilverStream attempts to update a database record, it checks to make sure that it has not been changed--that is, it verifies that the SilverStream image of the data matches the current data. Thus, a SilverStream transaction might fail because:
If the transaction fails, SilverStream does not refresh the data on the client. You might want to use the
refreshRows() method to re-fetch data from the server, discarding all changes from the current session. The
refreshRows() method causes a refresh of data for the form or page, all its data-bound controls, and its descendants.
These data cache objects provide a consistent programming interface, because they all implement the AgiRowCursor and AgiRowSetManager interfaces. The AgiRowCursor interface provides methods that let you read values, write values, and navigate rows of a data set. The AgiRowSetManager interface provides methods to populate, update, or refresh the contents of the data set.
You can think of the AgiRowCursor data model as a tree of heterogeneous data rows or "bands." Each row contains a set of data values (called properties or columns) that can be accessed either by name or by index. The top of the data tree is called the root cursor. The root cursor has no properties. Typically, its children are the top-level (and only) set of rows.
Different rows within a set of data can have different sets of values that correspond to different property names. A set of property names is known as a band, and a heterogeneous collection of rows is known as multi-banded. Rows within a set of data can have children, which represent a data hierarchy (such as master/ detail).
The AgiRowCursor interface provides methods that allow you to navigate through the bands of a data set and to obtain or set properties for the columns in the band. You can use its methods to remove rows, add rows, or modify columns in rows. AgiRowCursors broadcast data change and navigation events.
This diagram illustrates an AgiRowCursor object that supplies the Cust-id, Cust-Name and Postal-Code columns of the Customer table.
This table lists the AgiRowCursor methods that are useful for data navigation.
You can use these methods to reposition the AgiRowCursor to a new row. For example, suppose that you want to position the cursor to the next row in the data set. You call the
gotoNext() method, which has this declaration.
boolean gotoNext() throws AgoSecurityException, AgoTransientSystemException,AgoUnrecoverableSystemException
It returns true if the cursor was repositioned successfully, false if the current row is the last row.
NOTE Each of these methods will initiate a query if you select the "no initial query" option for the object's initial upload. For more information, see Controlling how data is loaded to a form or page.
This table lists the AgiRowCursor methods that are useful for data manipulation.
Each of these methods performs the operation on the row at the current cursor location.
This example shows how to get the specific property called customername.
String name = (String) rc.getProperty("customername");
The column name corresponds to the names that you specify when you bind the data cache object (AgcData, AgpData, and AgaData) to a database column, which is described in the section Data binding.
getProperty() method returns an Object, which you must cast to the correct type.
This example illustrates how to change the customer name of the current row to Bob Smith.
rc.setProperty("customername", "Bob Smith");
These names are assigned by default for a form's or page's primary data set. For an AgcData, you specify the column names in the Form Designer. You can optionally obtain the column name by its index, but you should not assume that you know the index of a given column name. You can obtain a column's index from its name by calling
When you call the setProperty() method, SilverStream makes the changes to the data cached by SilverStream, not directly into the database tables. The changes do not take effect until
updateRows() is called.
While AgcData, AgpData, and AgaData can only supply data from a single data source, some implementations of AgiRowCursor also support hierarchical data sets. For example, if you use the View Designer to create a view and specify related columns from multiple tables, the resulting AgiRowCursor object is hierarchical.
NOTE Business object data caches (AgaData) do not exist in a hierarchy.
This diagram illustrates what a hierarchical data set might look like if the data set included rows from the Customer table and the Customer Contact table. These tables are related through the Cust-id column. The related column does not have to be included in the resulting data set.
This table lists the AgiRowCursor methods you can use to navigate and manipulate the child rows.
Using the example diagram above, if the cursor is positioned at customer BERTO and you want to position it at the first child band of BERTO, you call
gotoChild(). If the cursor is positioned at BERTO and you want to position it at LEMON, you call
Remember that the data used by the form or page is a copy of the data in the database at the time the server processed the request for data. The form or page is not directly connected to the database. As the user works with the data--for example, navigating or changing its contents--SilverStream performs all of the actions on the rows in the data cache.
You update the cache using the methods available through the AgiRowSetManager interface. This interface provides methods that let you update a set of data. The following table describes the methods that are available on AgiRowSetManager.
You typically call the
query("employees.empid > 0", "employee.emp_name asc");
The AgcDataPrimary, AgpDataPrimary, and AgoBusinessObjectEvent (for AgaDatas) also have a method called
updateTransactionally() method updates two or more AgxData objects in a single transaction. The updates are processed in the order in which they were made. As with any SilverStream transaction, if any operation contained within the transaction cannot complete, then the entire transaction fails. It has this declaration:
void updateTransactionally(AgxData dataObjects) throws AgoSecurityException, AgoTransientSystemException, AgoUnrecoverableSystemException, AgoInvalidDataException, AgoMissingDataException, AgoDataConcurrencyException
where dataObjects is an array of all the AgaData, AgpData, or AgcData objects to be updated in the single transaction.
One transaction is used per database. If some of the AgxData objects are in different databases, then not all the changes will happen within a single transaction. All the changes within a particular database are submitted in the correct order relative to each other, committed, then a transaction is started on the next database.
You can specify how the data on a form or page is initially displayed at runtime by specifying the Initial Data Mode property at design time.
The following table explains the initial load options.
The form or page does not load any data from the server, and the form or page is initially in find mode. (See AgoBindingManager.beginFind() in the SilverStream API section of online help.)
SilverStream automatically binds controls to AgcDataPrimary or AgpDataPrimary columns when the Wizard creates them or when you drag them from the Relational Data Palette. You can use the Property Inspector to assign or reassign bindings.
SilverStream provides both design-time and runtime data-binding capabilities for all data cache objects. Data binding is the process of associating a control or other object with a column in a data cache object. The AgoBindingManager object provides the data binding services. It manages the relationship between data-aware controls on forms and pages and their data sources (AgcData or AgpData objects). AgoBindingManager provides the following:
The binding manager binds controls to a column of an AgcData or AgpData. The bound control displays new contents whenever the data cache object navigates to a new row, or when the value of the bound column in the current row changes. When the user changes the control's value, the change is reflected in the data source cache.
Controls can be bound to an expression, which can be any combination of functions, control properties, and data source columns. The bound control's value changes when any property in the expression changes. Note that the expression is read-only. It does not cause changes to the data source, so its value does not change when the control's value changes.
Every SilverStream form and page has an AgoBindingManager instance variable, named agDataMgr, which you can use for programmatic binding services. This diagram illustrates how the binding manager relates to controls and JavaBeans, and its related data cache object.
When a control fires a PropertyChange or VetoablePropertyChange event, the binding manager does the following:
If the validation tests succeed, the AgoBindingManager fires the ValueChanged event.
If any of the validation tests fail, the AgoBindingManager fires the ValidationFailed event.
You can programmatically code the ValidationFailed event to succeed by calling
AgoValidationException.success(). If you correct the failure, the AgoBindingManager fires the ValueChanged event.
If the failure is not corrected, the AgoBindingManager fires the GlobalValidationFailed event.
NOTE The GlobalValidationTest event fires when all data on the form changes (for example, when navigating to a new row). This is unrelated to the ValidationFailed or GlobalValidationFailed events discussed in this section.
SilverStream provides multiple ways to bind controls to data sources including Wizards, the Relational Data Palette, and the Property Inspector. SilverStream generates the Java code to bind the controls to the given data source at runtime.
There are two ways to bind forms and pages to a primary database table:
The Wizard prompts you to pick a database table. The first table that you select through the Wizard's interface becomes the form's primary table. If you click the Wizard's Cancel button, the form is not bound to a database table.
You can use the Relational Data Palette to select or change the primary table at any time in the design process.
Once the form or page is bound to a primary database table, the columns of that table are available to any control that you place on it. The primary database table is available through the agData variable, which is an instance of the AgcDataPrimary or AgpDataPrimary class. The code generated by the Wizard updates only the primary table.
where and order by clauses
The default query for agData instance includes all the rows in the table, in no specific order. You can refine the query by specifying a where clause and/or an order by clause. A where clause specifies the selection criteria for the data you want to display. It generally consists of one or more selection conditions connected by the logical operators AND, OR, and NOT. If you omit the where clause, all table rows qualify for the selection.
SilverStream provides two options for entering a where clause and an order by clause:
agData's query()method. For example:
NOTE In where clauses that you set programmatically, you can only use columns referenced by the form definition. Such a where clause is appended to the where clause specified in the form's property sheet (using the AND operator).
Many of the form and page controls are data-aware; that is, you can bind them to a database column directly or to an AgcData control. (You must add the AgcData control to the form or page before you can bind a control to it.) You can tell if a control is data-aware if the control's Property Inspector includes a property named "data column."
The AgcData (or AgpData) control lets you refine the associated data set by specifying where and order by clauses. Additionally, you specify column expressions that define the columns provided by the AgcData by specifying the column names (used by the
setProperty() methods) and expressions that resolve to the column values.
The AgcData control's property sheet looks like this:
To make columns of the AgcData control available for binding to controls, you must specify column expressions.
To bind a control to a column provided by agData or any AgcData controls that might be available, click the ( ... ) button next to the data column. SilverStream lets you choose the column using this dialog box:
Like form and page controls, the Business Object Designer allows you to specify a data set to associate with the business object. Unlike forms and pages, business objects are not provided with an automatic agData instance variable. If you want access to a data set, you must add it at design time. The objects are called AgaData. You have to obtain any AgaData's from the event object using the
To add a data set, choose the Data icon from the Business Object Designer toolbar, then set its properties. You set the properties the same way you do for forms and pages, that is, using the Property Inspector to choose a table and specify column expressions.
The AgoBindingManager provides a
bind() method to bind a property of a control to a property (for example, a column) of an agData, AgcData, AgpData, or any object that implements AgiRowCursor. Once bound, the control displays the value of the data source's property. If the user edits the control's value, the binding manager ensures that the data source property is updated accordingly.
When you use SilverStream's default binding capabilities, SilverStream uses the
bind() method to generate the appropriate Java code to bind form and page controls to a data source at runtime.
bind() method has this declaration:
bind(Object control,String controlPropName,String getMethod,String
setMethod,AgiRowCursor dataSource,Object dsProperty)
This example shows how SilverStream binds the Text property of an AgcTextField control named txtCustomer to the form's agData.
agDataMgr.bind(txtCustomer, "Text", "getText", "setText", agData,
The AgcTextField's default property is "Text". A control's default property is the property that represents the value of the control.
NOTE Any property can be bound programmatically: only the default property can be bound using the Property Inspector.
If you want to bind a control dynamically you can also use the
bind() method. You can bind multiple, non-default properties to any data source that is available. This example illustrates how to bind multiple properties on a calendar control to different properties of an AgcData control.
agDataMgr.bind(ctrlCalendar, "Month", "getMonth", "setMonth",
agDataMgr.bind(ctrlCalendar, "Day", "getDay", "setDay", agcData1, "Event.DAY");
agDataMgr.bind(ctrlCalendar, "Year", "getYear", "setYear",
The bound control can be any object (such as a SilverStream control or a JavaBean) that conforms to the following rules:
You can unbind a control by calling
If you are using Java Swing components, you can bind a JTable to an AgcData, and you can bind to a JTable created outside of SilverStream.
For more information, see the SilverStream API online help for AgcJTable and AgoRowCursorTableModel.