The DataStore component provides the main access point to high performance data caching and compact persistence. It uses a simple hierarchical file system which allows it to contain DataExpress DataSets, arbitrary files, and Java Objects. The DataStore component uses a single file to store one or more data streams. A DataStore file has a hierarchical directory structure that associates a name and various directory status with a particular data stream. Classes in the DataStore package will remember the rows fetched in a table, even after the application has been terminated and restarted. For an example of using a DataStore, see the tutorial.
The public data stream categories in a DataStore are:
This stream provides data caching and persistence for any DataExpress DataSet that extends from StorageDataSet (i.e. QueryDataSet, ProcedureDataSet, and TableDataSet). All semantics of StorageDataSet are supported including master-detail, picklists, constraints, calculated fields, sorting, filtering, and aggregation.
There are createFile and openFile DataStore methods for creating and opening arbitrary file streams. These methods return a FileStream that extends the Java InputStream class with additional seek() and write() methods.
Once data of type Object is in the DataStore, it is treated as a file stream. There are readObject and writeObject methods for reading and writing Java objects to a DataStore. These Objects are serialized into the DataStore using Java serialization. Java Objects may also be persisted in a DataStore by using a DataSet with a Column that has an Object data type.
The DataStore Directory can be accessed by calling the DataStore.getDirectoryDataSet method. This returns a DataSet with several columns for the name, data stream category, modification time, size, delete status, etc. The directory is maintained as a sorted list of file names.
Caching and persisting StorageDataSets in a DataStore is accomplished through two required property settings on a StorageDataSet called StorageDataSet.store and StorageDataSet.storeName. By default, all StorageDataSets use a MemoryStore if the StorageDataSet.store property is not set. Currently MemoryStore and DataStore are the only implementations for the store property. The StorageDataSet.storeName property is the unique name associated with this StorageDataSet in the DataStore directory.
To store and persist a DataSet in a DataStore,
Property name | Value |
Connection URL | jdbc:odbc:DataSet Tutorial |
Username | SYSDBA |
Password | masterkey |
Click the Test Connection button to check that the connection properties have been correctly set. Results of the connection attempt are displayed in the gray area below the button. When the connection is successful, click OK.
Adding a DataStore component writes an import statement for it to your code, and adds the "Datastore 2.0" library to your project properties.
If you write code that instantiates a DataStore, instead of letting the Designer do it, you must add the import statement and the library. To add the library, select File|Project Properties, click the Add button, and choose the "Datastore 2.0" library.
Double-click the query property of the QueryDataSet component in the Inspector and set the following properties:
Property name | Value |
Database | database1 |
SQL Statement | select * from employee |
Place SQL text in resource bundle | unchecked |
Click Test query to ensure that the query is runnable. When the gray area beneath the button indicates Success, click OK to close the dialog.
try { if (dataStore1.isOpen()) { dataStore1.close();} } catch (Exception ex) { ex.printStackTrace(); }
if ( ! (new File("C:\\mypath\\myDataStore.jds").exists()))
dataStore1.create();
In the running application, make some changes to the data and click the Post button on the navigator to save the changes to the DataStore file. Changes are also saved to the file when you move off of a row, just as they are with an in-memory data set (MemoryStore). To save changes back to the data source, see Saving changes to the SQL database on the server.
Close the application and run it again. You see the data as you edited it in the previous run. This, of course, is very different from the behavior of an in-memory data set. If you want, you can exit the application, shut down Local InterBase Server (by right-clicking on its icon in the task bar and choosing Shutdown), and run the application again. Without any connection to the SQL database, you can continue to view and edit data in the DataStore. This would be especially useful if you want to work with data off-line, such as at home or on an airplane.
So far in the tutorial, nothing has been saved back to the SQL database on the server. On the NavigatorControl,
Options set in the queryDescriptor also have an effect on how data is stored, saved, and refreshed. In the queryDescriptor in this example, the Execute Query Immediately When Opened option is selected. This option indicates how data is loaded into the DataStore file when the application was first run. On subsequent runs, the execution of the query is suppressed, because the data set is found in the DataStore file instead of on the server. As a result,
The Execute Query Immediately When Opened option is not allowed to overwrite existing data. This means that you can safely close and reopen a data set to change property settings in either a MemoryStore or in a DataStore without losing editing changes.
Some other information related to working with the DataStore:
try { if (!connected) { // Connect to SQL database database1.openConnection(); navigatorControl1.setButtonEnabled(NavigatorControl.SAVE, true); navigatorControl1.setButtonEnabled(NavigatorControl.REFRESH, true); buttonControl1.setLabel("Disconnect"); connected = true; } else { // Disconnect from SQL database database1.closeConnection(); navigatorControl1.setButtonEnabled(NavigatorControl.SAVE, false); navigatorControl1.setButtonEnabled(NavigatorControl.REFRESH, false); buttonControl1.setLabel(" Connect "); connected = false; } } catch (Exception ex) { ex.printStackTrace(); }
boolean connected; // connection made to SQL database?
// Disable Navigator buttons that require SQL database until connection is made navigatorControl1.setButtonEnabled(NavigatorControl.SAVE, false); navigatorControl1.setButtonEnabled(NavigatorControl.REFRESH, false);
Now, the navigator's Save and Refresh buttons are dimmed, clicking them has no effect. When a connection is successfully established by clicking the Connect button, the button's label will change to "Disconnect" and the navigator's Save and Refresh buttons will be enabled.
It is strongly recommended that DataStore components be placed inside data modules and that all StorageDataSets that use a particular DataStore be placed in the same DataModule that the DataStore is placed in. Any references to these StorageDataSets should be made through DataModule accesser methods such as businessModule.getCustomer(). The reason for this is that much of the functionality surfaced through StorageDataSets is driven by property and event settings. Although most of the important "structural" StorageDataSet properties are persisted in the DataStore directory, the classes that implement the event listener interfaces are not. By instantiating the StorageDataSet with all event listener settings, constraints, calc fields, and filters implemented with events, they will be properly maintained at run time and design time.
Be sure to call DataStore.close when the DataStore is no longer needed. Closing a DataStore ensures that all modifications are saved to disk. There is a daemon thread for all open DataStore instances that is constantly saving modified cache data (by default modified data is saved every 100 milliseconds). Although the DataStore daemon is very good at saving off DataStore modifications, there are some precautions that should be taken:
If you suspect that cache contents were not properly saved on a DataStore, there is a borland.datastore.StreamVerifier class with public static verify() methods that can verify a single stream or all streams in a DataStore.
Another benefit to closing DataStore components is that when all DataStore components are closed, the memory allocated to the DataStore cache is released.
Close all DataSets that have their store property set to a DataStore when you are done with them. This frees up DataStore resources associated with the DataSet and allows the DataSet to be garbage collected.
The persistent column editor in the JBuilder UI Designer provides support for moving, deleting, and inserting columns. Column data types can also be changed. To activate the persistent column editor,
This designer works for StorageDataSets that are using a MemoryStore or a DataStore. MemoryStore performs all operations instantly. When a Column data type is changed, MemoryStore does not convert data values to the new data type. The old values are lost.
DataStore does not perform the move/insert/delete/change type operations on StorageDataSets immediately. The structural change is noted inside the DataStore directory as a "pending" operation. The StorageDataSet.getNeedsRestructure method returns true when there is a "pending" restructure operation. A StorageDataSet with pending structural changes can still be used.
To make a pending restructure operation take effect, press the restructure speed bar button in the persistent column editor You can also cause a restructure operation to happen programmatically by calling the StorageDataSet.restructure() method.
The restructure method can be used even when there are no pending structural changes to repair or compact a StorageDataSet and its associated indexes.
Many DataSets, like QueryDataSet and ProcedureDataSet, obtain their structure and row data from a SQL server. But there is also the need to add columns to these DataSets or to create stand-alone tables. This can be accomplished with the JBuilder persistent column editor. DataSet columns can be changed, deleted, moved, and added instantly at design time. The DataStore uses a mapping table to provide an illusion that each structural operation has occurred. When all structural changes are complete, the Restructure button can be pressed
When the UI Designer is activated on while a StorageDataSet node is selected, the columns for that node are displayed in a grid on the design surface. A toolbar for adding, deleting, moving up, moving down, and restructuring the data set is displayed above the grid.
Streams are deleted in a DataStore by marking their directory entry as deleted. The contents of these streams will be reallocated as new storage is needed for existing streams. Although streams can be undeleted, the entire contents may not be present if space was needed for new allocations.
The maximum DataStore file size: Blocksize * 2 gigabytes. The default block size is 2k, so that is about 4,400 gigabytes. The maximum rows per DataSet is 4 gig. The maximum row length is 1/3 block size, but long values are stored as blobs (blobs have a 2 gigabyte limit).