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}