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 MySQL table with the equivalent
  28. * of an auto-increment column. Note: If you use this class, your MySQL 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. The table type of the sequence table
  33. * should be MyISAM so the sequences are allocated without regard to any
  34. * transactions that might be in progress.
  35. *
  36. * <p>Example:
  37. *
  38. * <pre>
  39. * create table tab (id int unsigned not null primary key, text varchar(100));
  40. * create table tab_sequence (value int not null) type=MYISAM;
  41. * insert into tab_sequence values(0);</pre>
  42. *
  43. * If cacheSize is set, the intermediate values are served without querying the
  44. * database. If the server or your application is stopped or crashes or a transaction
  45. * is rolled back, the unused values will never be served. The maximum hole size in
  46. * numbering is consequently the value of cacheSize.
  47. *
  48. * @author Isabelle Muszynski
  49. * @author Jean-Pierre Pawlak
  50. * @author Thomas Risberg
  51. * @version $Id: MySQLMaxValueIncrementer.java,v 1.6 2004/04/22 07:58:24 jhoeller Exp $
  52. */
  53. public class MySQLMaxValueIncrementer extends AbstractDataFieldMaxValueIncrementer {
  54. /** The Sql string for retrieving the new sequence value */
  55. private static final String VALUE_SQL = "select last_insert_id()";
  56. /** The name of the column for this sequence */
  57. private String columnName;
  58. /** The number of keys buffered in a cache */
  59. private int cacheSize = 1;
  60. /** The next id to serve */
  61. private long nextId = 0;
  62. /** The max id to serve */
  63. private long maxId = 0;
  64. /**
  65. * Default constructor.
  66. **/
  67. public MySQLMaxValueIncrementer() {
  68. }
  69. /**
  70. * Convenience constructor.
  71. * @param ds the DataSource to use
  72. * @param incrementerName the name of the sequence/table to use
  73. * @param columnName the name of the column in the sequence table to use
  74. **/
  75. public MySQLMaxValueIncrementer(DataSource ds, String incrementerName, String columnName) {
  76. setDataSource(ds);
  77. setIncrementerName(incrementerName);
  78. this.columnName = columnName;
  79. afterPropertiesSet();
  80. }
  81. /**
  82. * Set the name of the column in the sequence table.
  83. */
  84. public void setColumnName(String columnName) {
  85. this.columnName = columnName;
  86. }
  87. /**
  88. * Return the name of the column in the sequence table.
  89. */
  90. public String getColumnName() {
  91. return this.columnName;
  92. }
  93. /**
  94. * Set the number of buffered keys.
  95. */
  96. public void setCacheSize(int cacheSize) {
  97. this.cacheSize = cacheSize;
  98. }
  99. /**
  100. * Return the number of buffered keys.
  101. */
  102. public int getCacheSize() {
  103. return this.cacheSize;
  104. }
  105. public void afterPropertiesSet() {
  106. super.afterPropertiesSet();
  107. if (this.columnName == null) {
  108. throw new IllegalArgumentException("columnName is required");
  109. }
  110. }
  111. protected synchronized long getNextKey() throws DataAccessException {
  112. if (this.maxId == this.nextId) {
  113. /*
  114. * Need to use straight JDBC code because we need to make sure that the insert and select
  115. * are performed on the same connection (otherwise we can't be sure that last_insert_id()
  116. * returned the correct value)
  117. */
  118. Connection con = DataSourceUtils.getConnection(getDataSource());
  119. Statement stmt = null;
  120. try {
  121. stmt = con.createStatement();
  122. DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
  123. // increment the sequence column
  124. stmt.executeUpdate("update "+ getIncrementerName() + " set " + this.columnName +
  125. " = last_insert_id(" + this.columnName + " + " + getCacheSize() + ")");
  126. // retrieve the new max of the sequence column
  127. ResultSet rs = stmt.executeQuery(VALUE_SQL);
  128. try {
  129. if (!rs.next()) {
  130. throw new DataAccessResourceFailureException("last_insert_id() failed after executing an update");
  131. }
  132. this.maxId = rs.getLong(1);
  133. }
  134. finally {
  135. JdbcUtils.closeResultSet(rs);
  136. }
  137. this.nextId = this.maxId - getCacheSize() + 1;
  138. }
  139. catch (SQLException ex) {
  140. throw new DataAccessResourceFailureException("Could not obtain last_insert_id()", ex);
  141. }
  142. finally {
  143. JdbcUtils.closeStatement(stmt);
  144. DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
  145. }
  146. }
  147. else {
  148. this.nextId++;
  149. }
  150. return this.nextId;
  151. }
  152. }