001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jdbc.support.incrementer;
018:
019: import java.sql.Connection;
020: import java.sql.ResultSet;
021: import java.sql.SQLException;
022: import java.sql.Statement;
023:
024: import javax.sql.DataSource;
025:
026: import org.springframework.dao.DataAccessException;
027: import org.springframework.dao.DataAccessResourceFailureException;
028: import org.springframework.jdbc.datasource.DataSourceUtils;
029: import org.springframework.jdbc.support.JdbcUtils;
030:
031: /**
032: * Class to increment maximum value of a given HSQL table with the equivalent
033: * of an auto-increment column. Note: If you use this class, your HSQL key
034: * column should <i>NOT</i> be auto-increment, as the sequence table does the job.
035: *
036: * <p>The sequence is kept in a table. There should be one sequence table per
037: * table that needs an auto-generated key.
038: *
039: * <p>Example:
040: *
041: * <pre class="code">create table tab (id int not null primary key, text varchar(100));
042: * create table tab_sequence (value identity);
043: * insert into tab_sequence values(0);</pre>
044: *
045: * If cacheSize is set, the intermediate values are served without querying the
046: * database. If the server or your application is stopped or crashes or a transaction
047: * is rolled back, the unused values will never be served. The maximum hole size in
048: * numbering is consequently the value of cacheSize.
049: *
050: * @author Jean-Pierre Pawlak
051: * @author Thomas Risberg
052: * @author Juergen Hoeller
053: */
054: public class HsqlMaxValueIncrementer extends
055: AbstractDataFieldMaxValueIncrementer {
056:
057: /** The name of the column for this sequence */
058: private String columnName;
059:
060: /** The number of keys buffered in a cache */
061: private int cacheSize = 1;
062:
063: private long[] valueCache = null;
064:
065: /** The next id to serve from the value cache */
066: private int nextValueIndex = -1;
067:
068: /**
069: * Default constructor.
070: **/
071: public HsqlMaxValueIncrementer() {
072: }
073:
074: /**
075: * Convenience constructor.
076: * @param ds the DataSource to use
077: * @param incrementerName the name of the sequence/table to use
078: * @param columnName the name of the column in the sequence table to use
079: **/
080: public HsqlMaxValueIncrementer(DataSource ds,
081: String incrementerName, String columnName) {
082: setDataSource(ds);
083: setIncrementerName(incrementerName);
084: this .columnName = columnName;
085: afterPropertiesSet();
086: }
087:
088: /**
089: * Set the name of the column in the sequence table.
090: */
091: public void setColumnName(String columnName) {
092: this .columnName = columnName;
093: }
094:
095: /**
096: * Return the name of the column in the sequence table.
097: */
098: public String getColumnName() {
099: return this .columnName;
100: }
101:
102: /**
103: * Set the number of buffered keys.
104: */
105: public void setCacheSize(int cacheSize) {
106: this .cacheSize = cacheSize;
107: }
108:
109: /**
110: * Return the number of buffered keys.
111: */
112: public int getCacheSize() {
113: return this .cacheSize;
114: }
115:
116: public void afterPropertiesSet() {
117: super .afterPropertiesSet();
118: if (this .columnName == null) {
119: throw new IllegalArgumentException("columnName is required");
120: }
121: }
122:
123: protected synchronized long getNextKey() throws DataAccessException {
124: if (this .nextValueIndex < 0
125: || this .nextValueIndex >= getCacheSize()) {
126: /*
127: * Need to use straight JDBC code because we need to make sure that the insert and select
128: * are performed on the same connection (otherwise we can't be sure that last_insert_id()
129: * returned the correct value)
130: */
131: Connection con = DataSourceUtils
132: .getConnection(getDataSource());
133: Statement stmt = null;
134: try {
135: stmt = con.createStatement();
136: DataSourceUtils.applyTransactionTimeout(stmt,
137: getDataSource());
138: this .valueCache = new long[getCacheSize()];
139: this .nextValueIndex = 0;
140: for (int i = 0; i < getCacheSize(); i++) {
141: stmt.executeUpdate("insert into "
142: + getIncrementerName() + " values(null)");
143: ResultSet rs = stmt
144: .executeQuery("select max(identity()) from "
145: + getIncrementerName());
146: try {
147: if (!rs.next()) {
148: throw new DataAccessResourceFailureException(
149: "identity() failed after executing an update");
150: }
151: this .valueCache[i] = rs.getLong(1);
152: } finally {
153: JdbcUtils.closeResultSet(rs);
154: }
155: }
156: long maxValue = this .valueCache[(this .valueCache.length - 1)];
157: stmt.executeUpdate("delete from "
158: + getIncrementerName() + " where "
159: + this .columnName + " < " + maxValue);
160: } catch (SQLException ex) {
161: throw new DataAccessResourceFailureException(
162: "Could not obtain identity()", ex);
163: } finally {
164: JdbcUtils.closeStatement(stmt);
165: DataSourceUtils.releaseConnection(con, getDataSource());
166: }
167: }
168: return this.valueCache[this.nextValueIndex++];
169: }
170:
171: }
|