/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.jdbc.support.incrementer;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;

Abstract base class for DataFieldMaxValueIncrementer implementations which are based on identity columns in a sequence-like table.
Author:Juergen Hoeller, Thomas Risberg
Since:4.1.2
/** * Abstract base class for {@link DataFieldMaxValueIncrementer} implementations * which are based on identity columns in a sequence-like table. * * @author Juergen Hoeller * @author Thomas Risberg * @since 4.1.2 */
public abstract class AbstractIdentityColumnMaxValueIncrementer extends AbstractColumnMaxValueIncrementer { private boolean deleteSpecificValues = false;
The current cache of values.
/** The current cache of values. */
private long[] valueCache;
The next id to serve from the value cache.
/** The next id to serve from the value cache. */
private int nextValueIndex = -1;
Default constructor for bean property style usage.
See Also:
/** * Default constructor for bean property style usage. * @see #setDataSource * @see #setIncrementerName * @see #setColumnName */
public AbstractIdentityColumnMaxValueIncrementer() { } public AbstractIdentityColumnMaxValueIncrementer(DataSource dataSource, String incrementerName, String columnName) { super(dataSource, incrementerName, columnName); }
Specify whether to delete the entire range below the current maximum key value (false - the default), or the specifically generated values (true). The former mode will use a where range clause whereas the latter will use an in clause starting with the lowest value minus 1, just preserving the maximum value.
/** * Specify whether to delete the entire range below the current maximum key value * ({@code false} - the default), or the specifically generated values ({@code true}). * The former mode will use a where range clause whereas the latter will use an in * clause starting with the lowest value minus 1, just preserving the maximum value. */
public void setDeleteSpecificValues(boolean deleteSpecificValues) { this.deleteSpecificValues = deleteSpecificValues; }
Return whether to delete the entire range below the current maximum key value (false - the default), or the specifically generated values (true).
/** * Return whether to delete the entire range below the current maximum key value * ({@code false} - the default), or the specifically generated values ({@code true}). */
public boolean isDeleteSpecificValues() { return this.deleteSpecificValues; } @Override protected synchronized long getNextKey() throws DataAccessException { if (this.nextValueIndex < 0 || this.nextValueIndex >= getCacheSize()) { /* * Need to use straight JDBC code because we need to make sure that the insert and select * are performed on the same connection (otherwise we can't be sure that @@identity * returns the correct value) */ Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { stmt = con.createStatement(); DataSourceUtils.applyTransactionTimeout(stmt, getDataSource()); this.valueCache = new long[getCacheSize()]; this.nextValueIndex = 0; for (int i = 0; i < getCacheSize(); i++) { stmt.executeUpdate(getIncrementStatement()); ResultSet rs = stmt.executeQuery(getIdentityStatement()); try { if (!rs.next()) { throw new DataAccessResourceFailureException("Identity statement failed after inserting"); } this.valueCache[i] = rs.getLong(1); } finally { JdbcUtils.closeResultSet(rs); } } stmt.executeUpdate(getDeleteStatement(this.valueCache)); } catch (SQLException ex) { throw new DataAccessResourceFailureException("Could not increment identity", ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } } return this.valueCache[this.nextValueIndex++]; }
Statement to use to increment the "sequence" value.
Returns:the SQL statement to use
/** * Statement to use to increment the "sequence" value. * @return the SQL statement to use */
protected abstract String getIncrementStatement();
Statement to use to obtain the current identity value.
Returns:the SQL statement to use
/** * Statement to use to obtain the current identity value. * @return the SQL statement to use */
protected abstract String getIdentityStatement();
Statement to use to clean up "sequence" values.

The default implementation either deletes the entire range below the current maximum value, or the specifically generated values (starting with the lowest minus 1, just preserving the maximum value) - according to the isDeleteSpecificValues() setting.

Params:
Returns:the SQL statement to use
/** * Statement to use to clean up "sequence" values. * <p>The default implementation either deletes the entire range below * the current maximum value, or the specifically generated values * (starting with the lowest minus 1, just preserving the maximum value) * - according to the {@link #isDeleteSpecificValues()} setting. * @param values the currently generated key values * (the number of values corresponds to {@link #getCacheSize()}) * @return the SQL statement to use */
protected String getDeleteStatement(long[] values) { StringBuilder sb = new StringBuilder(64); sb.append("delete from ").append(getIncrementerName()).append(" where ").append(getColumnName()); if (isDeleteSpecificValues()) { sb.append(" in (").append(values[0] - 1); for (int i = 0; i < values.length - 1; i++) { sb.append(", ").append(values[i]); } sb.append(")"); } else { long maxValue = values[values.length - 1]; sb.append(" < ").append(maxValue); } return sb.toString(); } }