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.object; 018 019import java.util.List; 020import java.util.Map; 021 022import javax.sql.DataSource; 023 024import org.springframework.jdbc.core.CallableStatementCreator; 025import org.springframework.jdbc.core.CallableStatementCreatorFactory; 026import org.springframework.jdbc.core.ParameterMapper; 027import org.springframework.jdbc.core.SqlParameter; 028import org.springframework.lang.Nullable; 029import org.springframework.util.Assert; 030 031/** 032 * RdbmsOperation using a JdbcTemplate and representing an SQL-based 033 * call such as a stored procedure or a stored function. 034 * 035 * <p>Configures a CallableStatementCreatorFactory based on the declared 036 * parameters. 037 * 038 * @author Rod Johnson 039 * @author Thomas Risberg 040 * @see CallableStatementCreatorFactory 041 */ 042public abstract class SqlCall extends RdbmsOperation { 043 044 /** 045 * Flag used to indicate that this call is for a function and to 046 * use the {? = call get_invoice_count(?)} syntax. 047 */ 048 private boolean function = false; 049 050 /** 051 * Flag used to indicate that the sql for this call should be used exactly as 052 * it is defined. No need to add the escape syntax and parameter place holders. 053 */ 054 private boolean sqlReadyForUse = false; 055 056 /** 057 * Call string as defined in java.sql.CallableStatement. 058 * String of form {call add_invoice(?, ?, ?)} or {? = call get_invoice_count(?)} 059 * if isFunction is set to true. Updated after each parameter is added. 060 */ 061 @Nullable 062 private String callString; 063 064 /** 065 * Object enabling us to create CallableStatementCreators 066 * efficiently, based on this class's declared parameters. 067 */ 068 @Nullable 069 private CallableStatementCreatorFactory callableStatementFactory; 070 071 072 /** 073 * Constructor to allow use as a JavaBean. 074 * A DataSource, SQL and any parameters must be supplied before 075 * invoking the {@code compile} method and using this object. 076 * @see #setDataSource 077 * @see #setSql 078 * @see #compile 079 */ 080 public SqlCall() { 081 } 082 083 /** 084 * Create a new SqlCall object with SQL, but without parameters. 085 * Must add parameters or settle with none. 086 * @param ds the DataSource to obtain connections from 087 * @param sql the SQL to execute 088 */ 089 public SqlCall(DataSource ds, String sql) { 090 setDataSource(ds); 091 setSql(sql); 092 } 093 094 095 /** 096 * Set whether this call is for a function. 097 */ 098 public void setFunction(boolean function) { 099 this.function = function; 100 } 101 102 /** 103 * Return whether this call is for a function. 104 */ 105 public boolean isFunction() { 106 return this.function; 107 } 108 109 /** 110 * Set whether the SQL can be used as is. 111 */ 112 public void setSqlReadyForUse(boolean sqlReadyForUse) { 113 this.sqlReadyForUse = sqlReadyForUse; 114 } 115 116 /** 117 * Return whether the SQL can be used as is. 118 */ 119 public boolean isSqlReadyForUse() { 120 return this.sqlReadyForUse; 121 } 122 123 124 /** 125 * Overridden method to configure the CallableStatementCreatorFactory 126 * based on our declared parameters. 127 * @see RdbmsOperation#compileInternal() 128 */ 129 @Override 130 protected final void compileInternal() { 131 if (isSqlReadyForUse()) { 132 this.callString = resolveSql(); 133 } 134 else { 135 StringBuilder callString = new StringBuilder(32); 136 List<SqlParameter> parameters = getDeclaredParameters(); 137 int parameterCount = 0; 138 if (isFunction()) { 139 callString.append("{? = call ").append(resolveSql()).append('('); 140 parameterCount = -1; 141 } 142 else { 143 callString.append("{call ").append(resolveSql()).append('('); 144 } 145 for (SqlParameter parameter : parameters) { 146 if (!parameter.isResultsParameter()) { 147 if (parameterCount > 0) { 148 callString.append(", "); 149 } 150 if (parameterCount >= 0) { 151 callString.append('?'); 152 } 153 parameterCount++; 154 } 155 } 156 callString.append(")}"); 157 this.callString = callString.toString(); 158 } 159 if (logger.isDebugEnabled()) { 160 logger.debug("Compiled stored procedure. Call string is [" + this.callString + "]"); 161 } 162 163 this.callableStatementFactory = new CallableStatementCreatorFactory(this.callString, getDeclaredParameters()); 164 this.callableStatementFactory.setResultSetType(getResultSetType()); 165 this.callableStatementFactory.setUpdatableResults(isUpdatableResults()); 166 167 onCompileInternal(); 168 } 169 170 /** 171 * Hook method that subclasses may override to react to compilation. 172 * This implementation does nothing. 173 */ 174 protected void onCompileInternal() { 175 } 176 177 /** 178 * Get the call string. 179 */ 180 @Nullable 181 public String getCallString() { 182 return this.callString; 183 } 184 185 /** 186 * Return a CallableStatementCreator to perform an operation 187 * with this parameters. 188 * @param inParams parameters. May be {@code null}. 189 */ 190 protected CallableStatementCreator newCallableStatementCreator(@Nullable Map<String, ?> inParams) { 191 Assert.state(this.callableStatementFactory != null, "No CallableStatementFactory available"); 192 return this.callableStatementFactory.newCallableStatementCreator(inParams); 193 } 194 195 /** 196 * Return a CallableStatementCreator to perform an operation 197 * with the parameters returned from this ParameterMapper. 198 * @param inParamMapper parametermapper. May not be {@code null}. 199 */ 200 protected CallableStatementCreator newCallableStatementCreator(ParameterMapper inParamMapper) { 201 Assert.state(this.callableStatementFactory != null, "No CallableStatementFactory available"); 202 return this.callableStatementFactory.newCallableStatementCreator(inParamMapper); 203 } 204 205}