001/* 002 * Copyright 2002-2014 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 * https://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 017package org.springframework.jdbc.support.incrementer; 018 019import java.sql.Connection; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.sql.Statement; 023import javax.sql.DataSource; 024 025import org.springframework.dao.DataAccessException; 026import org.springframework.dao.DataAccessResourceFailureException; 027import org.springframework.jdbc.datasource.DataSourceUtils; 028import org.springframework.jdbc.support.JdbcUtils; 029 030/** 031 * Abstract base class for {@link DataFieldMaxValueIncrementer} implementations 032 * which are based on identity columns in a sequence-like table. 033 * 034 * @author Juergen Hoeller 035 * @author Thomas Risberg 036 * @since 4.1.2 037 */ 038public abstract class AbstractIdentityColumnMaxValueIncrementer extends AbstractColumnMaxValueIncrementer { 039 040 private boolean deleteSpecificValues = false; 041 042 /** The current cache of values */ 043 private long[] valueCache; 044 045 /** The next id to serve from the value cache */ 046 private int nextValueIndex = -1; 047 048 049 /** 050 * Default constructor for bean property style usage. 051 * @see #setDataSource 052 * @see #setIncrementerName 053 * @see #setColumnName 054 */ 055 public AbstractIdentityColumnMaxValueIncrementer() { 056 } 057 058 public AbstractIdentityColumnMaxValueIncrementer(DataSource dataSource, String incrementerName, String columnName) { 059 super(dataSource, incrementerName, columnName); 060 } 061 062 063 /** 064 * Specify whether to delete the entire range below the current maximum key value 065 * ({@code false} - the default), or the specifically generated values ({@code true}). 066 * The former mode will use a where range clause whereas the latter will use an in 067 * clause starting with the lowest value minus 1, just preserving the maximum value. 068 */ 069 public void setDeleteSpecificValues(boolean deleteSpecificValues) { 070 this.deleteSpecificValues = deleteSpecificValues; 071 } 072 073 /** 074 * Return whether to delete the entire range below the current maximum key value 075 * ({@code false} - the default), or the specifically generated values ({@code true}). 076 */ 077 public boolean isDeleteSpecificValues() { 078 return this.deleteSpecificValues; 079 } 080 081 082 @Override 083 protected synchronized long getNextKey() throws DataAccessException { 084 if (this.nextValueIndex < 0 || this.nextValueIndex >= getCacheSize()) { 085 /* 086 * Need to use straight JDBC code because we need to make sure that the insert and select 087 * are performed on the same connection (otherwise we can't be sure that @@identity 088 * returns the correct value) 089 */ 090 Connection con = DataSourceUtils.getConnection(getDataSource()); 091 Statement stmt = null; 092 try { 093 stmt = con.createStatement(); 094 DataSourceUtils.applyTransactionTimeout(stmt, getDataSource()); 095 this.valueCache = new long[getCacheSize()]; 096 this.nextValueIndex = 0; 097 for (int i = 0; i < getCacheSize(); i++) { 098 stmt.executeUpdate(getIncrementStatement()); 099 ResultSet rs = stmt.executeQuery(getIdentityStatement()); 100 try { 101 if (!rs.next()) { 102 throw new DataAccessResourceFailureException("Identity statement failed after inserting"); 103 } 104 this.valueCache[i] = rs.getLong(1); 105 } 106 finally { 107 JdbcUtils.closeResultSet(rs); 108 } 109 } 110 stmt.executeUpdate(getDeleteStatement(this.valueCache)); 111 } 112 catch (SQLException ex) { 113 throw new DataAccessResourceFailureException("Could not increment identity", ex); 114 } 115 finally { 116 JdbcUtils.closeStatement(stmt); 117 DataSourceUtils.releaseConnection(con, getDataSource()); 118 } 119 } 120 return this.valueCache[this.nextValueIndex++]; 121 } 122 123 124 /** 125 * Statement to use to increment the "sequence" value. 126 * @return the SQL statement to use 127 */ 128 protected abstract String getIncrementStatement(); 129 130 /** 131 * Statement to use to obtain the current identity value. 132 * @return the SQL statement to use 133 */ 134 protected abstract String getIdentityStatement(); 135 136 /** 137 * Statement to use to clean up "sequence" values. 138 * <p>The default implementation either deletes the entire range below 139 * the current maximum value, or the specifically generated values 140 * (starting with the lowest minus 1, just preserving the maximum value) 141 * - according to the {@link #isDeleteSpecificValues()} setting. 142 * @param values the currently generated key values 143 * (the number of values corresponds to {@link #getCacheSize()}) 144 * @return the SQL statement to use 145 */ 146 protected String getDeleteStatement(long[] values) { 147 StringBuilder sb = new StringBuilder(64); 148 sb.append("delete from ").append(getIncrementerName()).append(" where ").append(getColumnName()); 149 if (isDeleteSpecificValues()) { 150 sb.append(" in (").append(values[0] - 1); 151 for (int i = 0; i < values.length - 1; i++) { 152 sb.append(", ").append(values[i]); 153 } 154 sb.append(")"); 155 } 156 else { 157 long maxValue = values[values.length - 1]; 158 sb.append(" < ").append(maxValue); 159 } 160 return sb.toString(); 161 } 162 163}