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

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