The Java Tutorials have been written for JDK 8.Java教程是为JDK 8编写的。Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available.本页中描述的示例和实践没有利用后续版本中引入的改进,并且可能使用不再可用的技术。See Java Language Changes for a summary of updated language features in Java SE 9 and subsequent releases.有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言更改。
See JDK Release Notes for information about new features, enhancements, and removed or deprecated options for all JDK releases.有关所有JDK版本的新功能、增强功能以及已删除或不推荐的选项的信息,请参阅JDK发行说明。
A CachedRowSet
object is special in that it can operate without being connected to its data source, that is, it is a disconnected RowSet
object. CachedRowSet
对象的特殊之处在于,它可以在不连接到其数据源的情况下进行操作,也就是说,它是一个断开连接的RowSet
对象。It gets its name from the fact that it stores (caches) its data in memory so that it can operate on its own data rather than on the data stored in a database.它之所以得名,是因为它将数据存储(缓存)在内存中,这样它就可以对自己的数据而不是数据库中存储的数据进行操作。
The CachedRowSet
interface is the superinterface for all disconnected RowSet
objects, so everything demonstrated here also applies to WebRowSet
, JoinRowSet
, and FilteredRowSet
objects.CachedRowSet
接口是所有断开连接的RowSet
对象的超级接口,因此此处演示的所有内容也适用于WebRowSet
、JoinRowSet
和FilteredRowSet
对象。
Note that although the data source for a 请注意,尽管CachedRowSet
object (and the RowSet
objects derived from it) is almost always a relational database, a CachedRowSet
object is capable of getting data from any data source that stores its data in a tabular format. CachedRowSet
对象(及其派生的RowSet
对象)的数据源几乎总是关系数据库,但CachedRowSet
对象能够从以表格格式存储其数据的任何数据源获取数据。For example, a flat file or spreadsheet could be the source of data. 例如,平面文件或电子表格可能是数据源。This is true when the 当实现断开连接的RowSetReader
object for a disconnected RowSet
object is implemented to read data from such a data source. RowSet
对象的RowSetReader
对象以从这样的数据源读取数据时,这是正确的。The CachedRowSet
interface has a RowSetReader
object that reads data from a relational database, so in this tutorial, the data source is always a database.CachedRowSet
接口有一个RowSetReader
对象,它从关系数据库中读取数据,因此在本教程中,数据源始终是一个数据库。
The following topics are covered:涵盖以下主题:
Setting up a 设置CachedRowSet
object involves the following:CachedRowSet
对象涉及以下内容:
Create a new 使用CachedRowSet
object by using an instance of RowSetFactory
, which is created from the class RowSetProvider
.RowSetFactory
的实例创建新的CachedRowSet
对象,该实例是从类RowSetProvider
创建的。
The following example from CCachedRowSetSample
creates a CachedRowSet
object:CachedRowSetSample
中的以下示例创建CachedRowSet
对象:
RowSetFactory factory = RowSetProvider.newFactory(); CachedRowSet crs = factory.createCachedRowSet();
The object crs
has the same default values for its properties that a JdbcRowSet
object has when it is first created. In addition, it has been assigned an instance of the default SyncProvider
implementation, RIOptimisticProvider
.
A SyncProvider
object supplies a RowSetReader
object (a reader) and a RowSetWriter
object (a writer), which a disconnected RowSet
object needs in order to read data from its data source or to write data back to its data source. What a reader and writer do is explained later in the sections What Reader Does and What Writer Does. One thing to keep in mind is that readers and writers work entirely in the background, so the explanation of how they work is for your information only. Having some background on readers and writers should help you understand what some of the methods defined in the CachedRowSet
interface do in the background.
Generally, the default values for properties are fine as they are, but you may change the value of a property by calling the appropriate setter method. 通常情况下,属性的默认值是可以接受的,但是您可以通过调用适当的setter方法来更改属性的值。There are some properties without default values that you must set yourself.有些属性没有默认值,您必须自行设置。
In order to get data, a disconnected 为了获取数据,断开连接的RowSet
object must be able to connect to a data source and have some means of selecting the data it is to hold. RowSet
对象必须能够连接到数据源,并有一些方法来选择要保存的数据。The following properties hold information necessary to obtain a connection to a database.以下属性包含获取数据库连接所需的信息。
username
password
url
datasourceName
Which of these properties you must set depends on how you are going to make a connection. 您必须设置哪些属性取决于您将如何建立连接。The preferred way is to use a DataSource
object, but it may not be practical for you to register a DataSource
object with a JNDI naming service, which is generally done by a system administrator. Therefore, the code examples all use the DriverManager
mechanism to obtain a connection, for which you use the url
property and not the datasourceName
property.
The following lines of code set the username
, password
, and url
properties so that a connection can be obtained using the DriverManager
class. (You will find the JDBC URL to set as the value for the url
property in the documentation for your JDBC driver.)
public void setConnectionProperties( String username, String password) { crs.setUsername(username); crs.setPassword(password); crs.setUrl("jdbc:mySubprotocol:mySubname"); // ...
Another property that you must set is the command
property. Data is read into a RowSet
object from a ResultSet
object. The query that produces that ResultSet
object is the value for the command
property. For example, the following line of code sets the command
property with a query that produces a ResultSet
object containing all the data in the table MERCH_INVENTORY
:
crs.setCommand("select * from MERCH_INVENTORY");
If you are going make any updates to the 如果要对crs对象进行任何更新并希望将这些更新保存在数据库中,则必须再设置一条信息:键列。crs
object and want those updates saved in the database, you must set one more piece of information: the key columns. Key columns are essentially the same as a primary key because they indicate one or more columns that uniquely identify a row. 键列本质上与主键相同,因为它们表示唯一标识行的一个或多个列。The difference is that a primary key is set on a table in the database, whereas key columns are set on a particular 不同之处在于,主键是在数据库中的表上设置的,而键列是在特定的RowSet
object. RowSet
对象上设置的。The following lines of code set the key columns for 以下代码行将crs
to the first column:crs
的键列设置为第一列:
int[] keys = {1}; crs.setKeyColumns(keys);
The first column in the table MERCH_INVENTORY
is ITEM_ID
. It can serve as the key column because every item identifier is different and therefore uniquely identifies one row and only one row in the table MERCH_INVENTORY
. In addition, this column is specified as a primary key in the definition of the MERCH_INVENTORY
table. The method setKeyColumns
takes an array to allow for the fact that it may take two or more columns to identify a row uniquely.
As a point of interest, the method setKeyColumns
does not set a value for a property. In this case, it sets the value for the field keyCols
. Key columns are used internally, so after setting them, you do nothing more with them. You will see how and when key columns are used in the section Using SyncResolver Objects.
Populating a disconnected RowSet
object involves more work than populating a connected RowSet
object. Fortunately, the extra work is done in the background. After you have done the preliminary work to set up the CachedRowSet
object crs
, the following line of code populates crs
:
crs.execute();
The data in crs
is the data in the ResultSet
object produced by executing the query in the command property.
What is different is that the CachedRowSet
implementation for the execute
method does a lot more than the JdbcRowSet
implementation. Or more correctly, the CachedRowSet
object's reader, to which the method execute delegates its tasks, does a lot more.
Every disconnected RowSet
object has a SyncProvider
object assigned to it, and this SyncProvider
object is what provides the RowSet
object's reader (a RowSetReader
object). When the crs
object was created, it was used as the default CachedRowSetImpl
constructor, which, in addition to setting default values for properties, assigns an instance of the RIOptimisticProvider
implementation as the default SyncProvider
object.
When an application calls the method execute
, a disconnected RowSet
object's reader works behind the scenes to populate the RowSet
object with data. A newly created CachedRowSet
object is not connected to a data source and therefore must obtain a connection to that data source in order to get data from it. The default SyncProvider
object (RIOptimisticProvider
) provides a reader that obtains a connection by using the values set for the user name, password, and either the JDBC URL or the data source name, whichever was set more recently. Then the reader executes the query set for the command. It reads the data in the ResultSet
object produced by the query, populating the CachedRowSet
object with that data. Finally, the reader closes the connection.
In the Coffee Break scenario, the owner wants to streamline operations. The owner decides to have employees at the warehouse enter inventory directly into a PDA (personal digital assistant), thereby avoiding the error-prone process of having a second person do the data entry. A CachedRowSet
object is ideal in this situation because it is lightweight, serializable, and can be updated without a connection to the data source.
The owner will have the application development team create a GUI tool for the PDA that warehouse employees will use for entering inventory data. Headquarters will create a CachedRowSet
object populated with the table showing the current inventory and send it using the Internet to the PDAs. When a warehouse employee enters data using the GUI tool, the tool adds each entry to an array, which the CachedRowSet
object will use to perform the updates in the background. Upon completion of the inventory, the PDAs send their new data back to headquarters, where the data is uploaded to the main server.
This section covers the following topics:本节涵盖以下主题:
Updating data in a CachedRowSet
object is just the same as updating data in a JdbcRowSet
object. For example, the following code fragment from CachedRowSetSample.java
increments the value in the column QUAN
by 1 in the row whose ITEM_ID
column has an item identifier of 12345
:
while (crs.next()) { System.out.println("Found item " + crs.getInt("ITEM_ID") + ": " + crs.getString("ITEM_NAME")); if (crs.getInt("ITEM_ID") == 1235) { int currentQuantity = crs.getInt("QUAN") + 1; System.out.println("Updating quantity to " + currentQuantity); crs.updateInt("QUAN", currentQuantity + 1); crs.updateRow(); // Syncing the row back to the DB crs.acceptChanges(con); } } // End of inner while
Just as with updating a column value, the code for inserting and deleting rows in a CachedRowSet
object is the same as for a JdbcRowSet
object.
The following excerpt from CachedRowSetSample.java
inserts a new row into the CachedRowSet
object crs
:
crs.moveToInsertRow(); crs.updateInt("ITEM_ID", newItemId); crs.updateString("ITEM_NAME", "TableCloth"); crs.updateInt("SUP_ID", 927); crs.updateInt("QUAN", 14); Calendar timeStamp; timeStamp = new GregorianCalendar(); timeStamp.set(2006, 4, 1); crs.updateTimestamp( "DATE_VAL", new Timestamp(timeStamp.getTimeInMillis())); crs.insertRow(); crs.moveToCurrentRow();
If headquarters has decided to stop stocking a particular item, it would probably remove the row for that coffee itself. However, in the scenario, a warehouse employee using a PDA also has the capability of removing it. The following code fragment finds the row where the value in the ITEM_ID
column is 12345
and deletes it from the CachedRowSet
crs
:
while (crs.next()) { if (crs.getInt("ITEM_ID") == 12345) { crs.deleteRow(); break; } }
There is a major difference between making changes to a JdbcRowSet
object and making changes to a CachedRowSet
object. Because a JdbcRowSet
object is connected to its data source, the methods updateRow
, insertRow
, and deleteRow
can update both the JdbcRowSet
object and the data source. In the case of a disconnected RowSet
object, however, these methods update the data stored in the CachedRowSet
object's memory but cannot affect the data source. A disconnected RowSet
object must call the method acceptChanges
in order to save its changes to the data source. In the inventory scenario, back at headquarters, an application will call the method acceptChanges
to update the database with the new values for the column QUAN
.
crs.acceptChanges();
Like the method execute
, the method acceptChanges
does its work invisibly. Whereas the method execute
delegates its work to the RowSet
object's reader, the method acceptChanges
delegates its tasks to the RowSet
object's writer. In the background, the writer opens a connection to the database, updates the database with the changes made to the RowSet
object, and then closes the connection.
The difficulty is that a conflict can arise. 困难在于可能发生冲突。A conflict is a situation in which another party has updated a value in the database that corresponds to a value that was updated in a 冲突是另一方更新了数据库中与RowSet
object. RowSet
对象中更新的值相对应的值的情况。Which value should persist in the database? What the writer does when there is a conflict depends on how it is implemented, and there are many possibilities. 数据库中应该保留哪个值?当发生冲突时,作者会做什么取决于它是如何实现的,并且有很多可能性。At one end of the spectrum, the writer does not even check for conflicts and just writes all changes to the database. 在范围的一端,编写器甚至不检查冲突,只将所有更改写入数据库。This is the case with the RIXMLProvider
implementation, which is used by a WebRowSet
object. At the other end, the writer ensures that there are no conflicts by setting database locks that prevent others from making changes.WebRowSet
对象使用的RIXMLProvider
实现就是这种情况。另一方面,编写器通过设置阻止其他人进行更改的数据库锁来确保没有冲突。
The writer for the crs
object is the one provided by the default SyncProvider
implementation, RIOptimisticProvider
. The RIOPtimisticProvider
implementation gets its name from the fact that it uses an optimistic concurrency model. This model assumes that there will be few, if any, conflicts and therefore sets no database locks. The writer checks to see if there are any conflicts, and if there is none, it writes the changes made to the crs
object to the database, and those changes become persistent. If there are any conflicts, the default is not to write the new RowSet
values to the database.
In the scenario, the default behavior works very well. 在该场景中,默认行为非常有效。Because no one at headquarters is likely to change the value in the 因为总部没有人可能会改变QUAN
column of COF_INVENTORY
, there will be no conflicts. COF_INVENTORY
的QUAN
列中的值,所以不会有冲突。As a result, the values entered into the 因此,在仓库中输入crs对象的值将被写入数据库,因此将是持久的,这是期望的结果。crs
object at the warehouse will be written to the database and thus will be persistent, which is the desired outcome.
In other situations, however, it is possible for conflicts to exist. 然而,在其他情况下,可能存在冲突。To accommodate these situations, the 为了适应这些情况,RIOPtimisticProvider
implementation provides an option that lets you look at the values in conflict and decide which ones should be persistent. This option is the use of a SyncResolver
object.RIOPtimisticProvider
实现提供了一个选项,允许您查看冲突中的值,并决定哪些值应该是持久的。此选项用于使用SyncResolver
对象。
When the writer has finished looking for conflicts and has found one or more, it creates a 编写器完成冲突查找并找到一个或多个冲突后,将创建一个SyncResolver
object containing the database values that caused the conflicts. SyncResolver
对象,其中包含导致冲突的数据库值。Next, the method acceptChanges
throws a SyncProviderException
object, which an application may catch and use to retrieve the SyncResolver
object. The following lines of code retrieve the SyncResolver
object resolver
:
try { crs.acceptChanges(); } catch (SyncProviderException spe) { SyncResolver resolver = spe.getSyncResolver(); }
The object resolver
is a RowSet
object that replicates the crs
object except that it contains only the values in the database that caused a conflict. All other column values are null.
With the resolver
object, you can iterate through its rows to locate the values that are not null and are therefore values that caused a conflict. Then you can locate the value at the same position in the crs
object and compare them. The following code fragment retrieves resolver
and uses the SyncResolver
method nextConflict
to iterate through the rows that have conflicting values. The object resolver
gets the status of each conflicting value, and if it is UPDATE_ROW_CONFLICT
, meaning that the crs
was attempting an update when the conflict occurred, the resolver
object gets the row number of that value. Then the code moves the cursor for the crs
object to the same row. Next, the code finds the column in that row of the resolver
object that contains a conflicting value, which will be a value that is not null. After retrieving the value in that column from both the resolver
and crs
objects, you can compare the two and decide which one you want to become persistent. Finally, the code sets that value in both the crs
object and the database using the method setResolvedValue
, as shown in the following code from CachedRowSetSample
:
try { // ... // Syncing the new row back to the database. System.out.println("About to add a new row..."); crs.acceptChanges(con); System.out.println("Added a row..."); this.viewTable(con); // ... } catch (SyncProviderException spe) { SyncResolver resolver = spe.getSyncResolver(); Object crsValue; // value in the RowSet object Object resolverValue; // value in the SyncResolver object Object resolvedValue; // value to be persisted while (resolver.nextConflict()) { if (resolver.getStatus() == SyncResolver.INSERT_ROW_CONFLICT) { int row = resolver.getRow(); crs.absolute(row); int colCount = crs.getMetaData().getColumnCount(); for (int j = 1; j <= colCount; j++) { if (resolver.getConflictValue(j) != null) { crsValue = crs.getObject(j); resolverValue = resolver.getConflictValue(j); // Compare crsValue and resolverValue to determine // which should be the resolved value (the value to persist) // // This example chooses the value in the RowSet object, // crsValue, to persist., resolvedValue = crsValue; resolver.setResolvedValue(j, resolvedValue); } } } } }
Being a JavaBeans component means that a 作为JavaBeans组件意味着RowSet
object can notify other components when certain things happen to it. RowSet
对象可以在发生某些事情时通知其他组件。For example, if data in a 例如,如果RowSet
object changes, the RowSet
object can notify interested parties of that change. RowSet
对象中的数据发生更改,该RowSet
对象可以将该更改通知相关方。The nice thing about this notification mechanism is that, as an application programmer, all you have to do is add or remove the components that will be notified.这种通知机制的好处在于,作为应用程序程序员,您所要做的就是添加或删除将被通知的组件。
This section covers the following topics:本节涵盖以下主题:
A listener for a RowSet
object is a component that implements the following methods from the RowSetListener
interface:
cursorMoved
: Defines what the listener will do, if anything, when the cursor in the RowSet
object moves.rowChanged
: Defines what the listener will do, if anything, when one or more column values in a row have changed, a row has been inserted, or a row has been deleted.rowSetChanged
: Defines what the listener will do, if anything, when the RowSet
object has been populated with new data.An example of a component that might want to be a listener is a BarGraph
object that graphs the data in a RowSet
object. As the data changes, the BarGraph
object can update itself to reflect the new data.
As an application programmer, the only thing you must do to take advantage of the notification mechanism is to add or remove listeners. 作为应用程序程序员,要利用通知机制,您必须做的唯一一件事就是添加或删除侦听器。The following line of code means that every time the cursor for the crs
objects moves, values in crs
are changed, or crs
as a whole gets new data, the BarGraph
object bar
will be notified:
crs.addRowSetListener(bar);
You can also stop notifications by removing a listener, as is done in the following line of code:您还可以通过删除侦听器来停止通知,如下行代码所示:
crs.removeRowSetListener(bar);
Using the Coffee Break scenario, assume that headquarters checks with the database periodically to get the latest price list for the coffees it sells online. 使用咖啡休息场景,假设总部定期检查数据库,以获取其在线销售的咖啡的最新价目表。In this case, the listener is the PriceList
object priceList
at the Coffee Break web site, which must implement the RowSetListener
methods cursorMoved
, rowChanged
, and rowSetChanged
. The implementation of the cursorMoved
method could be to do nothing because the position of the cursor does not affect the priceList
object. The implementations for the rowChanged
and rowSetChanged
methods, on the other hand, must ascertain what changes have been made and update priceList
accordingly.
Methods that cause any of the RowSet
events automatically notify all registered listeners. For example, any method that moves the cursor also calls the method cursorMoved
on each of the listeners. Similarly, the method execute
calls the method rowSetChanged
on all listeners, and acceptChanges
calls rowChanged
on all listeners.
The sample code 示例代码CachedRowSetSample.testCachedRowSet
demonstrates how data can be sent in smaller pieces.CachedRowSetSample.testCachedRowSet
演示了如何以较小的片段发送数据。