Programmer's Guide



Chapter 6   Data Access Basics

This chapter introduces the SilverStream data access model and its supporting architecture. It has the following topics:

About the data access architecture   Top of page

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:

Data consumers

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.

Data providers

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.

Data abstraction model   Top of page

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.

Data caching   Top of page

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.

Data caching features

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.

Self-describing data

Provides methods for querying the number, names, and types of data columns.

Batched updates

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.

SilverStream data sets   Top of page

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 Enterprise Data Connectors   Top of page

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 data cache implementation   Top of page

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.

Class Name

Description and Notes

AgaData

Provides a data set to a triggered business object or EJB.

AgcDataPrimary

and

AgpDataPrimary

The primary data set for forms and pages. It is implemented on each form or page as an instance variable called agData, which SilverStream creates each time you create a form or page.

It is bound to a data set at design time when you specify a primary table using the Wizard, or the first time you add a bound control to a form or page.

AgpData

Provides a data set to HTML page controls.

AgcData

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.

AgrData

Provides a data set to client-side Java applications developed outside of SilverStream.

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.

Data flow    Top of page

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.

  1. The client (browser, SilverJRunner, or an external Java client) requests a URL from the SilverStream Server.

  2. The server retrieves the form or page from the database and checks if there is a data set descriptor associated with the form or page.

  3. The server returns the form or page to the client. If the returned object includes a data set descriptor, the server initializes the data set by creating and submitting a SQL query against the appropriate database table(s).

    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.

  4. When the form or page that is associated with a data set is downloaded, the form or page initializes its data cache and attaches it to the server.

  5. The form or page in the client reads rows in blocks from the server's data set.

    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.

  6. The client presents those rows to the user and also starts a low-priority background thread to fetch the next 100 rows. This background thread sleeps a short time every time it fetches 100 rows and continues to fetch until there is no more data. If the data is needed faster, the primary thread takes over the fetch.

  7. The fetch request is canceled when the user closes the form or page, or activates a new database request.

Updating data   Top of page

This section describes how the SilverStream Server responds when you update the data cache by calling the updateRows() method.

  1. The user issues a request to update the data (for example, clicking a Save button). The client submits the data in the client's cache object to the data set. The client sends only rows that were changed (added, modified, or deleted).

  2. SilverStream sends the rows back in the order in which changes were made. The data set constructs the appropriate SQL statement based on the user's action on the data. For example:

  3. The data set submits all of the SQL requests in a single transaction. Multiple parts of the transaction are submitted according to how you have programmed them using the updateRows() or 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.

How transactions are ordered

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:

Updating data from different databases   Top of page

The process is slightly more complicated when you are updating tables in different databases.

Ensuring data concurrency   Top of page

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.

About the AgiRowCursor data model   Top of page

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.

Row properties and bands

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.

Data navigation

This table lists the AgiRowCursor methods that are useful for data navigation.

Method

Description

gotoFirst()

Repositions the cursor to the first row in the data set.

gotoLast()

Repositions the cursor to the last row in the data set.

gotoNext()

Repositions the cursor to the next row in the data set.

gotoPrevious()

Repositions the cursor to the previous row in the data set.

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.

Data manipulation

This table lists the AgiRowCursor methods that are useful for data manipulation.

Method

Description

getProperty()

Obtains a data value for a data column, by column name or column index.

setProperty()

Sets the value of a property in the current row.

insertAfter()

Inserts a new row immediately after the current row.

insertBefore()

Inserts a new row immediately before the current row.

delete()

Deletes the current row from the database.

Each of these methods performs the operation on the row at the current cursor location.

getproperty()

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.

The getProperty() method returns an Object, which you must cast to the correct type.

setProperty()

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 getPropertyIndex().

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.

About Hierarchical AgiRowCursors   Top of page

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.

Navigating hierarchal rows

This table lists the AgiRowCursor methods you can use to navigate and manipulate the child rows.

Method

Description

allowsChildren()

Returns a Boolean value that indicates whether the row allows children.

appendChild()

Appends a new row as a child of the current row.

gotoChild()

Positions the cursor on the first child of the current row.

gotoParent()

Positions the cursor on the parent of the current row.

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 gotoNext().

Working with the data cache   Top of page

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.

Method

Description and Notes

updateRows()

Writes changes to the data and the structure of the data back to the data source. It includes changes to all of the rows, not just the current row.

The changes to the data include changes to any of the children in the containment hierarchy as well.

You typically call the updateRows() method on a Save button on a form or page. For information about how SilverStream constructs the SQL statements that update the database, see Updating data.

clearRows()

Discards all cached rows and all changes, freeing all storage.

haveRowsChanged()

Indicates whether modifications have been made to the data in any cache object in the containment hierarchy or to its structure since the last call to the updateRows() method.

query()

Specifies a string that represents a where clause and/or an order by clause. For example,

  query("employees.empid > 0", "employee.emp_name asc"); 

(The SilverStream Server translates these strings to the appropriate SQL syntax.)

refreshRows()

Re-fetches data from the server, discarding all changes from the current session. It discards all of the changes made to any cache object within the containment hierarchy.

Ensuring transaction integrity

The AgcDataPrimary, AgpDataPrimary, and AgoBusinessObjectEvent (for AgaDatas) also have a method called updateTransactionally(). The 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.

multiple databases

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.

Controlling how data is loaded to a form or page   Top of page

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.

Initial data mode value

Description

No Automatic Query

No data is loaded to the form. This is the default value.

You must call the agData.query() method to select the data to display or the agData.gotoFirst() method to begin the download of data from the server.

First Record

The form or page loads data from the server and the first database record is displayed. This value can improve an application's performance by loading the data as it loads the form or page.

NOTE   This is the same as calling the agData.gotoFirst() in the Loaded event, except that it is faster.

Find Mode

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.)

Insert Only

The form or page does not load any data from the server, and users can only add database rows.

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.

Data binding   Top of page

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:

Read/write binding

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.

Expression binding

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.

How data binding works   Top of page

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:

  1. Tests the control's built-in validation (for example, in an AgcTextField Max Value property.)

  2. Tests the control's expression validation, which is specified in the Property Inspector at design time.

  3. Fires the ValidationTest event (if there is code written for this event).

    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.

Design time data binding   Top of page

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.

Binding forms and pages

There are two ways to bind forms and pages to a primary database table:

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:

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).

Binding form and page controls to the primary table

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."

Binding controls to AgcData or AgpData controls

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 getProperty() and 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:

Binding triggered business objects

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 getAgaData() method.

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.

Programmatic data binding   Top of page

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.

The bind() method has this declaration:

  bind(Object control,String controlPropName,String getMethod,String 
setMethod,AgiRowCursor dataSource,Object dsProperty)

where:

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, 
new Integer(0));

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.

Binding SilverStream controls dynamically

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", 
agcData1, "Event.MONTH");
agDataMgr.bind(ctrlCalendar, "Day", "getDay", "setDay", agcData1, "Event.DAY");
agDataMgr.bind(ctrlCalendar, "Year", "getYear", "setYear",
agcData1, "Event.YEAR");

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 unbind().

Binding Swing JTables

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.






Copyright © 2000, SilverStream Software, Inc. All rights reserved.