關於 JDBC 的批量操作 executeBatch() 所引發 sql 語句異常


關於 JDBC 的批量操作 executeBatch() 所引發 sql 語句異常
java.sql.BatchUpdateException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '('1512144017', 'quqiang01' at line 1
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
    at com.mysql.jdbc.Util.getInstance(Util.java:408)
    at com.mysql.jdbc.SQLError.createBatchUpdateException(SQLError.java:1162)
    at com.mysql.jdbc.PreparedStatement.executeBatchedInserts(PreparedStatement.java:1587)
    at com.mysql.jdbc.PreparedStatement.executeBatchInternal(PreparedStatement.java:1253)
    at com.mysql.jdbc.StatementImpl.executeBatch(StatementImpl.java:970)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '('1512144017', 'quqiang01' at line 1
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
    at com.mysql.jdbc.Util.getInstance(Util.java:408)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:943)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3973)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3909)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2487)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858)
    at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2079)
    at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2013)
    at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5104)
    at com.mysql.jdbc.PreparedStatement.executeBatchedInserts(PreparedStatement.java:1548)
    ... 5 more

最近在使用 JDBC 的時候, 一個比較坑的細節, 就是關於他里面使用 PreparedStatement 或者 Statement 的 addBatch()/executeBatch() 的具體實現問題;

不要手賤在你傳入的 sql 語句沒末尾加上分號;

具體是這樣:

1 //以一個批量提交的實現工具舉例
 2 // List<List<Object>> lists: 所有數據的list
 3 // List<Object>:單條數據"值"的list
 5 public void batchExecutePstamt(String sql, List < List < Object >> lists) {
 6     try {
 7         connection.setAutoCommit(false);
 8         pstmt = connection.prepareStatement(sql);
 9         if (lists != null && !lists.isEmpty()) {
10             for (List < Object > cList: lists) {
11                 if (cList == null || cList.isEmpty())
12                     continue;
14                 for (int i = 0; i < cList.size(); i++) {
15                     pstmt.setObject(i + 1, cList.get(i));
16                 }
17                 pstmt.addBatch();
18             }
19             log.info(pstmt.toString());
20             pstmt.executeBatch();
21             connection.commit();
22         }
23     } catch (SQLException e) {
24         e.printStackTrace();
25     }
26 }

在這里, 我們傳入大量的需要插入的對象的 List<List<Object>>, 里面的 List<Object> 就是某一條具體的記錄的值;

如果你調用的時候, 在傳入 sql 的時候, 傳入了類似於  insert into `tablename` (name,age) values (?,?); 的 sql 語句
那么他每次 addBatch 的時候就會在
com.mysql.jdbc.PreparedStatement.addBatch() 的創建 com.mysql.jdbc.PreparedStatement.BatchParams 然后放進 batchedArgs 靜態列表里面

1  public void addBatch() throws SQLException {
 2         synchronized (checkClosed().getConnectionMutex()) {
 3             if (this.batchedArgs == null) {
 4                 this.batchedArgs = new ArrayList<Object>();
 5             }
 7             for (int i = 0; i < this.parameterValues.length; i++) {
 8                 checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
 9             }
11             this.batchedArgs.add(new BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
12         }
13     }
addBatch()
1  public class BatchParams {
 2         public boolean[] isNull = null;
 4         public boolean[] isStream = null;
 6         public InputStream[] parameterStreams = null;
 8         public byte[][] parameterStrings = null;
10         public int[] streamLengths = null;
12         BatchParams(byte[][] strings, InputStream[] streams, boolean[] isStreamFlags, int[] lengths, boolean[] isNullFlags) {
13             //
14             // Make copies
15             //
16             this.parameterStrings = new byte[strings.length][];
17             this.parameterStreams = new InputStream[streams.length];
18             this.isStream = new boolean[isStreamFlags.length];
19             this.streamLengths = new int[lengths.length];
20             this.isNull = new boolean[isNullFlags.length];
21             System.arraycopy(strings, 0, this.parameterStrings, 0, strings.length);
22             System.arraycopy(streams, 0, this.parameterStreams, 0, streams.length);
23             System.arraycopy(isStreamFlags, 0, this.isStream, 0, isStreamFlags.length);
24             System.arraycopy(lengths, 0, this.streamLengths, 0, lengths.length);
25             System.arraycopy(isNullFlags, 0, this.isNull, 0, isNullFlags.length);
26         }
27     }
29 PreparedStatement batchedStatement = null;
BatchParams

然后會在最后使用 executeBatch() 的時候處理 batchedArgs 中的 數據

處理的步驟為:
>> com.mysql.jdbc.StatementImpl.executeBatch()
>> com.mysql.jdbc.PreparedStatement.executeBatchInternal()
這里回去判斷你設置的一些參數, 比如 connection.getRewriteBatchedStatements(), 這個有助於批量數據處理的參數, 需要在 jdbc.url 連接中設置 rewriteBatchedStatements = true, 不過好像只針對於某個版本 (5.2 ? 不確定) 以后的數據操作性能有幫助.
在這里會進入一個將多個 PreparedStatement 轉化為 BLUK 模式的一條語句;(bluk 模式不知道的請自行百度)

>> com.mysql.jdbc.PreparedStatement.executeBatchedInserts(int)


這里面有一段源碼:

1 PreparedStatement batchedStatement = null;
 3 int batchedParamIndex = 1;
 4 long updateCountRunningTotal = 0;
 5 int numberToExecuteAsMultiValue = 0;
 6 int batchCounter = 0;
 7 CancelTask timeoutTask = null;
 8 SQLException sqlEx = null;
10 long[]updateCounts = new long[numBatchedArgs];
11 //上面不多說
13 try {
14     //這句會把初始化我們傳進來的sql
15     //我們傳進來的是類似於 insert into  tablename (columns…) values (?,?...)這種
16     //到這里相當於初始化成 INSERT INTO tablename` (`name`, `age`) values (** NOT SPECIFIED **, ** NOT SPECIFIED **);
17     //然后他會根據順序傳進我們上面所傳進的多個已經賦值的PreparedStatement,
18     batchedStatement = /* FIXME -if we ever care about folks proxying our MySQLConnection */
19         prepareBatchedInsertSQL(locallyScopedConn, numValuesPerBatch);
21     if (locallyScopedConn.getEnableQueryTimeouts() && batchTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
22         timeoutTask = new CancelTask(batchedStatement);
23         locallyScopedConn.getCancelTimer().schedule(timeoutTask, batchTimeout);
24     }
26     if (numBatchedArgs < numValuesPerBatch) {
27         numberToExecuteAsMultiValue = numBatchedArgs;
28     } else {
29         numberToExecuteAsMultiValue = numBatchedArgs / numValuesPerBatch;
30     }
32     int numberArgsToExecute = numberToExecuteAsMultiValue * numValuesPerBatch;
34     for (int i = 0; i < numberArgsToExecute; i++) {
35         if (i != 0 && i % numValuesPerBatch == 0) {
36             try {
37                 updateCountRunningTotal += batchedStatement.executeLargeUpdate();
38             } catch (SQLException ex) {
39                 sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex);
40             }
42             getBatchedGeneratedKeys(batchedStatement);
43             batchedStatement.clearParameters();
44             batchedParamIndex = 1;
46         }
47         //@see 這里是關鍵
48         //@see 如果還有后續的PreparedStatement,根據后面的一個參數,那么他就會將batchedStatement設置為:
49         // INSERT INTO tablename` (`name`, `age`) values("名字1",'23'), (** NOT SPECIFIED **, ** NOT SPECIFIED **);
51         //@see 問題就在這里
52         //@see 如果你傳入的sql :" insert into  tablename (columns…) values (?,?...);"是這樣的,那么生成的單條語句以及單個PreparedStatement沒有任何問題,你拿到數據庫執行去也OK
53         //@see 但是他現在要給你轉為BLUK模式的,如果你結尾帶了分號,語句就會變成:
54         // INSERT INTO tablename` (`name`, `age`) values("名字1",'23');, (** NOT SPECIFIED **, ** NOT SPECIFIED **);然后等待下一次填充參數
55         //@see 多的這個分號就是導致sql語句錯誤的原因
58         //@see 有興趣可以進源碼的這個方法看看
59         batchedParamIndex = setOneBatchedParameterSet(batchedStatement, batchedParamIndex, this.batchedArgs.get(batchCounter++));
60     }
62     try {
63         updateCountRunningTotal += batchedStatement.executeLargeUpdate();
64     } catch (SQLException ex) {
65         sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex);
66     }
68     getBatchedGeneratedKeys(batchedStatement);
70     numValuesPerBatch = numBatchedArgs - batchCounter;
71 }
72 finally {
73     if (batchedStatement != null) {
74         batchedStatement.close();
75         batchedStatement = null;
76     }
77 }
全文完
本文由 簡悅 SimpRead 轉碼,用以提升閱讀體驗, 原文地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM