You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TableQuery.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.data.util.sqlcontainer.query;
  5. import java.io.IOException;
  6. import java.sql.Connection;
  7. import java.sql.DatabaseMetaData;
  8. import java.sql.PreparedStatement;
  9. import java.sql.ResultSet;
  10. import java.sql.ResultSetMetaData;
  11. import java.sql.SQLException;
  12. import java.util.ArrayList;
  13. import java.util.Collections;
  14. import java.util.EventObject;
  15. import java.util.HashMap;
  16. import java.util.LinkedList;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.logging.Level;
  20. import java.util.logging.Logger;
  21. import com.vaadin.data.Container.Filter;
  22. import com.vaadin.data.util.filter.Compare.Equal;
  23. import com.vaadin.data.util.sqlcontainer.ColumnProperty;
  24. import com.vaadin.data.util.sqlcontainer.OptimisticLockException;
  25. import com.vaadin.data.util.sqlcontainer.RowId;
  26. import com.vaadin.data.util.sqlcontainer.RowItem;
  27. import com.vaadin.data.util.sqlcontainer.SQLUtil;
  28. import com.vaadin.data.util.sqlcontainer.TemporaryRowId;
  29. import com.vaadin.data.util.sqlcontainer.connection.JDBCConnectionPool;
  30. import com.vaadin.data.util.sqlcontainer.query.generator.DefaultSQLGenerator;
  31. import com.vaadin.data.util.sqlcontainer.query.generator.MSSQLGenerator;
  32. import com.vaadin.data.util.sqlcontainer.query.generator.SQLGenerator;
  33. import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
  34. @SuppressWarnings("serial")
  35. public class TableQuery implements QueryDelegate,
  36. QueryDelegate.RowIdChangeNotifier {
  37. private static final Logger logger = Logger.getLogger(TableQuery.class
  38. .getName());
  39. /** Table name, primary key column name(s) and version column name */
  40. private String tableName;
  41. private List<String> primaryKeyColumns;
  42. private String versionColumn;
  43. /** Currently set Filters and OrderBys */
  44. private List<Filter> filters;
  45. private List<OrderBy> orderBys;
  46. /** SQLGenerator instance to use for generating queries */
  47. private SQLGenerator sqlGenerator;
  48. /** Fields related to Connection and Transaction handling */
  49. private JDBCConnectionPool connectionPool;
  50. private transient Connection activeConnection;
  51. private boolean transactionOpen;
  52. /** Row ID change listeners */
  53. private LinkedList<RowIdChangeListener> rowIdChangeListeners;
  54. /** Row ID change events, stored until commit() is called */
  55. private final List<RowIdChangeEvent> bufferedEvents = new ArrayList<RowIdChangeEvent>();
  56. /** Set to true to output generated SQL Queries to System.out */
  57. private boolean debug = false;
  58. /** Prevent no-parameters instantiation of TableQuery */
  59. @SuppressWarnings("unused")
  60. private TableQuery() {
  61. }
  62. /**
  63. * Creates a new TableQuery using the given connection pool, SQL generator
  64. * and table name to fetch the data from. All parameters must be non-null.
  65. *
  66. * @param tableName
  67. * Name of the database table to connect to
  68. * @param connectionPool
  69. * Connection pool for accessing the database
  70. * @param sqlGenerator
  71. * SQL query generator implementation
  72. */
  73. public TableQuery(String tableName, JDBCConnectionPool connectionPool,
  74. SQLGenerator sqlGenerator) {
  75. if (tableName == null || tableName.trim().length() < 1
  76. || connectionPool == null || sqlGenerator == null) {
  77. throw new IllegalArgumentException(
  78. "All parameters must be non-null and a table name must be given.");
  79. }
  80. this.tableName = tableName;
  81. this.sqlGenerator = sqlGenerator;
  82. this.connectionPool = connectionPool;
  83. fetchMetaData();
  84. }
  85. /**
  86. * Creates a new TableQuery using the given connection pool and table name
  87. * to fetch the data from. All parameters must be non-null. The default SQL
  88. * generator will be used for queries.
  89. *
  90. * @param tableName
  91. * Name of the database table to connect to
  92. * @param connectionPool
  93. * Connection pool for accessing the database
  94. */
  95. public TableQuery(String tableName, JDBCConnectionPool connectionPool) {
  96. this(tableName, connectionPool, new DefaultSQLGenerator());
  97. }
  98. /*
  99. * (non-Javadoc)
  100. *
  101. * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getCount()
  102. */
  103. public int getCount() throws SQLException {
  104. logger.log(Level.FINE, "Fetching count...");
  105. StatementHelper sh = sqlGenerator.generateSelectQuery(tableName,
  106. filters, null, 0, 0, "COUNT(*)");
  107. boolean shouldCloseTransaction = false;
  108. if (!transactionOpen) {
  109. shouldCloseTransaction = true;
  110. beginTransaction();
  111. }
  112. ResultSet r = executeQuery(sh);
  113. r.next();
  114. int count = r.getInt(1);
  115. r.getStatement().close();
  116. r.close();
  117. if (shouldCloseTransaction) {
  118. commit();
  119. }
  120. return count;
  121. }
  122. /*
  123. * (non-Javadoc)
  124. *
  125. * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getResults(int,
  126. * int)
  127. */
  128. public ResultSet getResults(int offset, int pagelength) throws SQLException {
  129. StatementHelper sh;
  130. /*
  131. * If no ordering is explicitly set, results will be ordered by the
  132. * first primary key column.
  133. */
  134. if (orderBys == null || orderBys.isEmpty()) {
  135. List<OrderBy> ob = new ArrayList<OrderBy>();
  136. ob.add(new OrderBy(primaryKeyColumns.get(0), true));
  137. sh = sqlGenerator.generateSelectQuery(tableName, filters, ob,
  138. offset, pagelength, null);
  139. } else {
  140. sh = sqlGenerator.generateSelectQuery(tableName, filters, orderBys,
  141. offset, pagelength, null);
  142. }
  143. return executeQuery(sh);
  144. }
  145. /*
  146. * (non-Javadoc)
  147. *
  148. * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#
  149. * implementationRespectsPagingLimits()
  150. */
  151. public boolean implementationRespectsPagingLimits() {
  152. return true;
  153. }
  154. /*
  155. * (non-Javadoc)
  156. *
  157. * @see
  158. * com.vaadin.addon.sqlcontainer.query.QueryDelegate#storeRow(com.vaadin
  159. * .addon.sqlcontainer.RowItem)
  160. */
  161. public int storeRow(RowItem row) throws UnsupportedOperationException,
  162. SQLException {
  163. if (row == null) {
  164. throw new IllegalArgumentException("Row argument must be non-null.");
  165. }
  166. StatementHelper sh;
  167. int result = 0;
  168. if (row.getId() instanceof TemporaryRowId) {
  169. setVersionColumnFlagInProperty(row);
  170. sh = sqlGenerator.generateInsertQuery(tableName, row);
  171. result = executeUpdateReturnKeys(sh, row);
  172. } else {
  173. setVersionColumnFlagInProperty(row);
  174. sh = sqlGenerator.generateUpdateQuery(tableName, row);
  175. result = executeUpdate(sh);
  176. }
  177. if (versionColumn != null && result == 0) {
  178. throw new OptimisticLockException(
  179. "Someone else changed the row that was being updated.",
  180. row.getId());
  181. }
  182. return result;
  183. }
  184. private void setVersionColumnFlagInProperty(RowItem row) {
  185. ColumnProperty versionProperty = (ColumnProperty) row
  186. .getItemProperty(versionColumn);
  187. if (versionProperty != null) {
  188. versionProperty.setVersionColumn(true);
  189. }
  190. }
  191. /**
  192. * Inserts the given row in the database table immediately. Begins and
  193. * commits the transaction needed. This method was added specifically to
  194. * solve the problem of returning the final RowId immediately on the
  195. * SQLContainer.addItem() call when auto commit mode is enabled in the
  196. * SQLContainer.
  197. *
  198. * @param row
  199. * RowItem to add to the database
  200. * @return Final RowId of the added row
  201. * @throws SQLException
  202. */
  203. public RowId storeRowImmediately(RowItem row) throws SQLException {
  204. beginTransaction();
  205. /* Set version column, if one is provided */
  206. setVersionColumnFlagInProperty(row);
  207. /* Generate query */
  208. StatementHelper sh = sqlGenerator.generateInsertQuery(tableName, row);
  209. PreparedStatement pstmt = activeConnection.prepareStatement(
  210. sh.getQueryString(), primaryKeyColumns.toArray(new String[0]));
  211. sh.setParameterValuesToStatement(pstmt);
  212. logger.log(Level.FINE, "DB -> " + sh.getQueryString());
  213. int result = pstmt.executeUpdate();
  214. if (result > 0) {
  215. /*
  216. * If affected rows exist, we'll get the new RowId, commit the
  217. * transaction and return the new RowId.
  218. */
  219. ResultSet generatedKeys = pstmt.getGeneratedKeys();
  220. RowId newId = getNewRowId(row, generatedKeys);
  221. generatedKeys.close();
  222. pstmt.clearParameters();
  223. pstmt.close();
  224. commit();
  225. return newId;
  226. } else {
  227. pstmt.clearParameters();
  228. pstmt.close();
  229. /* On failure return null */
  230. return null;
  231. }
  232. }
  233. /*
  234. * (non-Javadoc)
  235. *
  236. * @see
  237. * com.vaadin.addon.sqlcontainer.query.QueryDelegate#setFilters(java.util
  238. * .List)
  239. */
  240. public void setFilters(List<Filter> filters)
  241. throws UnsupportedOperationException {
  242. if (filters == null) {
  243. this.filters = null;
  244. return;
  245. }
  246. this.filters = Collections.unmodifiableList(filters);
  247. }
  248. /*
  249. * (non-Javadoc)
  250. *
  251. * @see
  252. * com.vaadin.addon.sqlcontainer.query.QueryDelegate#setOrderBy(java.util
  253. * .List)
  254. */
  255. public void setOrderBy(List<OrderBy> orderBys)
  256. throws UnsupportedOperationException {
  257. if (orderBys == null) {
  258. this.orderBys = null;
  259. return;
  260. }
  261. this.orderBys = Collections.unmodifiableList(orderBys);
  262. }
  263. /*
  264. * (non-Javadoc)
  265. *
  266. * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#beginTransaction()
  267. */
  268. public void beginTransaction() throws UnsupportedOperationException,
  269. SQLException {
  270. if (transactionOpen && activeConnection != null) {
  271. throw new IllegalStateException();
  272. }
  273. logger.log(Level.FINE, "DB -> begin transaction");
  274. activeConnection = connectionPool.reserveConnection();
  275. activeConnection.setAutoCommit(false);
  276. transactionOpen = true;
  277. }
  278. /*
  279. * (non-Javadoc)
  280. *
  281. * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#commit()
  282. */
  283. public void commit() throws UnsupportedOperationException, SQLException {
  284. if (transactionOpen && activeConnection != null) {
  285. logger.log(Level.FINE, "DB -> commit");
  286. activeConnection.commit();
  287. connectionPool.releaseConnection(activeConnection);
  288. } else {
  289. throw new SQLException("No active transaction");
  290. }
  291. transactionOpen = false;
  292. /* Handle firing row ID change events */
  293. RowIdChangeEvent[] unFiredEvents = bufferedEvents
  294. .toArray(new RowIdChangeEvent[] {});
  295. bufferedEvents.clear();
  296. if (rowIdChangeListeners != null && !rowIdChangeListeners.isEmpty()) {
  297. for (RowIdChangeListener r : rowIdChangeListeners) {
  298. for (RowIdChangeEvent e : unFiredEvents) {
  299. r.rowIdChange(e);
  300. }
  301. }
  302. }
  303. }
  304. /*
  305. * (non-Javadoc)
  306. *
  307. * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#rollback()
  308. */
  309. public void rollback() throws UnsupportedOperationException, SQLException {
  310. if (transactionOpen && activeConnection != null) {
  311. logger.log(Level.FINE, "DB -> rollback");
  312. activeConnection.rollback();
  313. connectionPool.releaseConnection(activeConnection);
  314. } else {
  315. throw new SQLException("No active transaction");
  316. }
  317. transactionOpen = false;
  318. }
  319. /*
  320. * (non-Javadoc)
  321. *
  322. * @see
  323. * com.vaadin.addon.sqlcontainer.query.QueryDelegate#getPrimaryKeyColumns()
  324. */
  325. public List<String> getPrimaryKeyColumns() {
  326. return Collections.unmodifiableList(primaryKeyColumns);
  327. }
  328. public String getVersionColumn() {
  329. return versionColumn;
  330. }
  331. public void setVersionColumn(String column) {
  332. versionColumn = column;
  333. }
  334. public String getTableName() {
  335. return tableName;
  336. }
  337. public SQLGenerator getSqlGenerator() {
  338. return sqlGenerator;
  339. }
  340. /**
  341. * Executes the given query string using either the active connection if a
  342. * transaction is already open, or a new connection from this query's
  343. * connection pool.
  344. *
  345. * @param sh
  346. * an instance of StatementHelper, containing the query string
  347. * and parameter values.
  348. * @return ResultSet of the query
  349. * @throws SQLException
  350. */
  351. private ResultSet executeQuery(StatementHelper sh) throws SQLException {
  352. Connection c = null;
  353. if (transactionOpen && activeConnection != null) {
  354. c = activeConnection;
  355. } else {
  356. throw new SQLException("No active transaction!");
  357. }
  358. PreparedStatement pstmt = c.prepareStatement(sh.getQueryString());
  359. sh.setParameterValuesToStatement(pstmt);
  360. logger.log(Level.FINE, "DB -> " + sh.getQueryString());
  361. return pstmt.executeQuery();
  362. }
  363. /**
  364. * Executes the given update query string using either the active connection
  365. * if a transaction is already open, or a new connection from this query's
  366. * connection pool.
  367. *
  368. * @param sh
  369. * an instance of StatementHelper, containing the query string
  370. * and parameter values.
  371. * @return Number of affected rows
  372. * @throws SQLException
  373. */
  374. private int executeUpdate(StatementHelper sh) throws SQLException {
  375. Connection c = null;
  376. PreparedStatement pstmt = null;
  377. try {
  378. if (transactionOpen && activeConnection != null) {
  379. c = activeConnection;
  380. } else {
  381. c = connectionPool.reserveConnection();
  382. }
  383. pstmt = c.prepareStatement(sh.getQueryString());
  384. sh.setParameterValuesToStatement(pstmt);
  385. logger.log(Level.FINE, "DB -> " + sh.getQueryString());
  386. int retval = pstmt.executeUpdate();
  387. return retval;
  388. } finally {
  389. if (pstmt != null) {
  390. pstmt.clearParameters();
  391. pstmt.close();
  392. }
  393. if (!transactionOpen) {
  394. connectionPool.releaseConnection(c);
  395. }
  396. }
  397. }
  398. /**
  399. * Executes the given update query string using either the active connection
  400. * if a transaction is already open, or a new connection from this query's
  401. * connection pool.
  402. *
  403. * Additionally adds a new RowIdChangeEvent to the event buffer.
  404. *
  405. * @param sh
  406. * an instance of StatementHelper, containing the query string
  407. * and parameter values.
  408. * @param row
  409. * the row item to update
  410. * @return Number of affected rows
  411. * @throws SQLException
  412. */
  413. private int executeUpdateReturnKeys(StatementHelper sh, RowItem row)
  414. throws SQLException {
  415. Connection c = null;
  416. PreparedStatement pstmt = null;
  417. ResultSet genKeys = null;
  418. try {
  419. if (transactionOpen && activeConnection != null) {
  420. c = activeConnection;
  421. } else {
  422. c = connectionPool.reserveConnection();
  423. }
  424. pstmt = c.prepareStatement(sh.getQueryString(),
  425. primaryKeyColumns.toArray(new String[0]));
  426. sh.setParameterValuesToStatement(pstmt);
  427. logger.log(Level.FINE, "DB -> " + sh.getQueryString());
  428. int result = pstmt.executeUpdate();
  429. genKeys = pstmt.getGeneratedKeys();
  430. RowId newId = getNewRowId(row, genKeys);
  431. bufferedEvents.add(new RowIdChangeEvent(row.getId(), newId));
  432. return result;
  433. } finally {
  434. if (genKeys != null) {
  435. genKeys.close();
  436. }
  437. if (pstmt != null) {
  438. pstmt.clearParameters();
  439. pstmt.close();
  440. }
  441. if (!transactionOpen) {
  442. connectionPool.releaseConnection(c);
  443. }
  444. }
  445. }
  446. /**
  447. * Fetches name(s) of primary key column(s) from DB metadata.
  448. *
  449. * Also tries to get the escape string to be used in search strings.
  450. */
  451. private void fetchMetaData() {
  452. Connection c = null;
  453. try {
  454. c = connectionPool.reserveConnection();
  455. DatabaseMetaData dbmd = c.getMetaData();
  456. if (dbmd != null) {
  457. tableName = SQLUtil.escapeSQL(tableName);
  458. ResultSet tables = dbmd.getTables(null, null, tableName, null);
  459. if (!tables.next()) {
  460. tables = dbmd.getTables(null, null,
  461. tableName.toUpperCase(), null);
  462. if (!tables.next()) {
  463. throw new IllegalArgumentException(
  464. "Table with the name \""
  465. + tableName
  466. + "\" was not found. Check your database contents.");
  467. } else {
  468. tableName = tableName.toUpperCase();
  469. }
  470. }
  471. tables.close();
  472. ResultSet rs = dbmd.getPrimaryKeys(null, null, tableName);
  473. List<String> names = new ArrayList<String>();
  474. while (rs.next()) {
  475. names.add(rs.getString("COLUMN_NAME"));
  476. }
  477. rs.close();
  478. if (!names.isEmpty()) {
  479. primaryKeyColumns = names;
  480. }
  481. if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) {
  482. throw new IllegalArgumentException(
  483. "Primary key constraints have not been defined for the table \""
  484. + tableName
  485. + "\". Use FreeFormQuery to access this table.");
  486. }
  487. for (String colName : primaryKeyColumns) {
  488. if (colName.equalsIgnoreCase("rownum")) {
  489. if (getSqlGenerator() instanceof MSSQLGenerator
  490. || getSqlGenerator() instanceof MSSQLGenerator) {
  491. throw new IllegalArgumentException(
  492. "When using Oracle or MSSQL, a primary key column"
  493. + " named \'rownum\' is not allowed!");
  494. }
  495. }
  496. }
  497. }
  498. } catch (SQLException e) {
  499. throw new RuntimeException(e);
  500. } finally {
  501. connectionPool.releaseConnection(c);
  502. }
  503. }
  504. private RowId getNewRowId(RowItem row, ResultSet genKeys) {
  505. try {
  506. /* Fetch primary key values and generate a map out of them. */
  507. Map<String, Object> values = new HashMap<String, Object>();
  508. ResultSetMetaData rsmd = genKeys.getMetaData();
  509. int colCount = rsmd.getColumnCount();
  510. if (genKeys.next()) {
  511. for (int i = 1; i <= colCount; i++) {
  512. values.put(rsmd.getColumnName(i), genKeys.getObject(i));
  513. }
  514. }
  515. /* Generate new RowId */
  516. List<Object> newRowId = new ArrayList<Object>();
  517. if (values.size() == 1) {
  518. if (primaryKeyColumns.size() == 1) {
  519. newRowId.add(values.get(values.keySet().iterator().next()));
  520. } else {
  521. for (String s : primaryKeyColumns) {
  522. if (!((ColumnProperty) row.getItemProperty(s))
  523. .isReadOnlyChangeAllowed()) {
  524. newRowId.add(values.get(values.keySet().iterator()
  525. .next()));
  526. } else {
  527. newRowId.add(values.get(s));
  528. }
  529. }
  530. }
  531. } else {
  532. for (String s : primaryKeyColumns) {
  533. newRowId.add(values.get(s));
  534. }
  535. }
  536. return new RowId(newRowId.toArray());
  537. } catch (Exception e) {
  538. logger.log(Level.FINE,
  539. "Failed to fetch key values on insert: " + e.getMessage());
  540. return null;
  541. }
  542. }
  543. /*
  544. * (non-Javadoc)
  545. *
  546. * @see
  547. * com.vaadin.addon.sqlcontainer.query.QueryDelegate#removeRow(com.vaadin
  548. * .addon.sqlcontainer.RowItem)
  549. */
  550. public boolean removeRow(RowItem row) throws UnsupportedOperationException,
  551. SQLException {
  552. logger.log(Level.FINE, "Removing row with id: "
  553. + row.getId().getId()[0].toString());
  554. if (executeUpdate(sqlGenerator.generateDeleteQuery(getTableName(),
  555. primaryKeyColumns, versionColumn, row)) == 1) {
  556. return true;
  557. }
  558. if (versionColumn != null) {
  559. throw new OptimisticLockException(
  560. "Someone else changed the row that was being deleted.",
  561. row.getId());
  562. }
  563. return false;
  564. }
  565. /*
  566. * (non-Javadoc)
  567. *
  568. * @see
  569. * com.vaadin.addon.sqlcontainer.query.QueryDelegate#containsRowWithKey(
  570. * java.lang.Object[])
  571. */
  572. public boolean containsRowWithKey(Object... keys) throws SQLException {
  573. ArrayList<Filter> filtersAndKeys = new ArrayList<Filter>();
  574. if (filters != null) {
  575. filtersAndKeys.addAll(filters);
  576. }
  577. int ix = 0;
  578. for (String colName : primaryKeyColumns) {
  579. filtersAndKeys.add(new Equal(colName, keys[ix]));
  580. ix++;
  581. }
  582. StatementHelper sh = sqlGenerator.generateSelectQuery(tableName,
  583. filtersAndKeys, orderBys, 0, 0, "*");
  584. boolean shouldCloseTransaction = false;
  585. if (!transactionOpen) {
  586. shouldCloseTransaction = true;
  587. beginTransaction();
  588. }
  589. ResultSet rs = null;
  590. try {
  591. rs = executeQuery(sh);
  592. boolean contains = rs.next();
  593. return contains;
  594. } finally {
  595. if (rs != null) {
  596. if (rs.getStatement() != null) {
  597. rs.getStatement().close();
  598. }
  599. rs.close();
  600. }
  601. if (shouldCloseTransaction) {
  602. commit();
  603. }
  604. }
  605. }
  606. /**
  607. * Custom writeObject to call rollback() if object is serialized.
  608. */
  609. private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  610. try {
  611. rollback();
  612. } catch (SQLException ignored) {
  613. }
  614. out.defaultWriteObject();
  615. }
  616. /**
  617. * Simple RowIdChangeEvent implementation.
  618. */
  619. public class RowIdChangeEvent extends EventObject implements
  620. QueryDelegate.RowIdChangeEvent {
  621. private final RowId oldId;
  622. private final RowId newId;
  623. private RowIdChangeEvent(RowId oldId, RowId newId) {
  624. super(oldId);
  625. this.oldId = oldId;
  626. this.newId = newId;
  627. }
  628. public RowId getNewRowId() {
  629. return newId;
  630. }
  631. public RowId getOldRowId() {
  632. return oldId;
  633. }
  634. }
  635. /**
  636. * Adds RowIdChangeListener to this query
  637. */
  638. public void addListener(RowIdChangeListener listener) {
  639. if (rowIdChangeListeners == null) {
  640. rowIdChangeListeners = new LinkedList<QueryDelegate.RowIdChangeListener>();
  641. }
  642. rowIdChangeListeners.add(listener);
  643. }
  644. /**
  645. * Removes the given RowIdChangeListener from this query
  646. */
  647. public void removeListener(RowIdChangeListener listener) {
  648. if (rowIdChangeListeners != null) {
  649. rowIdChangeListeners.remove(listener);
  650. }
  651. }
  652. }