1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.jdbc.support.incrementer;
  17. import java.sql.Connection;
  18. import java.sql.ResultSet;
  19. import java.sql.SQLException;
  20. import java.sql.Statement;
  21. import javax.sql.DataSource;
  22. import org.springframework.dao.DataAccessException;
  23. import org.springframework.dao.DataAccessResourceFailureException;
  24. import org.springframework.jdbc.datasource.DataSourceUtils;
  25. import org.springframework.jdbc.support.JdbcUtils;
  26. /**
  27. * Class to increment maximum value of a given HSQL table with the equivalent
  28. * of an auto-increment column. Note: If you use this class, your HSQL key
  29. * column should <i>NOT</i> be auto-increment, as the sequence table does the job.
  30. *
  31. * <p>The sequence is kept in a table. There should be one sequence table per
  32. * table that needs an auto-generated key.
  33. *
  34. * <p>Example:
  35. *
  36. * <pre>
  37. * create table tab (id int not null primary key, text varchar(100));
  38. * create table tab_sequence (value identity);
  39. * insert into tab_sequence values(0);</pre>
  40. *
  41. * If cacheSize is set, the intermediate values are served without querying the
  42. * database. If the server or your application is stopped or crashes or a transaction
  43. * is rolled back, the unused values will never be served. The maximum hole size in
  44. * numbering is consequently the value of cacheSize.
  45. *
  46. * @author Isabelle Muszynski
  47. * @author Jean-Pierre Pawlak
  48. * @author Thomas Risberg
  49. * @version $Id: HsqlMaxValueIncrementer.java,v 1.6 2004/04/22 07:58:24 jhoeller Exp $
  50. */
  51. public class HsqlMaxValueIncrementer extends AbstractDataFieldMaxValueIncrementer {
  52. /** The name of the column for this sequence */
  53. private String columnName;
  54. /** The number of keys buffered in a cache */
  55. private int cacheSize = 1;
  56. private long[] valueCache = null;
  57. /** The next id to serve from the value cache */
  58. private int nextValueIndex = -1;
  59. /**
  60. * Default constructor.
  61. **/
  62. public HsqlMaxValueIncrementer() {
  63. }
  64. /**
  65. * Convenience constructor.
  66. * @param ds the DataSource to use
  67. * @param incrementerName the name of the sequence/table to use
  68. * @param columnName the name of the column in the sequence table to use
  69. **/
  70. public HsqlMaxValueIncrementer(DataSource ds, String incrementerName, String columnName) {
  71. setDataSource(ds);
  72. setIncrementerName(incrementerName);
  73. this.columnName = columnName;
  74. afterPropertiesSet();
  75. }
  76. /**
  77. * Set the name of the column in the sequence table.
  78. */
  79. public void setColumnName(String columnName) {
  80. this.columnName = columnName;
  81. }
  82. /**
  83. * Return the name of the column in the sequence table.
  84. */
  85. public String getColumnName() {
  86. return this.columnName;
  87. }
  88. /**
  89. * Set the number of buffered keys.
  90. */
  91. public void setCacheSize(int cacheSize) {
  92. this.cacheSize = cacheSize;
  93. }
  94. /**
  95. * Return the number of buffered keys.
  96. */
  97. public int getCacheSize() {
  98. return this.cacheSize;
  99. }
  100. public void afterPropertiesSet() {
  101. super.afterPropertiesSet();
  102. if (this.columnName == null) {
  103. throw new IllegalArgumentException("columnName is required");
  104. }
  105. }
  106. protected synchronized long getNextKey() throws DataAccessException {
  107. if (this.nextValueIndex < 0 || this.nextValueIndex >= getCacheSize()) {
  108. /*
  109. * Need to use straight JDBC code because we need to make sure that the insert and select
  110. * are performed on the same connection (otherwise we can't be sure that last_insert_id()
  111. * returned the correct value)
  112. */
  113. Connection con = DataSourceUtils.getConnection(getDataSource());
  114. Statement stmt = null;
  115. try {
  116. stmt = con.createStatement();
  117. DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
  118. this.valueCache = new long[getCacheSize()];
  119. this.nextValueIndex = 0;
  120. for (int i = 0; i < getCacheSize(); i++) {
  121. stmt.executeUpdate("insert into " + getIncrementerName() + " values(null)");
  122. ResultSet rs = stmt.executeQuery("select max(identity()) from " + getIncrementerName());
  123. try {
  124. if (!rs.next()) {
  125. throw new DataAccessResourceFailureException("identity() failed after executing an update");
  126. }
  127. this.valueCache[i] = rs.getLong(1);
  128. }
  129. finally {
  130. JdbcUtils.closeResultSet(rs);
  131. }
  132. }
  133. long maxValue = this.valueCache[(this.valueCache.length - 1)];
  134. stmt.executeUpdate("delete from " + getIncrementerName() + " where " + this.columnName + " < " + maxValue);
  135. }
  136. catch (SQLException ex) {
  137. throw new DataAccessResourceFailureException("Could not obtain identity()", ex);
  138. }
  139. finally {
  140. JdbcUtils.closeStatement(stmt);
  141. DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
  142. }
  143. }
  144. return this.valueCache[this.nextValueIndex++];
  145. }
  146. }