天天看點

JDBC基礎 JDBC基礎

标簽: Java與存儲

JDBC(Java Database Connectivity)代表Java程式設計語言與資料庫連接配接的标準API,然而JDBC隻是接口,JDBC驅動才是真正的接口實作,沒有驅動無法完成資料庫連接配接. 每個資料庫廠商都有自己的驅動,用來連接配接自己公司的資料庫(如Oricle, MySQL, DB2, MS SQLServer).

下面我們以MySQL為例,JDBC程式設計大緻步驟如下:

注意: 需要在pom.xml中添加如下MySQL驅動:

注: <code>ResultSet</code>參數<code>columnIndex</code>索引從1開始,而不是0!

JDBC規定: 驅動類在被加載時,需要<code>主動</code>把自己注冊到<code>DriverManger</code>中:

com.mysql.jdbc.Driver

代碼顯示:隻要去加載<code>com.mysql.jdbc.Driver</code>類那麼就會執行<code>static</code>塊, 進而把<code>com.mysql.jdbc.Driver</code>注冊到<code>DriverManager</code>中.

<code>java.sql.DriverManager</code>是用于管理JDBC驅動的服務類,其主要功能是擷取<code>Connection</code>對象:

1. <code>static Connection getConnection(String url, Properties info)</code>

2. <code>static Connection getConnection(String url, String user, String password)</code>

另: 還可以在擷取<code>Connection</code>的URL中設定參數,如: jdbc:mysql://host:port/database?useUnicode=true&amp;characterEncoding=UTF8 <code>useUnicode=true&amp;characterEncoding=UTF8</code>指定連接配接資料庫的過程中使用Unicode字元集/UTF-8編碼;

<code>java.sql.Connection</code>代表資料庫連接配接,每個<code>Connection</code>代表一個實體連接配接會話, 該接口提供如下建立<code>Statement</code>的方法, 隻有擷取<code>Statement</code>之後才可執行SQL語句:

方法

描述

<code>Statement createStatement()</code>

Creates a Statement object for sending SQL statements to the database.

<code>PreparedStatement prepareStatement(String sql)</code>

Creates a PreparedStatement object for sending parameterized SQL statements to the database.

<code>CallableStatement prepareCall(String sql)</code>

Creates a CallableStatement object for calling database stored procedures.

其中<code>Connection</code>還提供了如下控制事務/儲存點的方法:

<code>Savepoint setSavepoint(String name)</code>

Creates a savepoint with the given name in the current transaction and returns the new Savepoint object that represents it.

<code>void setTransactionIsolation(int level)</code>

Attempts to change the transaction isolation level(事務隔離級别) for this Connection object to the one given.

<code>void setAutoCommit(boolean autoCommit)</code>

Sets this connection’s auto-commit mode to the given state.

<code>void rollback()</code>

Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object.

<code>void rollback(Savepoint savepoint)</code>

Undoes all changes made after the given Savepoint object was set.

<code>void commit()</code>

Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object.

由于擷取<code>Connection</code>的步驟單一,每次可能隻是加載的參數不同,是以我們可以将擷取<code>Connection</code>的操作封裝成一個方法,并使其從配置檔案中加載配置:

配置檔案形式

ConnectionManger

SQLUtil

前面通過<code>DriverManger</code>獲得<code>Connection</code>, 一個<code>Connection</code>對應一個實際的實體連接配接,每次操作都需要打開實體連接配接, 使用完後立即關閉;這樣頻繁的打開/關閉連接配接會造成不必要的資料庫系統性能消耗.

資料庫連接配接池提供的解決方案是:當應用啟動時,主動建立足夠的資料庫連接配接,并将這些連接配接組織成連接配接池,每次請求連接配接時,無須重新打開連接配接,而是從池中取出已有連接配接,使用完後并不實際關閉連接配接,而是歸還給池.

JDBC資料庫連接配接池使用<code>javax.sql.DataSource</code>表示, <code>DataSource</code>隻是一個接口, 其實作通常由伺服器提供商(如WebLogic, WebShere)或開源組織(如DBCP,C3P0和HikariCP)提供.

資料庫連接配接池的常用參數如下:

資料庫初始連接配接數;

連接配接池最大連接配接數;

連接配接池最小連接配接數;

連接配接池每次增加的容量;

Tomcat預設使用的是DBCP連接配接池,但相比之下,C3P0則比DBCP更勝一籌(Hibernate推薦使用C3P0),C3P0不僅可以自動清理不再使用的<code>Connection</code>, 還可以自動清理<code>Statement</code>/<code>ResultSet</code>, 使用C3P0需要在pom.xml中添加如下依賴:

C3P0還可以使用配置檔案來初始化連接配接池(配置檔案可以是properties/XML, 在此僅介紹XML),C3P0配置檔案名必須為c3p0-config.xml,其放在類路徑下:

這樣, 我們在建立<code>ComboPooledDataSource</code>時就預設加載配置檔案中的配置, 無須手動配置:

C3P0配置檔案可以配置多個連接配接資訊, 并為每個配置命名, 這樣可以友善的通過配置名稱來切換配置資訊:

HikariCP用方法擷取<code>Connection</code>的方法與C3P0大同小異:

附:

2. properties檔案形式如下:

<code>java.sql.Statement</code>可用于執行DDL/DML/DCL語句:

<code>boolean execute(String sql)</code>

Executes the given SQL statement, which may return multiple results.

<code>ResultSet executeQuery(String sql)</code>

Executes the given SQL statement, which returns a single ResultSet object.

<code>int executeUpdate(String sql)</code>

Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.

<code>int[] executeBatch()</code>

Submits a batch of commands to the database for execution and if all commands execute successfully, returns an array of update counts.

Java 1.7還新增了<code>closeOnCompletion()</code>方法,當所有依賴于目前<code>Statement</code>的<code>ResultSet</code>關閉時,該<code>Statement</code>自動關閉.

<code>Statement</code>使用<code>executeUpdate</code>方法執行DDL/DML(不包含<code>select</code>)語句:執行DDL該方法傳回0; 執行DML傳回受影響的記錄數.

DDL

DML

<code>execute</code>方法幾乎可以執行任何SQL語句,但較為繁瑣(除非不清楚SQL語句類型,否則不要使用<code>execute</code>方法).該方法傳回值為<code>boolean</code>,代表執行該SQL語句是否傳回<code>ResultSet</code>,然後<code>Statement</code>提供了如下方法來擷取SQL執行的結果:

<code>ResultSet getResultSet()</code>

Retrieves the current result as a ResultSet object.

<code>int getUpdateCount()</code>

Retrieves the current result as an update count; if the result is a ResultSet object or there are no more results, -1 is returned.

client

<code>PreparedStatement</code>是<code>Statement</code>的子接口, 它可以預編譯SQL語句,編譯後的SQL模闆被存儲在<code>PreparedStatement</code>對象中,每次使用時首先為SQL模闆設值,然後執行該語句(是以使用<code>PreparedStatement</code>效率更高).

建立<code>PreparedStatement</code>需要使用<code>Connection</code>的<code>prepareStatement(String sql)</code>方法,該方法需要傳入SQL模闆,可以包含占位符參數:

<code>PreparedStatement</code>也提供了<code>excute</code>等方法來執行SQL語句, 隻是這些方法無須傳入參數, 因為SQL語句已經存儲在<code>PreparedStatement</code>對象中.

由于執行SQL前需要為SQL模闆傳入參數值,<code>PreparedStatement</code>提供了一系列的<code>setXxx(int parameterIndex, X x)</code>方法;另外,如果不清楚SQL模闆各參數的類型,可以使用<code>setObject(int parameterIndex, Object x)</code>方法傳入參數, 由<code>PreparedStatement</code>來負責類型轉換.

注意: SQL語句的占位符參數隻能代替普通值, 不能代替表名/列名等資料庫對象, 更不能代替INSERT/SELECT等關鍵字.
注: 1. 預設使用<code>PreparedStatement</code>是沒有開啟預編譯功能的,需要在URL中給出<code>useServerPrepStmts=true</code>參數來開啟此功能; 2. 當使用不同的<code>PreparedStatement</code>對象來執行相同SQL語句時,還是會出現編譯兩次的現象,這是因為驅動沒有緩存編譯後的函數key,導緻二次編譯.如果希望緩存編譯後的函數key,那麼就要設定<code>cachePrepStmts=true</code>參數. 3. 另外, 還可以設定預編譯緩存的大小:cachePrepStmts=true&amp;prepStmtCacheSize=50&amp;prepStmtCacheSqlLimit=300` jdbc:mysql://host:port/database?useServerPrepStmts=true&amp;cachePrepStmts=true&amp;prepStmtCacheSize=50&amp;prepStmtCacheSqlLimit=300

在資料庫中建立一個簡單的存儲過程<code>add_pro</code>:

<code>delimiter //</code>會将SQL語句的結束符改為<code>//</code>, 這樣就可以在建立存儲過程時使用<code>;</code>作為分隔符. MySQL預設使用<code>;</code>作為SQL結束符.

調用存儲過程需要使用<code>CallableStatement</code>,可以通過<code>Connection</code>的<code>prepareCall()</code>方法來建立,建立時需要傳入調用存儲過程的SQL語句,形式為:

存儲過程的參數既有入參,也有回參; 入參可通過<code>setXxx(int parameterIndex/String parameterName, X x)</code>方法傳入;回參可以通過調用<code>registerOutParameter(int parameterIndex, int sqlType)</code>來注冊, 經過上面步驟, 就可以調用<code>execute()</code>方法來調用該存儲過程, 執行結束, 則可通過<code>getXxx(int parameterIndex/String parameterName)</code>方法來擷取指定回參的值:

JDBC使用<code>ResultSet</code>封裝查詢結果,然後通過<code>ResultSet</code>的記錄指針來讀取/更新記錄.并提供了<code>ResultSetMetaDate</code>來獲得<code>ResultSet</code>對象的中繼資料資訊.

<code>java.sql.ResultSet</code>是結果集對象,可以通過列索引/列名來讀/寫資料, 它提供了如下常用方法來移動記錄指針:

<code>boolean next()</code>

Moves the cursor froward one row from its current position.

<code>boolean previous()</code>

Moves the cursor to the previous row in this ResultSet object.

<code>boolean first()</code>

Moves the cursor to the first row in this ResultSet object.

<code>boolean last()</code>

Moves the cursor to the last row in this ResultSet object.

<code>void beforeFirst()</code>

Moves the cursor to the front of this ResultSet object, just before the first row.

<code>void afterLast()</code>

Moves the cursor to the end of this ResultSet object, just after the last row.

<code>boolean absolute(int row)</code>

Moves the cursor to the given row number in this ResultSet object.

<code>boolean relative(int rows)</code>

Moves the cursor a relative number of rows, either positive or negative.

當把記錄指針定位到指定行後, <code>ResultSet</code>可通過<code>getXxx(int columnIndex/String columnLabel)</code>方法來獲得指定類型值.或使用<code>&lt;T&gt; T getObject(int columnIndex/String columnLabel, Class&lt;T&gt; type)</code>來擷取任意類型值.

以預設方式打開的<code>ResultSet</code>是不可更新的,獲得可更新的<code>ResultSet</code>,需要在建立<code>Statement</code>/<code>PreparedStatement</code>時傳入如下兩個參數:

resultSetType: 控制<code>ResultSet</code>可移動方向

<code>ResultSet.TYPE_FORWARD_ONLY</code>

The constant indicating the type for a ResultSet object whose cursor may move only forward.

<code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>

The constant indicating the type for a ResultSet object that is scrollable but generally not sensitive to changes to the data that underlies the ResultSet.

<code>ResultSet.TYPE_SCROLL_SENSITIVE</code>

The constant indicating the type for a ResultSet object that is scrollable and generally sensitive to changes to the data that underlies the ResultSet.

resultSetConcurrency: 控制<code>ResultSet</code>的讀/寫并發類型

<code>ResultSet.CONCUR_READ_ONLY</code>

The constant indicating the concurrency mode for a ResultSet object that may NOT be updated.

<code>ResultSet.CONCUR_UPDATABLE</code>

The constant indicating the concurrency mode for a ResultSet object that may be updated.

另外可更新的結果集還必須滿足如下條件: 1) 所有資料都來自一個表; 2)選出的資料集必須包含主鍵列;

這樣, 擷取的<code>ResultSet</code>就是可更新/可滾動的, 程式可通過調用<code>ResultSet</code>的<code>updateXxx(int columnIndex/String columnLabel, X x)</code>方法來修改記錄指針所指向的值, 最後調用<code>updateRow()</code>來送出修改.

SQLClient

<code>ResultSet</code>提供了<code>getMetaData()</code>方法來擷取<code>ResultSetMetaData</code>以分析關于<code>ResultSet</code>的描述資訊(前面我們已經使用<code>ResultSetMetaData</code>來擷取結果集的列數以及列名):

ResultSetMetaData方法

<code>int getColumnCount()</code>

Returns the number of columns in this ResultSet object.

<code>String getColumnLabel(int column)</code>

Gets the designated column’s suggested title for use in printouts and displays.

<code>String getColumnName(int column)</code>

Get the designated column’s name.

<code>int getColumnType(int column)</code>

Retrieves the designated column’s SQL type.

<code>String getColumnTypeName(int column)</code>

Retrieves the designated column’s database-specific type name.

<code>boolean isAutoIncrement(int column)</code>

Indicates whether the designated column is automatically numbered.

analyseResult

注: 因為擷取<code>ResultSetMetaData</code>隻能通過<code>ResultSet</code>的<code>getMetaData()</code>方法,是以使用<code>ResultSetMetaData</code>就需要一定的運作時開銷;是以如果在編碼過程中已經知道列數/列名/類型等資訊, 就沒有再用<code>ResultSetMetaData</code>了.

<code>javax.sql.RowSet</code>繼承自<code>ResultSet</code>, <code>RowSet</code>的子接口有<code>CachedRowSet</code>, <code>FilteredRowSet</code>, <code>JdbcRowSet</code>, <code>JoinRowSet</code>, <code>WebRowSet</code>, 其中隻有<code>JdbcRowSet</code>需要保持與資料庫的連接配接, 其他都是離線<code>RowSet</code>.

與<code>ResultSet</code>相比, <code>RowSet</code>預設就是可滾動/可更新/可序列化的結果集,是以可以作為JavaBean使用(比如在網絡上傳輸,用于同步兩端資料).

而對于離線<code>RowSet</code>, 程式在建立<code>RowSet</code>時已把資料從底層資料庫讀取到了記憶體,是以可以充分發揮記憶體的優勢,降低資料庫Server的負載,提高性能.

<code>RowSet</code>接口提供了如下常用方法:

<code>void setUrl(String url)</code>

Sets the URL this RowSet object will use when it uses the DriverManager to create a connection.

<code>void setUsername(String name)</code>

Sets the username property for this RowSet object to the given String.

<code>void setPassword(String password)</code>

Sets the database password for this RowSet object to the given String.

<code>void setCommand(String cmd)</code>

Sets this RowSet object’s command property to the given SQL query.

<code>void setXxx(String parameterName/int parameterIndex, X x)</code>

<code>void execute()</code>

Fills this RowSet object with data.

Java 1.7為<code>RowSet</code>提供了<code>RowSetProvider</code>與<code>RowSetFactory</code>工具, <code>RowSetProvider</code>負載建立<code>RowSetFactory</code>, <code>RowSetFactory</code>提供如下方法建立<code>RowSet</code>執行個體:

<code>CachedRowSet createCachedRowSet()</code>

Creates a new instance of a CachedRowSet.

<code>FilteredRowSet createFilteredRowSet()</code>

Creates a new instance of a FilteredRowSet.

<code>JdbcRowSet createJdbcRowSet()</code>

Creates a new instance of a JdbcRowSet.

<code>JoinRowSet createJoinRowSet()</code>

Creates a new instance of a JoinRowSet.

<code>WebRowSet createWebRowSet()</code>

Creates a new instance of a WebRowSet.

JdbcRowSetClient

可将初始化<code>RowSet</code>操作封裝成一個方法:

前面查詢得到<code>ResultSet</code>後必須立即處理,否則一旦<code>Connection</code>連接配接關閉,再去讀/寫<code>ResultSet</code>就會引發異常.而離線<code>RowSet</code>會直接将資料讀入記憶體,封裝成<code>RowSet</code>對象,<code>CachedRowSet</code>是所有離線<code>RowSet</code>的父接口,提供了如下實用方法:

<code>void populate(ResultSet data)</code>

Populates this CachedRowSet object with data from the given ResultSet object.

<code>void acceptChanges()</code>

Propagates row update, insert and delete changes made to this CachedRowSet object to the underlying data source.

<code>void acceptChanges(Connection con)</code>

Propagates all row update, insert and delete changes to the data source backing this CachedRowSet object using the specified Connection object to establish a connection to the data source.

CachedRowSetClient

由于<code>CachedRowSet</code>會将底層資料庫資料直接裝載到記憶體,是以如果SQL查詢傳回資料過大,可能會導緻記憶體溢出.為了解決這個問題,<code>CachedRowSet</code>提供了分頁功能: 一次隻裝載<code>ResultSet</code>的部分記錄,這樣可以避免<code>CachedRowSet</code>占用記憶體過大.

<code>void populate(ResultSet rs, int startRow)</code>

<code>void setPageSize(int size)</code>

Sets the CachedRowSet object’s page-size.

<code>boolean nextPage()</code>

Increments the current page of the CachedRowSet.

<code>boolean previousPage()</code>

Decrements the current page of the CachedRowSet.

事務是由一步/幾步資料庫操作序列組成的邏輯執行單元, 這些操作要麼全部執行, 要麼全部不執行.

原子性(A: Atomicity): 事務是不可再分的最小邏輯執行體;

一緻性(C: Consistency): 事務執行的結果, 必須使資料庫從一個一緻性狀态, 變為另一個一緻性狀态.

隔離性(I: Isolation): 各個事務的執行互不幹擾, 任意一個事務的内部操作對其他并發事務都是隔離的(并發執行的事務之間不能看到對方的中間狀态,不能互相影響)

持續性(D: Durability): 持續性也稱持久性(Persistence), 指事務一旦送出, 對資料所做的任何改變都要記錄到永久存儲器(通常指實體資料庫).

當事務所包含的全部操作都成功執行後送出事務,使操作永久生效,事務送出有兩種方式:

1). 顯式送出: 使用<code>commit</code>;

2). 自動送出: 執行DDL/DCL語句或程式正常退出;

當事務所包含的任意一個操作執行失敗後應該復原事務, 使該事務中所做的修改全部失效, 事務復原也有兩種方式:

1). 顯式復原: 使用<code>rollback</code>;

2). 自動復原: 系統錯誤或強行退出.

注意: 同一事務中所有的操作,都必須使用同一個<code>Connection</code>.

JDBC對事務的支援由<code>Connection</code>提供, <code>Connection</code>預設打開自動送出,即關閉事務,SQL語句一旦執行, 便會立即送出資料庫,永久生效,無法對其進行復原操作,是以需要關閉自動送出功能.

首先建立一張表用于測試

插入兩條測試資料

No Transaction

By Transaction

注意: 當<code>Connection</code>遇到一個未處理的<code>SQLException</code>時, 程式将會非正常退出,事務也會自動復原;但如果程式捕獲了該異常, 則需要在異常處理塊中顯式地復原事務.

在相同資料環境下,使用相同輸入,執行相同操作,根據不同的隔離級别,會導緻不同的結果.不同的事務隔離級别能夠解決的資料并發問題的能力是不同的, 由弱到強分為以下四級:

隔離級别

釋義

<code>read uncommitted</code>

讀未送出資料

不符合原子性,稱為”髒讀”, 在實際業務中不用.

<code>read commited</code>

讀已送出資料(Oracle)

事務執行中,讀不到另一個事務正在進行的操作,但可以讀到另一個事務結束的操作.

<code>repeatable read</code>

可重複讀(MySQL)

在事務執行中,所有資訊都來自事務開始的那一瞬間的資訊,不受其他已送出事務的影響.

<code>serializeable</code>

串行化

所有的事務按順序執行,也就沒有了沖突的可能.隔離級别最高,但事務互相等待時間太長,性能最差,少用.

MySQL設定事務隔離級别:

<code>set session transaction isolation level [read uncommitted | read committed | repeatable read |serializable]</code>

檢視目前事務隔離級别:

<code>select @@tx_isolation</code>

JDBC設定隔離級别

<code>connection.setTransactionIsolation(int level)</code>

level可為以下值:

1). <code>Connection.TRANSACTION_READ_UNCOMMITTED</code>

2). <code>Connection.TRANSACTION_READ_COMMITTED</code>

3). <code>Connection.TRANSACTION_REPEATABLE_READ</code>

4). <code>Connection.TRANSACTION_SERIALIZABLE</code>

附: 事務并發讀問題 1. 髒讀(<code>dirty read</code>):讀到另一個事務的未送出的資料,即讀取到了髒資料(<code>read commited</code>級别可解決). 2. 不可重複讀(<code>unrepeatable read</code>):對同一記錄的兩次讀取不一緻,因為另一事務對該記錄做了修改(<code>repeatable read</code>級别可解決) 3. 幻讀/虛讀(<code>phantom read</code>):對同一張表的兩次查詢不一緻,因為另一事務插入了一條記錄(<code>repeatable read</code>級别可解決) 不可重複讀和幻讀的差別: 不可重複讀是讀取到了另一事務的更新; 幻讀是讀取到了另一事務的插入(MySQL中無法測試到幻讀,效果與不可重複讀一緻);
多條SQL語句被當做同一批操作同時執行.

調用<code>Statement</code>對象的<code>addBatch(String sql)</code>方法将多條SQL語句收集起來, 然後調用<code>executeBatch()</code>同時執行.

為了讓批量操作可以正确進行, 必須把批處理視為單個事務, 如果在執行過程中失敗, 則讓事務復原到批處理開始前的狀态.

1). 對于批處理,也可以使用<code>PreparedStatement</code>,建議使用<code>Statement</code>,因為<code>PreparedStatement</code>的預編譯空間有限,當資料量過大時,可能會引起記憶體溢出. 2). MySQL預設也沒有打開批處理功能,需要在URL中設定<code>rewriteBatchedStatements=true</code>參數打開.

commons-dbutils是Apache Commons元件中的一員,提供了對JDBC的簡單封裝,以簡化JDBC程式設計;使用dbutils需要在pom.xml中添加如下依賴:

dbutils的常用類/接口如下:

<code>DbUtils</code>: 提供了一系列的實用靜态方法(如:<code>close()</code>);

<code>ResultSetHandler</code>: 提供對結果集<code>ResultSet</code>與JavaBean等的轉換;

<code>QueryRunner</code>:

<code>update()</code>(執行insert/update/delete)

<code>query()</code>(執行select)

<code>batch()</code>(批處理).

常用的update方法簽名如下:

第二種方式需要提供<code>Connection</code>, 這樣多次調用<code>update</code>可以共用一個<code>Connection</code>, 是以調用該方法可以支援事務;

QueryRunner常用的query方法簽名如下:

<code>query()</code>方法會通過sql語句和params參數查詢出<code>ResultSet</code>,然後通過<code>ResultSetHandler</code>将<code>ResultSet</code>轉換成對應的JavaBean傳回.

在上例中, 我們使用自定的<code>ResultSetHandler</code>将<code>ResultSet</code>轉換成JavaBean, 但實際上dbutils預設已經提供了很多定義良好的<code>Handler</code>實作:

<code>BeanHandler</code> : 單行處理器,将<code>ResultSet</code>轉換成JavaBean;

<code>BeanListHandler</code> : 多行處理器,将<code>ResultSet</code>轉換成<code>List&lt;JavaBean&gt;</code>;

<code>MapHandler</code> : 單行處理器,将<code>ResultSet</code>轉換成<code>Map&lt;String,Object&gt;</code>, 列名為鍵;

<code>MapListHandler</code> : 多行處理器,将<code>ResultSet</code>轉換成<code>List&lt;Map&lt;String,Object&gt;&gt;</code>;

<code>ScalarHandler</code> : 單行單列處理器,将<code>ResultSet</code>轉換成<code>Object</code>(如儲存<code>SELECT COUNT(*) FROM t_ddl</code>).

<code>ColumnListHandler</code> : 多行單列處理器,将<code>ResultSet</code>轉換成<code>List&lt;Object&gt;</code>(使用時需要指定某一列的名稱/編号,如<code>new ColumListHandler(“name”)</code>:表示把name列資料放到<code>List</code>中);

<code>QueryRunner</code>提供了批處理方法<code>int[] batch(String sql, Object[][] params)</code>(由于更新一行時需要<code>Object[] param</code>作為參數, 是以批處理需要指定<code>Object[][] params</code>,其中每個<code>Object[]</code>對應一條記錄):