--- src/com/mysql/jdbc/PreparedStatement.java 5.1.7
+++ src/com/mysql/jdbc/PreparedStatement.java 5.1.7 + optim
@@ -181,7 +181,13 @@
 
 		byte[][] staticSql = null;
 
-		boolean isOnDuplicateKeyUpdate = false;
+        int onDuplicateKeyUpdateIndex = -1;
+        
+        boolean isOnDuplicateKeyUpdateWithArgs = false;
+        
+        int firstValue = -1;
+        
+        int lastParmEnd = 0;
 		
 		/**
 		 * Represents the "parsed" state of a client-side
@@ -199,10 +205,136 @@
 							SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
 				}
 
-				this.isOnDuplicateKeyUpdate = containsOnDuplicateKeyInString(sql);
+                BashParseInfo parent = null;
+                // True if it is a query build to run a executeBatch operation.
+                if ( ( parent = (BashParseInfo) parseInfoModelForBatchedQuery.get()) != null
+                        && parent.originalParseInfo != null // the original parseInfo is not null, 
+                                                            // it is possible to quickly build this object.
+                        && parent.batchedQuery == sql ) {  // the same object
+
+                    ParseInfo original = parent.originalParseInfo;
+                    
+                    
+                    this.firstStmtChar = original.firstStmtChar;
+                    this.foundLimitClause = original.foundLimitClause;
+                    this.foundLoadData = original.foundLoadData;
+                    this.lastUsed = System.currentTimeMillis();
+                    this.statementStartPos = original.statementStartPos;
+                    this.isOnDuplicateKeyUpdateWithArgs = original.isOnDuplicateKeyUpdateWithArgs;
+                    
+                    // If it is the same query
+                    if (parent.batchSize == 1) {
+                        this.onDuplicateKeyUpdateIndex = original.onDuplicateKeyUpdateIndex;
+                        this.statementLength = original.statementLength;
+                        this.firstValue = original.firstValue;
+                        this.lastParmEnd = original.lastParmEnd;
+                        this.staticSql = original.staticSql;   
+                        return;
+                    }
+                    
+                    this.statementLength = sql.length();
+                    this.onDuplicateKeyUpdateIndex = original.onDuplicateKeyUpdateIndex;
+                    
+                    int staticSqlSize = 1 + ((original.staticSql.length - 1) * parent.batchSize);
+                    
+                    this.staticSql = new byte[staticSqlSize][];
+                    
+                    this.staticSql[0] = original.staticSql[0];
+                    this.staticSql[staticSqlSize - 1] = original.staticSql[original.staticSql.length - 1];
+                     
+                    StringBuffer sb = new StringBuffer(sql.substring(sql.length() 
+                            - original.statementLength
+                            + original.lastParmEnd ));
+                    
+                    // The query start by #B# and the number of batch.
+                    int delta = BATCH_TAG.length() + String.valueOf(parent.batchSize).length();
+                    
+                    
+                    // It is a query without '?'
+                    if (original.staticSql.length == 1 ) {
+                        this.onDuplicateKeyUpdateIndex = original.onDuplicateKeyUpdateIndex;
+                        this.firstValue = original.firstValue;
+                        this.lastParmEnd = original.lastParmEnd;
+                        this.staticSql = new byte[1][];
+                        
+                        if (parent.startClausePosition > 0) {
+                        	// It is a insert query.
+                        	
+                        	if (this.onDuplicateKeyUpdateIndex > 0) {                        		
+                        		sb.setLength(sb.length() - original.statementLength
+                        				+ original.onDuplicateKeyUpdateIndex);
+                        		
+                        		for (int i = 1; i < parent.batchSize; ++i) {                        		
+                        			sb.append(',');
+                        			sb.append(sql.substring(parent.startClausePosition + delta, original.onDuplicateKeyUpdateIndex + delta));
+                        		}
+                        		
+                        		this.onDuplicateKeyUpdateIndex = sql.length()
+                        		- (original.statementLength - original.onDuplicateKeyUpdateIndex);
+                        		
+                        		sb.append(sql.substring(original.onDuplicateKeyUpdateIndex + delta));                        		
+                        	} else {                        		
+                        		for (int i = 1; i < parent.batchSize; ++i) {                        		
+                        			sb.append(',');
+                        			sb.append(sql.substring(parent.startClausePosition + delta, original.firstValue + delta));
+                        		}
+                        	}
+                        	
+                        } else {
+                        	// If it's not a insert query it is PreparedBatchAsMultiStatement. 
+                        	// Example: UPDATE ....;UPDATE .....
+                        	
+                        	for (int i = 1; i < parent.batchSize; ++i) {
+                        		sb.append(';');
+                        		sb.append(sql.substring(delta, original.firstValue + delta));
+                        	}
+                        }   
+                        
+                        this.statementLength = sb.length();
+                        this.staticSql[0] = sb.toString().getBytes();
+                        return;
+                    }
 				
+                    if (parent.startClausePosition > 0) {
+                    	// It is a insert query.
+                        if (this.onDuplicateKeyUpdateIndex > 0) {
+                            this.onDuplicateKeyUpdateIndex = sql.length()
+                                    - (original.statementLength - original.onDuplicateKeyUpdateIndex);
+                            
+                            sb.setLength(sb.length() - original.statementLength
+                                    + original.onDuplicateKeyUpdateIndex);
+                        }
+                        sb.append(',');
+                        sb.append(sql.substring(parent.startClausePosition + delta, original.firstValue + delta));
+                    } else {
+                        // If it's not a insert query it is PreparedBatchAsMultiStatement. 
+                        // Example: UPDATE ....;UPDATE .....
+                        sb.append(';');
+                        sb.append(sql.substring(delta, original.firstValue + delta));
+                    }   
+                    
+                    byte[] midle = sb.toString().getBytes();
+                    int index = 0;
+                    for (int i = 0; i < parent.batchSize; ++i) {
+                        if (i > 0) {
+                            this.staticSql[++index] = midle;
+                        }
+                        for (int j = 1; j <= original.staticSql.length - 2; ++j) {
+                            this.staticSql[++index] = original.staticSql[j];
+                        }
+                    }
+                    return;
+                    
+                }
+
+                this.onDuplicateKeyUpdateIndex = containsOnDuplicateKeyInString(sql);
+                this.isOnDuplicateKeyUpdateWithArgs = this.onDuplicateKeyUpdateIndex == -1;
 	         this.lastUsed = System.currentTimeMillis();
 
+                this.statementStartPos = findStartOfStatement(sql);
+                this.statementLength = sql.length();
+                
+
 				String quotedIdentifierString = dbmd.getIdentifierQuoteString();
 
 				char quotedIdentifierChar = 0;
@@ -213,13 +345,11 @@
 					quotedIdentifierChar = quotedIdentifierString.charAt(0);
 				}
 
-				this.statementLength = sql.length();
 
 				ArrayList endpointList = new ArrayList();
 				boolean inQuotes = false;
 				char quoteChar = 0;
 				boolean inQuotedId = false;
-				int lastParmEnd = 0;
 				int i;
 
 				int stopLookingForLimitClause = this.statementLength - 5;
@@ -321,6 +451,11 @@
 					if ((c == '?') && !inQuotes && !inQuotedId) {
 						endpointList.add(new int[] { lastParmEnd, i });
 						lastParmEnd = i + 1;
+                        if (!this.isOnDuplicateKeyUpdateWithArgs //if false there are a on duplicate key information.
+                                                                 // or there are already set to true by this check.
+                             && i > this.onDuplicateKeyUpdateIndex ) {
+                            this.isOnDuplicateKeyUpdateWithArgs = true;
+                        }
 					}
 
 					if (!inQuotes && (i < stopLookingForLimitClause)) {
@@ -357,6 +492,7 @@
 				}
 
 				endpointList.add(new int[] { lastParmEnd, this.statementLength });
+                this.firstValue = ((int[]) endpointList.get(0))[1];
 				this.staticSql = new byte[endpointList.size()][];
 				char[] asCharArray = sql.toCharArray();
 
@@ -402,11 +538,48 @@
 		}
 	}
 
+    class BashParseInfo {
+        
+        ParseInfo originalParseInfo = null;
+        int batchSize = 0;
+        int startClausePosition = -1;
+        int valuesClauseLength = -1;
+        String batchedQuery;
+        
+        
+        /**
+         * 
+         * It is a container of informations to easily and quickly build a ParseInfo object.
+         * It is used with rewriteBatchedStatements option at true.
+         * 
+         * @param parseInfo The parseInfo of the original PrepareStatement.
+         * @param numValuesPerBatch The number/size of the batch.
+         * @param query The sql of the batched query.  
+         * @param startClausePosition if value is upper to zero then the query is a insert query and is the first position of '('.
+         * @param valuesClauseLength The length of the clause '( ?, ?, ...)'.
+         */
+        public BashParseInfo(ParseInfo parseInfo,
+                             int numValuesPerBatch,
+                             String query, 
+                             int startClausePosition, 
+                             int valuesClauseLength) {
+            this.originalParseInfo = parseInfo;
+            this.batchSize = numValuesPerBatch;
+            this.batchedQuery = query;
+            this.startClausePosition = startClausePosition;
+            this.valuesClauseLength = valuesClauseLength;
+        }      
+    }
+
 	private final static byte[] HEX_DIGITS = new byte[] { (byte) '0',
 			(byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
 			(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
 			(byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' };
 
+    private final static ThreadLocal parseInfoModelForBatchedQuery = new ThreadLocal();
+    
+    private final static String BATCH_TAG = "#B#";
+
 	/**
 	 * Reads length bytes from reader into buf. Blocks until enough input is
 	 * available
@@ -507,6 +680,8 @@
 
 	protected String batchedValuesClause;
 
+    protected int batchedFirstClause = -1;
+
 	/** Where does the statement text actually start? */
 	
 	private int statementAfterCommentsPos;
@@ -1090,6 +1265,8 @@
 				clearWarnings();
 
 				if (!this.batchHasPlainStatements
+                        && ( this.batchedArgs == null 
+                            ||  this.batchedArgs.size() > 3 ) /* cost of option setting rt-wise */
 						&& this.connection.getRewriteBatchedStatements()) {
 					
 					
@@ -1098,9 +1275,7 @@
 					}
 					
 					if (this.connection.versionMeetsMinimum(4, 1, 0) 
-							&& !this.batchHasPlainStatements
-							&& this.batchedArgs != null 
-							&& this.batchedArgs.size() > 3 /* cost of option setting rt-wise */) {
+                            && !this.batchHasPlainStatements) {
 						return executePreparedBatchAsMultiStatement(batchTimeout);
 					}
 				}
@@ -1114,20 +1289,13 @@
 
 	public synchronized boolean canRewriteAsMultivalueInsertStatement() {
 		if (!this.hasCheckedForRewrite) {
-			// Needs to be INSERT, can't have INSERT ... SELECT or
-			// INSERT ... ON DUPLICATE KEY UPDATE
-			//
-			// We're not smart enough to re-write to
-			//
-			//    INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)
-			//    ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);
-			//
-			// (yet)
+            // Needs to be INSERT, can't have INSERT ... SELECT
+                      
 
 			this.canRewrite = StringUtils.startsWithIgnoreCaseAndWs(
 					this.originalSql, "INSERT", this.statementAfterCommentsPos) 
 			&& StringUtils.indexOfIgnoreCaseRespectMarker(this.statementAfterCommentsPos, this.originalSql, "SELECT", "\"'`", "\"'`", false) == -1 
-			&& !containsOnDuplicateKeyUpdateInSQL();
+            && ( !containsOnDuplicateKeyUpdateInSQL() || !containsOnDuplicateKeyUpdateInSQLWithArgs());
 			
 			this.hasCheckedForRewrite = true;
 		}
@@ -1180,19 +1348,25 @@
 				int updateCountCounter = 0;
 				int[] updateCounts = new int[numBatchedArgs];
 				SQLException sqlEx = null;
-				
+                String newQuery = null;
 				try {
 					if (!multiQueriesEnabled) {
 						locallyScopedConn.getIO().enableMultiQueries();
 					}
 					
+                    newQuery = generateMultiStatementForBatch(numValuesPerBatch);
+                    // Add the information to easily and quickly build a new ParseInfo from the current parseInfo
+                    parseInfoModelForBatchedQuery.set(new BashParseInfo(this.parseInfo,
+                                                                        numValuesPerBatch,
+                                                                        newQuery,
+                                                                        0,
+                                                                        0 ));
+                    
 					if (this.retrieveGeneratedKeys) {
-						batchedStatement = locallyScopedConn.prepareStatement(
-								generateMultiStatementForBatch(numValuesPerBatch),
+                        batchedStatement = locallyScopedConn.prepareStatement(newQuery,
 								RETURN_GENERATED_KEYS);
 					} else {
-						batchedStatement = locallyScopedConn
-								.prepareStatement(generateMultiStatementForBatch(numValuesPerBatch));
+                        batchedStatement = locallyScopedConn.prepareStatement(newQuery);
 					}
 
 					if (locallyScopedConn.getEnableQueryTimeouts() &&
@@ -1256,13 +1430,19 @@
 				try {
 					if (numValuesPerBatch > 0) {
 			
+                        newQuery = generateMultiStatementForBatch(numValuesPerBatch);
+                        // Add the information to easily and quickly build a new ParseInfo from the current parseInfo
+                        parseInfoModelForBatchedQuery.set(new BashParseInfo(this.parseInfo,
+                                                                            numValuesPerBatch,
+                                                                            newQuery,
+                                                                            0,
+                                                                            0 ));
+                        
 						if (this.retrieveGeneratedKeys) {
-							batchedStatement = locallyScopedConn.prepareStatement(
-									generateMultiStatementForBatch(numValuesPerBatch),
+                            batchedStatement = locallyScopedConn.prepareStatement(newQuery,
 								RETURN_GENERATED_KEYS);
 						} else {
-							batchedStatement = locallyScopedConn.prepareStatement(
-									generateMultiStatementForBatch(numValuesPerBatch));
+                            batchedStatement = locallyScopedConn.prepareStatement(newQuery);
 						}
 						
 						if (timeoutTask != null) {
@@ -1313,6 +1493,8 @@
 					}
 				}
 			} finally {
+            	// It is to reduce the memory in multi threading usage.
+                parseInfoModelForBatchedQuery.remove();
 				if (timeoutTask != null) {
 					timeoutTask.cancel();
 				}
@@ -1329,6 +1511,13 @@
 	}
 	
 	private String generateMultiStatementForBatch(int numBatches) {
+        if (this.parseInfo != null) {
+        	//It is to have "name" if the query parseInfo is set into a cache.
+            return ( new StringBuffer(this.originalSql.length() + BATCH_TAG.length() + 5)
+                            .append(BATCH_TAG)
+                            .append(numBatches)
+                            .append(this.originalSql)).toString();
+        }
 		StringBuffer newStatementSql = new StringBuffer((this.originalSql
 				.length() + 1) * numBatches);
 				
@@ -1386,17 +1575,21 @@
 		for (int i = 0; i < this.batchedArgs.size(); i++) {
 			updateCounts[i] = 1;
 		}
-		
+        String newQuery = null;
 		try {
 			try {
+                newQuery = generateBatchedInsertSQL(valuesClause, numValuesPerBatch);
+                // Add the information to easily and quickly build a new ParseInfo from the current parseInfo
+                parseInfoModelForBatchedQuery.set(new BashParseInfo(this.parseInfo,
+                                                                    numValuesPerBatch,
+                                                                    newQuery,
+                                                                    this.batchedFirstClause,
+                                                                    valuesClause.length() ));
+                
 				if (this.retrieveGeneratedKeys) {
-					batchedStatement = locallyScopedConn.prepareStatement(
-							generateBatchedInsertSQL(valuesClause,
-									numValuesPerBatch), RETURN_GENERATED_KEYS);
-				} else {
-					batchedStatement = locallyScopedConn
-							.prepareStatement(generateBatchedInsertSQL(
-									valuesClause, numValuesPerBatch));
+                    batchedStatement = locallyScopedConn.prepareStatement( newQuery, RETURN_GENERATED_KEYS);
+                } else {
+                    batchedStatement = locallyScopedConn.prepareStatement(newQuery);
 				}
 
 				if (this.connection.getEnableQueryTimeouts()
@@ -1458,15 +1651,18 @@
 			try {
 				if (numValuesPerBatch > 0) {
 
+                    newQuery = generateBatchedInsertSQL(valuesClause, numValuesPerBatch);
+                    // Add the information to easily and quickly build a new ParseInfo from the current parseInfo
+                    parseInfoModelForBatchedQuery.set(new BashParseInfo(this.parseInfo,
+                                                                        numValuesPerBatch,
+                                                                        newQuery,
+                                                                        this.batchedFirstClause,
+                                                                        valuesClause.length() ));
+                    
 					if (this.retrieveGeneratedKeys) {
-						batchedStatement = locallyScopedConn.prepareStatement(
-								generateBatchedInsertSQL(valuesClause,
-										numValuesPerBatch),
-								RETURN_GENERATED_KEYS);
+                        batchedStatement = locallyScopedConn.prepareStatement( newQuery, RETURN_GENERATED_KEYS);
 					} else {
-						batchedStatement = locallyScopedConn
-								.prepareStatement(generateBatchedInsertSQL(
-										valuesClause, numValuesPerBatch));
+                        batchedStatement = locallyScopedConn.prepareStatement(newQuery);
 					}
 
 					if (timeoutTask != null) {
@@ -1504,6 +1700,8 @@
 				}
 			}
 		} finally {
+        	// It is to reduce the memory in multi threading usage.
+            parseInfoModelForBatchedQuery.remove();
 			if (timeoutTask != null) {
 				timeoutTask.cancel();
 			}
@@ -2081,7 +2279,15 @@
 	}
 
 	protected boolean containsOnDuplicateKeyUpdateInSQL() {
-		return this.parseInfo.isOnDuplicateKeyUpdate;
+        return this.parseInfo.onDuplicateKeyUpdateIndex != -1;
+    }
+    
+    protected int indexOfOnDuplicateKeyUpdateInSQL() {
+        return this.parseInfo.onDuplicateKeyUpdateIndex;
+    }
+    
+    protected boolean containsOnDuplicateKeyUpdateInSQLWithArgs() {
+        return this.parseInfo.isOnDuplicateKeyUpdateWithArgs;
 	}
 
 	private String extractValuesClause() throws SQLException {
@@ -2105,23 +2311,31 @@
 				return null;
 			}
 	
-			int indexOfFirstParen = this.originalSql
-					.indexOf('(', indexOfValues + 7);
+            this.batchedFirstClause = this.originalSql.indexOf('(', indexOfValues + 7);
 	
-			if (indexOfFirstParen == -1) {
+            if (this.batchedFirstClause == -1) {
 				return null;
 			}
 	
-			int indexOfLastParen = this.originalSql.lastIndexOf(')');
+            int indexOfLastParen = (containsOnDuplicateKeyUpdateInSQL() && !containsOnDuplicateKeyUpdateInSQLWithArgs()) ?
+                                                     this.originalSql.lastIndexOf(')', (indexOfOnDuplicateKeyUpdateInSQL()))
+                                                     : this.originalSql.lastIndexOf(')');
 	
 			if (indexOfLastParen == -1) {
 				return null;
 			}
 	
-			this.batchedValuesClause = this.originalSql.substring(indexOfFirstParen,
+            if (this.parseInfo != null) {
+            	//It is just to have the length, it is not used, the parseInfo data is used to build the query. 
+            	this.batchedValuesClause = new StringBuffer(indexOfLastParen + 1 - this.batchedFirstClause).toString();
+            } else {            	
+            	this.batchedValuesClause = this.originalSql.substring(this.batchedFirstClause,
 					indexOfLastParen + 1);
 		}
 			
+    
+        }
+            
 		return this.batchedValuesClause;
 	}
 
@@ -2233,17 +2447,31 @@
 	}
 
 	private String generateBatchedInsertSQL(String valuesClause, int numBatches) {
+        if (this.parseInfo != null) {
+        	//It is to have "name" if the query parseInfo is set into a cache.
+            return ( new StringBuffer(this.originalSql.length() + BATCH_TAG.length() + 5)
+                           .append(BATCH_TAG)
+                           .append(numBatches)
+                           .append(this.originalSql)).toString();
+        }
 		StringBuffer newStatementSql = new StringBuffer(this.originalSql
 				.length()
 				+ (numBatches * (valuesClause.length() + 1)));
 
 		newStatementSql.append(this.originalSql);
+        if ((containsOnDuplicateKeyUpdateInSQL() && !containsOnDuplicateKeyUpdateInSQLWithArgs())) {
+            newStatementSql.setLength(indexOfOnDuplicateKeyUpdateInSQL() + 1);
+        }
 
 		for (int i = 0; i < numBatches - 1; i++) {
 			newStatementSql.append(',');
 			newStatementSql.append(valuesClause);
 		}
 
+        if ((containsOnDuplicateKeyUpdateInSQL() && !containsOnDuplicateKeyUpdateInSQLWithArgs())) {
+            newStatementSql.append(this.originalSql.substring(indexOfOnDuplicateKeyUpdateInSQL()));
+        }
+        
 		return newStatementSql.toString();
 	}
 
@@ -5189,8 +5417,8 @@
 		return count;
 	}
 	
-	protected boolean containsOnDuplicateKeyInString(String sql) {
+    protected int containsOnDuplicateKeyInString(String sql) {
 		return StringUtils.indexOfIgnoreCaseRespectMarker(0, 
-				sql, " ON DUPLICATE KEY UPDATE ", "\"'`", "\"'`", !this.connection.isNoBackslashEscapesSet()) != -1;
+                sql, " ON DUPLICATE KEY UPDATE ", "\"'`", "\"'`", !this.connection.isNoBackslashEscapesSet());
 	}
 }
