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: * NOTE: HSQL now supports sequences and you should consider using them instead.
051: *
052: * @author Jean-Pierre Pawlak
053: * @author Thomas Risberg
054: * @author Juergen Hoeller
055: * @see org.springframework.jdbc.support.incrementer.HsqlSequenceMaxValueIncrementer
056: */
057: public class HsqlMaxValueIncrementer extends
058: AbstractDataFieldMaxValueIncrementer {
059:
060: /** The name of the column for this sequence */
061: private String columnName;
062:
063: /** The number of keys buffered in a cache */
064: private int cacheSize = 1;
065:
066: private long[] valueCache = null;
067:
068: /** The next id to serve from the value cache */
069: private int nextValueIndex = -1;
070:
071: /**
072: * Default constructor.
073: **/
074: public HsqlMaxValueIncrementer() {
075: }
076:
077: /**
078: * Convenience constructor.
079: * @param ds the DataSource to use
080: * @param incrementerName the name of the sequence/table to use
081: * @param columnName the name of the column in the sequence table to use
082: **/
083: public HsqlMaxValueIncrementer(DataSource ds,
084: String incrementerName, String columnName) {
085: setDataSource(ds);
086: setIncrementerName(incrementerName);
087: this .columnName = columnName;
088: afterPropertiesSet();
089: }
090:
091: /**
092: * Set the name of the column in the sequence table.
093: */
094: public void setColumnName(String columnName) {
095: this .columnName = columnName;
096: }
097:
098: /**
099: * Return the name of the column in the sequence table.
100: */
101: public String getColumnName() {
102: return this .columnName;
103: }
104:
105: /**
106: * Set the number of buffered keys.
107: */
108: public void setCacheSize(int cacheSize) {
109: this .cacheSize = cacheSize;
110: }
111:
112: /**
113: * Return the number of buffered keys.
114: */
115: public int getCacheSize() {
116: return this .cacheSize;
117: }
118:
119: public void afterPropertiesSet() {
120: super .afterPropertiesSet();
121: if (this .columnName == null) {
122: throw new IllegalArgumentException("columnName is required");
123: }
124: }
125:
126: protected synchronized long getNextKey() throws DataAccessException {
127: if (this .nextValueIndex < 0
128: || this .nextValueIndex >= getCacheSize()) {
129: /*
130: * Need to use straight JDBC code because we need to make sure that the insert and select
131: * are performed on the same connection (otherwise we can't be sure that last_insert_id()
132: * returned the correct value)
133: */
134: Connection con = DataSourceUtils
135: .getConnection(getDataSource());
136: Statement stmt = null;
137: try {
138: stmt = con.createStatement();
139: DataSourceUtils.applyTransactionTimeout(stmt,
140: getDataSource());
141: this .valueCache = new long[getCacheSize()];
142: this .nextValueIndex = 0;
143: for (int i = 0; i < getCacheSize(); i++) {
144: stmt.executeUpdate("insert into "
145: + getIncrementerName() + " values(null)");
146: ResultSet rs = stmt
147: .executeQuery("select max(identity()) from "
148: + getIncrementerName());
149: try {
150: if (!rs.next()) {
151: throw new DataAccessResourceFailureException(
152: "identity() failed after executing an update");
153: }
154: this .valueCache[i] = rs.getLong(1);
155: } finally {
156: JdbcUtils.closeResultSet(rs);
157: }
158: }
159: long maxValue = this .valueCache[(this .valueCache.length - 1)];
160: stmt.executeUpdate("delete from "
161: + getIncrementerName() + " where "
162: + this .columnName + " < " + maxValue);
163: } catch (SQLException ex) {
164: throw new DataAccessResourceFailureException(
165: "Could not obtain identity()", ex);
166: } finally {
167: JdbcUtils.closeStatement(stmt);
168: DataSourceUtils.releaseConnection(con, getDataSource());
169: }
170: }
171: return this.valueCache[this.nextValueIndex++];
172: }
173:
174: }
|