diff --git a/.gitignore b/.gitignore
index fad40fcb3..acb9b8d91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ local.properties
.classpath
.vscode/
.settings/
+.gradle/
.loadpath
# External tool builders
diff --git a/build.gradle b/build.gradle
index 55792f0a3..38e407d03 100644
--- a/build.gradle
+++ b/build.gradle
@@ -67,8 +67,9 @@ repositories {
dependencies {
compile 'com.microsoft.azure:azure-keyvault:0.9.7',
- 'com.microsoft.azure:adal4j:1.1.3'
-
+ 'com.microsoft.azure:adal4j:1.1.3',
+ 'com.google.guava:guava:19.0'
+
testCompile 'junit:junit:4.12',
'org.junit.platform:junit-platform-console:1.0.0-M3',
'org.junit.platform:junit-platform-commons:1.0.0-M3',
diff --git a/pom.xml b/pom.xml
index 8dfd2d078..dfcc503e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,13 @@
true
+
+ com.google.guava
+ guava
+ 19.0
+ false
+
+
junit
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
index b1dff7c4d..923914f4e 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
@@ -28,6 +28,9 @@
import java.util.Vector;
import java.util.logging.Level;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.Cache;
+
/**
* SQLServerPreparedStatement provides JDBC prepared statement functionality. SQLServerPreparedStatement provides methods for the user to supply
* parameters as any native Java type and many Java object types.
@@ -98,6 +101,48 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
*/
private boolean encryptionMetadataIsRetrieved = false;
+ /** Size of the prepared statement meta data cache */
+ static final private int preparedStatementMetadataSQLCacheSize = 100;
+
+ /** Cache of prepared statement meta data */
+ static private Cache preparedStatementSQLMetadataCache;
+ static {
+ preparedStatementSQLMetadataCache = CacheBuilder.newBuilder()
+ .maximumSize(preparedStatementMetadataSQLCacheSize)
+ .build();
+ }
+
+ /**
+ * Used to keep track of an individual handle ready for un-prepare.
+ */
+ private final class PreparedStatementMetadataSQLCacheItem {
+ String preparedSQLText;
+ int parameterCount;
+ String procedureName;
+ boolean bReturnValueSyntax;
+
+ PreparedStatementMetadataSQLCacheItem(String preparedSQLText, int parameterCount, String procedureName, boolean bReturnValueSyntax){
+ this.preparedSQLText = preparedSQLText;
+ this.parameterCount = parameterCount;
+ this.procedureName = procedureName;
+ this.bReturnValueSyntax = bReturnValueSyntax;
+ }
+ }
+
+ /** Get prepared statement cache entry if exists */
+ public PreparedStatementMetadataSQLCacheItem getCachedPreparedStatementSQLMetadata(String initialSql){
+ return preparedStatementSQLMetadataCache.getIfPresent(initialSql);
+ }
+
+ /** Cache entry for this prepared statement */
+ public PreparedStatementMetadataSQLCacheItem metadataSQLCacheItem;
+
+ /** Add cache entry for prepared statement metadata*/
+ public void cachePreparedStatementSQLMetaData(String initialSql, PreparedStatementMetadataSQLCacheItem newItem){
+
+ preparedStatementSQLMetadataCache.put(initialSql, newItem);
+ }
+
// Internal function used in tracing
String getClassNameInternal() {
return "SQLServerPreparedStatement";
@@ -128,13 +173,34 @@ String getClassNameInternal() {
stmtPoolable = true;
sqlCommand = sql;
- JDBCSyntaxTranslator translator = new JDBCSyntaxTranslator();
- sql = translator.translate(sql);
- procedureName = translator.getProcedureName(); // may return null
- bReturnValueSyntax = translator.hasReturnValueSyntax();
-
- userSQL = sql;
- initParams(userSQL);
+ // Save original SQL statement.
+ sqlCommand = sql;
+
+ // Check for cached SQL metadata.
+ PreparedStatementMetadataSQLCacheItem cacheItem = getCachedPreparedStatementSQLMetadata(sql);
+
+ // No cached meta data found, parse.
+ if(null == cacheItem) {
+ JDBCSyntaxTranslator translator = new JDBCSyntaxTranslator();
+
+ userSQL = translator.translate(sql);
+ procedureName = translator.getProcedureName(); // may return null
+ bReturnValueSyntax = translator.hasReturnValueSyntax();
+
+ // Save processed SQL statement.
+ initParams(userSQL);
+
+ // Cache this entry.
+ cacheItem = new PreparedStatementMetadataSQLCacheItem(userSQL, inOutParam.length, procedureName, bReturnValueSyntax);
+ cachePreparedStatementSQLMetaData(sqlCommand/*original command as key*/, cacheItem);
+ }
+ else {
+ // Retrieve from cache item.
+ procedureName = cacheItem.procedureName;
+ bReturnValueSyntax = cacheItem.bReturnValueSyntax;
+ userSQL = cacheItem.preparedSQLText;
+ initParams(cacheItem.parameterCount);
+ }
}
/**
@@ -217,12 +283,11 @@ final void closeInternal() {
}
/**
- * Intialize the statement parameters.
+ * Find and intialize the statement parameters.
*
* @param sql
*/
/* L0 */ final void initParams(String sql) {
- encryptionMetadataIsRetrieved = false;
int nParams = 0;
// Figure out the expected number of parameters by counting the
@@ -231,6 +296,15 @@ final void closeInternal() {
while ((offset = ParameterUtils.scanSQLForChar('?', sql, ++offset)) < sql.length())
++nParams;
+ initParams(nParams);
+ }
+
+ /**
+ * Intialize the statement parameters.
+ *
+ * @param sql
+ */
+ /* L0 */ final void initParams(int nParams) {
inOutParam = new Parameter[nParams];
for (int i = 0; i < nParams; i++) {
inOutParam[i] = new Parameter(Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection));