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