亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

基于Java回顧之JDBC的使用詳解

2019-11-26 16:06:46
字體:
來源:轉載
供稿:網友

盡管在實際開發過程中,我們一般使用ORM框架來代替傳統的JDBC,例如Hibernate或者iBatis,但JDBC是Java用來實現數據訪問的基礎,掌握它對于我們理解Java的數據操作流程很有幫助。

JDBC的全稱是Java Database Connectivity。

JDBC對數據庫進行操作的流程:
•連接數據庫
•發送數據請求,即傳統的CRUD指令
•返回操作結果集

JDBC中常用的對象包括:
•ConnectionManager
•Connection
•Statement
•CallableStatement
•PreparedStatement
•ResultSet
•SavePoint
一個簡單示例
我們來看下面一個簡單的示例,它使用JDK自帶的Derby數據庫,創建一張表,插入一些記錄,然后將記錄返回:

復制代碼 代碼如下:

一個簡單的JDBC示例
 private static void test1() throws SQLException
 {
     String driver = "org.apache.derby.jdbc.EmbeddedDriver";
     String dbURL = "jdbc:derby:EmbeddedDB;create=true";

     Connection con = null;
     Statement st = null;
     try
     {
         Class.forName(driver);
         con = DriverManager.getConnection(dbURL);
         st = con.createStatement();
         st.execute("create table foo(ID INT NOT NULL, NAME VARCHAR(30))");
         st.executeUpdate("insert into foo(ID,NAME) values(1, 'Zhang San')");

         ResultSet rs = st.executeQuery("select ID,NAME from foo");

         while(rs.next())
         {
             int id = rs.getInt("ID");
             String name = rs.getString("NAME");
             System.out.println("ID=" + id + "; NAME=" + name);
         }
     }
     catch(Exception ex)
     {
         ex.printStackTrace();
     }
     finally
     {
         if (st != null) st.close();
         if (con != null) con.close();
     }
 }

如何建立數據庫連接
上面的示例代碼中,建立數據庫連接的部分如下:
復制代碼 代碼如下:

String driver = "org.apache.derby.jdbc.EmbeddedDriver";
String dbURL = "jdbc:derby:EmbeddedDB;create=true";

Class.forName(driver);
con = DriverManager.getConnection(dbURL);


建立數據庫連接的過程,可以分為兩步:

1)加載數據庫驅動,即上文中的driver以及Class.forName(dirver)

2)定位數據庫連接字符串, 即dbURL以及DriverManager.getConnection(dbURL)

不同的數據庫,對應的dirver和dbURL不同,但加載驅動和建立連接的方式是相同的,即只需要修改上面driver和dbURL的值就可以了。

自動加載數據庫驅動
如果我們每次建立連接時,都要使用Class.forName(...)來手動加載數據庫驅動,這樣會很麻煩,我們可以通過配置文件的方式,來保存數據庫驅動的信息。

我們可以在classpath中,即編譯出來的.class的存放路徑,添加如下文件:

復制代碼 代碼如下:

META-INF/services/java.sql.Driver

對應的內容就是JDBC驅動的全路徑,也就是上面driver變量的值:
復制代碼 代碼如下:

org.apache.derby.jdbc.EmbeddedDriver

接下來,我們在程序中,就不需要再顯示的用Class.forName(...)來加載驅動了,它會被自動加載進來,當我們的數據庫發生變化時,只需要修改這個文件就可以了,例如當我們的數據庫由Derby變為MySQL時,只需要將上述的配置修改為:
復制代碼 代碼如下:

com.mysql.jdbc.Driver

但是,需要注意一點,這里只是配置了JDBC驅動的全路徑,并沒有包含jar文件的信息,因此,我們還是需要將包含該驅動的jar文件手動的放置到程序的classpath中。

JDBC中的基本操作
對于數據庫操作來說,CRUD操作應該是最常見的操作了, 即我們常說的增、刪、查、改。

JDBC是使用Statement和ResultSet來完成這些操作的。

如何實現CRUD
下面是一個實現CRUD的示例:

復制代碼 代碼如下:

JDBC實現基本的CRUD示例
 private static void insertTest() throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     st.execute("insert into user(ID,NAME) values(1, 'Zhang San')");
     st.execute("insert into user(ID,NAME) values(2, 'Li Si')");
     st.execute("insert into user(ID,NAME) values(3, 'Wang Wu')");
     System.out.println("=====insert test=====");
     showUser(st);
     st.close();
     con.close();
 }

 private static void deleteTest() throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     st.execute("delete from user where ID=3");
     System.out.println("=====delete test=====");
     showUser(st);
     st.close();
     con.close();
 }

 private static void updateTest() throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     st.executeUpdate("update user set NAME='TEST' where ID=2");
     System.out.println("=====update test=====");
     showUser(st);
     st.close();
     con.close();
 }

 private static void showUser(Statement st) throws SQLException
 {
     ResultSet rs = st.executeQuery("select ID, NAME from user");
     while(rs.next())
     {
         int id = rs.getInt("ID");
         String name = rs.getString("NAME");
         System.out.println("ID:" + id + "; NAME=" + name);
     }
     rs.close();
 }

我們順序調用上面的測試方法:
復制代碼 代碼如下:

insertTest();
deleteTest();
updateTest();

執行結果如下:
復制代碼 代碼如下:

=====insert test=====
ID:1; NAME=Zhang San
ID:2; NAME=Li Si
ID:3; NAME=Wang Wu
=====delete test=====
ID:1; NAME=Zhang San
ID:2; NAME=Li Si
=====update test=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST

上面代碼中的showUser方法會把user表中的所有記錄打印出來。

如何調用存儲過程
存儲過程是做數據庫開發時經常使用的技術,它可以通過節省編譯時間的方式來提升系統性能,我們這里的示例使用MySQL數據庫。

如何調用不帶參數的存儲過程
假設我們現在有一個簡單的存儲過程,它只是返回user表中的所有記錄,存儲過程如下:

復制代碼 代碼如下:

CREATE DEFINER=`root`@`localhost` PROCEDURE `GetUser`()
BEGIN
select ID,NAME from user;
END

我們可以使用CallableStatement來調用存儲過程:
復制代碼 代碼如下:

調用存儲過程示例一
 private static void execStoredProcedureTest() throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     CallableStatement cst = con.prepareCall("call GetUser()");
     ResultSet rs = cst.executeQuery();
     while(rs.next())
     {
         int id = rs.getInt("ID");
         String name = rs.getString("NAME");
         System.out.println("ID:" + id + "; NAME=" + name);
     }
     rs.close();
     cst.close();
     con.close();
 }

它的執行結果如下:
復制代碼 代碼如下:

ID:1; NAME=Zhang San
ID:2; NAME=TEST

如何調用帶參數的存儲過程
MySQL的存儲過程中的參數分為三種:in/out/inout,我們可以把in看做入力參數,out看做出力參數,JDBC對這兩種類型的參數設置方式不同:

1)in, JDBC使用類似于cst.set(1, 10)的方式來設置

2)out,JDBC使用類似于cst.registerOutParameter(2, Types.VARCHAR);的方式來設置

我們來看一個in參數的示例,假設我們希望返回ID為特定值的user信息,存儲過程如下:

復制代碼 代碼如下:

CREATE DEFINER=`root`@`localhost` PROCEDURE `GetUserByID`(in id int)
 BEGIN
 set @sqlstr=concat('select * from user where ID=', id);
 prepare psmt from @sqlstr;
 execute psmt;
 END

Java的調用代碼如下:
復制代碼 代碼如下:

JDBC調用存儲過程示例二
 private static void execStoredProcedureTest2(int id) throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     CallableStatement cst = con.prepareCall("call GetUserByID(?)");
     cst.setInt(1, id);
     ResultSet rs = cst.executeQuery();
     while(rs.next())
     {
         String name = rs.getString("NAME");
         System.out.println("ID:" + id + "; NAME=" + name);
     }
     rs.close();
     cst.close();
     con.close();
 }

我們執行下面的語句:
復制代碼 代碼如下:

execStoredProcedureTest2(1);

結果如下:
復制代碼 代碼如下:

ID:1; NAME=Zhang San

對于out類型的參數,調用方式類似,不再贅述。

獲取數據庫以及結果集的metadata信息
在JDBC中,我們不僅能夠對數據進行操作,我們還能獲取數據庫以及結果集的元數據信息,例如數據庫的名稱、驅動信息、表信息;結果集的列信息等。

獲取數據庫的metadata信息
我們可以通過connection.getMetaData方法來獲取數據庫的元數據信息,它的類型是DatabaseMetaData。

復制代碼 代碼如下:

獲取數據庫的元數據信息
 private static void test1() throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/mysql";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");

     DatabaseMetaData dbmd = con.getMetaData();

     System.out.println("數據庫:" + dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion());
     System.out.println("驅動程序:" + dbmd.getDriverName() + " " + dbmd.getDriverVersion());

     ResultSet rs = dbmd.getTables(null, null, null, null);
     System.out.println(String.format("|%-26s|%-9s|%-9s|%-9s|", "表名稱","表類別","表類型","表模式"));       
     while(rs.next())
     {
         System.out.println(String.format("|%-25s|%-10s|%-10s|%-10s|",
                 rs.getString("TABLE_NAME"),rs.getString("TABLE_CAT"),
                 rs.getString("TABLE_TYPE"), rs.getString("TABLE_SCHEM")));
     }
 }

這里我們使用的數據庫是MySQL中自帶的默認數據庫:mysql,它會記錄整個數據庫服務器中的一些信息。上述代碼執行結果如下:
復制代碼 代碼如下:

數據庫:MySQL 5.5.28
驅動程序:MySQL-AB JDBC Driver mysql-connector-java-5.0.4 ( $Date: 2006-10-19 17:47:48 +0200 (Thu, 19 Oct 2006) $, $Revision: 5908 $ )
|表名稱                       |表類別      |表類型      |表模式      |
|columns_priv             |mysql     |TABLE     |null      |
|db                       |mysql     |TABLE     |null      |
|event                    |mysql     |TABLE     |null      |
|func                     |mysql     |TABLE     |null      |
。。。

由于mysql中表比較多,上述結果只截取了一部分。

獲取結果集的元數據信息
我們可以通過使用resultset.getMetaData方法來獲取結果集的元數據信息,它的類型是ResultSetMetaData。

復制代碼 代碼如下:

獲取結果集的元數據信息
 private static void test2() throws SQLException
 {
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     ResultSet rs = st.executeQuery("select ID, NAME from user");
     ResultSetMetaData rsmd = rs.getMetaData();
     for (int i = 1; i <= rsmd.getColumnCount(); i++)
     {
         System.out.println("Column Name:" + rsmd.getColumnName(i) + "; Column Type:" + rsmd.getColumnTypeName(i));
     }
 }

它的執行結果如下:
復制代碼 代碼如下:

Column Name:ID; Column Type:INTEGER UNSIGNED
Column Name:NAME; Column Type:VARCHAR

可以看到,它返回類結果集中每一列的名稱和類型。

基于ResultSet的操作
當我們需要對數據庫進行修改時,除了上述通過Statement完成操作外,我們也可以借助ResultSet來完成。

需要注意的是,在這種情況下,我們定義Statement時,需要添加參數。

Statement構造函數可以包含3個參數:

•resultSetType,它的取值包括:ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE,默認情況下,該參數的值是ResultSet.TYPE_FORWARD_ONLY。
•resultSetConcurrency,它的取值包括:ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE,默認情況下,該參數的值是ResultSet.CONCUR_READ_ONLY。
•resultSetHoldability,它的取值包括:ResultSet.HOLD_CURSORS_OVER_COMMIT 或 ResultSet.CLOSE_CURSORS_AT_COMMIT。
為了使得ResultSet能夠對數據進行操作我們需要:

•將resultSetType設置為ResultSet.TYPE_SCROLL_SENSITIVE。
•將resultSetConcurrency設置為ResultSet.CONCUR_UPDATABLE。
在通過ResultSet對數據進行調整的過程中,下面方法可能會被調用:

•resultset.last()
•resultset.first()
•resultset.moveToInsertRow()
•resultset.absolute()
•resultset.setxxx()
•resultset.updateRow()
•resultset.insertRow()
下面是一個通過ResultSet對數據進行增、刪、改的示例:

復制代碼 代碼如下:

通過ResultSet對數據進行增、刪、改
 private static void getResultCount() throws SQLException
 {
     System.out.println("=====Result Count=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
     ResultSet rs = st.executeQuery("select * from user");
     rs.last();
     System.out.println("返回結果的條數:"+ rs.getRow());
     rs.first();

     rs.close();
     st.close();
     con.close();
 }

 private static void insertDataToResultSet() throws SQLException
 {
     System.out.println("=====Insert=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
     ResultSet rs = st.executeQuery("select ID,NAME from user");
     rs.moveToInsertRow();
     rs.updateInt(1, 4);
     rs.updateString(2, "Xiao Ming");
     rs.insertRow();
     showUser(st);

     rs.close();
     st.close();
     con.close();
 }

 private static void updateDataToResultSet() throws SQLException
 {
     System.out.println("=====Update=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
     ResultSet rs = st.executeQuery("select * from user");
     rs.last();
     int count = rs.getRow();
     rs.first();
     rs.absolute(count);
     rs.updateString(2, "Xiao Qiang");
     rs.updateRow();
     showUser(st);

     rs.close();
     st.close();
     con.close();
 }

 private static void delDataFromResultSet() throws SQLException
 {
     System.out.println("=====Delete=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE, ResultSet.CLOSE_CURSORS_AT_COMMIT);
     ResultSet rs = st.executeQuery("select * from user");
     rs.last();
     int count = rs.getRow();
     rs.first();
     rs.absolute(count);
     rs.deleteRow();
     showUser(st);

     rs.close();
     st.close();
     con.close();
 }

分別調用上述方法:
復制代碼 代碼如下:

getResultCount();
insertDataToResultSet();
updateDataToResultSet();
delDataFromResultSet();

執行結果如下:
復制代碼 代碼如下:

=====Result Count=====
返回結果的條數:2
=====Insert=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:4; NAME=Xiao Ming
=====Update=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:4; NAME=Xiao Qiang
=====Delete=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST

可以看到我們對ID為4的記錄進行了插入、更新和刪除操作。

預處理以及批處理
預處理和批處理都是用來提升系統性能的方式,一種是利用數據庫的緩存機制,一種是利用數據庫一次執行多條語句的方式。

預處理
數據庫服務器接收到Statement后,一般會解析Statement、分析是否有語法錯誤、定制最優的執行計劃,這個過程可能會降低系統的性能。一般的數據庫服務器都這對這種情況,設計了緩存機制,當數據庫接收到指令時,如果緩存中已經存在,那么就不再解析,而是直接運行。

這里相同的指令是指sql語句完全一樣,包括大小寫。

JDBC使用PreparedStatement來完成預處理:

復制代碼 代碼如下:

預處理示例
 private static void test1() throws SQLException
 {
     System.out.println("=====Insert a single record by PreparedStatement=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     PreparedStatement pst = con.prepareStatement("insert into user(id,name) values(?,?)");
     pst.setInt(1, 5);
     pst.setString(2, "Lei Feng");
     pst.executeUpdate();
     showUser(pst);
     pst.close();
     con.close();
 }

執行結果如下:
復制代碼 代碼如下:

=====Insert a single record by PreparedStatement=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng

批處理
批處理是利用數據庫一次執行多條語句的機制來提升性能,這樣可以避免多次建立連接帶來的性能損失。

批處理使用Statement的addBatch來添加指令,使用executeBatch方法來一次執行多條指令:

復制代碼 代碼如下:

批處理示例
 private static void test2() throws SQLException
 {
     System.out.println("=====Insert multiple records by Statement & Batch=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     st.addBatch("insert into user(id,name) values(6,'Xiao Zhang')");
     st.addBatch("insert into user(id,name) values(7,'Xiao Liu')");
     st.addBatch("insert into user(id,name) values(8,'Xiao Zhao')");
     st.executeBatch();
     showUser(st);
     st.close();
     con.close();
 }

執行結果如下:
復制代碼 代碼如下:

=====Insert multiple records by Statement & Batch=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng
ID:6; NAME=Xiao Zhang
ID:7; NAME=Xiao Liu
ID:8; NAME=Xiao Zhao

預處理和批處理相結合
我們可以把預處理和批處理結合起來,利用數據庫的緩存機制,一次執行多條語句:
復制代碼 代碼如下:

預處理和批處理相結合的示例
 private static void test3() throws SQLException
 {
     System.out.println("=====Insert multiple records by PreparedStatement & Batch=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     PreparedStatement pst = con.prepareStatement("insert into user(id,name) values(?,?)");
     pst.setInt(1, 9);
     pst.setString(2, "Xiao Zhang");
     pst.addBatch();
     pst.setInt(1, 10);
     pst.setString(2, "Xiao Liu");
     pst.addBatch();
     pst.setInt(1, 11);
     pst.setString(2, "Xiao Zhao");
     pst.addBatch();
     pst.executeBatch();
     showUser(pst);
     pst.close();
     con.close();
 }

執行結果如下:
復制代碼 代碼如下:

=====Insert multiple records by PreparedStatement & Batch=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng
ID:9; NAME=Xiao Zhang
ID:10; NAME=Xiao Liu
ID:11; NAME=Xiao Zhao

數據庫事務
談到數據庫開發,事務是一個不可回避的話題,JDBC默認情況下,是每一步都自動提交的,我們可以通過設置connection.setAutoCommit(false)的方式來強制關閉自動提交,然后通過connection.commit()和connection.rollback()來實現事務提交和回滾。

簡單的數據庫事務
下面是一個簡單的數據庫事務的示例:

復制代碼 代碼如下:

簡單的數據庫事務示例
 private static void transactionTest1() throws SQLException
 {
     System.out.println("=====Simple Transaction test=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     try
     {
         con.setAutoCommit(false);
         st.executeUpdate("insert into user(id,name) values(12, 'Xiao Li')");
         con.commit();
     }
     catch(Exception ex)
     {
         ex.printStackTrace();
         con.rollback();
     }
     finally
     {
         con.setAutoCommit(true);
         showUser(st);
         if (st != null) st.close();
         if (con != null) con.close();
     }
 }

連續執行上述方法兩次,我們可以得出下面的結果:
復制代碼 代碼如下:

=====Simple Transaction test=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng
ID:12; NAME=Xiao Li
=====Simple Transaction test=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng
ID:12; NAME=Xiao Li
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '12' for key 'PRIMARY'
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:931)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665)
    at com.mysql.jdbc.Connection.execSQL(Connection.java:3170)
    at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1316)
    at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1235)
    at sample.jdbc.mysql.ResultSetSample.transactionTest1(ResultSetSample.java:154)
    at sample.jdbc.mysql.ResultSetSample.main(ResultSetSample.java:17)

可以看到,第一次調用時,操作成功,事務提交,向user表中插入了一條記錄;第二次調用時,發生主鍵沖突異常,事務回滾。

帶有SavePoint的事務
當我們的事務操作中包含多個處理,但我們有時希望一些操作完成后可以先提交,這樣可以避免整個事務的回滾。JDBC使用SavePoint來實現這一點。

復制代碼 代碼如下:

帶有SavePoint的事務示例
 private static void transactionTest2() throws SQLException
 {
     System.out.println("=====Simple Transaction test=====");
     String dbURL = "jdbc:mysql://localhost/test";
     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     Statement st = con.createStatement();
     Savepoint svpt = null;
     try
     {
         con.setAutoCommit(false);
         st.executeUpdate("insert into user(id,name) values(13, 'Xiao Li')");
         st.executeUpdate("insert into user(id,name) values(14, 'Xiao Wang')");
         svpt = con.setSavepoint("roll back to here");
         st.executeUpdate("insert into user(id,name) values(15, 'Xiao Zhao')");
         st.executeUpdate("insert into user(id,name) values(13, 'Xiao Li')");
         con.commit();
     }
     catch(Exception ex)
     {
         ex.printStackTrace();
         con.rollback(svpt);
     }
     finally
     {
         con.setAutoCommit(true);
         showUser(st);
         if (st != null) st.close();
         if (con != null) con.close();
     }
 }

執行結果如下:
復制代碼 代碼如下:

=====Simple Transaction test=====
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '13' for key 'PRIMARY'
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:931)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665)
    at com.mysql.jdbc.Connection.execSQL(Connection.java:3170)
    at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1316)
    at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1235)
    at sample.jdbc.mysql.ResultSetSample.transactionTest2(ResultSetSample.java:185)
    at sample.jdbc.mysql.ResultSetSample.main(ResultSetSample.java:18)
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng
ID:13; NAME=Xiao Li
ID:14; NAME=Xiao Wang

可以看到最終事務報出了主鍵沖突異常,事務回滾,但是依然向數據庫中插入了ID為13和14的記錄。

另外,在確定SavePoint后,ID為15的記錄并沒有被插入,它是通過事務進行了回滾。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲香蕉av在线一区二区三区| 黑人巨大精品欧美一区二区免费| 国产精品爽黄69| 亚洲视频一区二区| 亚洲一区二区少妇| 粗暴蹂躏中文一区二区三区| 亚洲激情 国产| 亚洲最新在线视频| 91在线播放国产| 国语自产精品视频在线看一大j8| 国产精品视频不卡| 亚洲a级在线播放观看| 国产精品pans私拍| 超碰精品一区二区三区乱码| 亚洲国产91色在线| 亚洲国产精品久久久久| 久热精品视频在线| 午夜精品一区二区三区在线视| 亚洲色在线视频| 久久精品久久久久久| 欧美一区二区大胆人体摄影专业网站| 蜜臀久久99精品久久久久久宅男| 欧美精品videossex88| 97视频在线观看播放| 欧美国产欧美亚洲国产日韩mv天天看完整| 国产成人avxxxxx在线看| 久久国内精品一国内精品| 欧美激情高清视频| 国产欧美精品日韩精品| 97精品欧美一区二区三区| 国产视频精品久久久| 久久成人av网站| 国产精品福利在线观看网址| 欧美精品999| 日韩精品在线免费观看视频| 色综合影院在线| 欧美巨大黑人极品精男| 亚洲色图五月天| 成人做爰www免费看视频网站| 欧美在线视频免费| 亚洲第一免费网站| 国产综合久久久久| 欧美激情videoshd| 久久久亚洲影院| 精品视频中文字幕| 亚洲精品欧美一区二区三区| 日韩成人小视频| 精品久久久久久久久久久| 亚州av一区二区| 欧美成人精品一区二区三区| 国产欧洲精品视频| 91色视频在线观看| 亚洲国产精品悠悠久久琪琪| 日韩一区二区av| 最新国产成人av网站网址麻豆| 欧美另类老女人| 最近2019年好看中文字幕视频| 国产成人精品电影久久久| 欧美精品一本久久男人的天堂| 国产精品成人一区二区三区吃奶| 91免费视频国产| 午夜免费久久久久| 国产精品永久免费在线| 日韩精品中文字幕有码专区| 国产精品精品视频一区二区三区| 欧美成人激情视频免费观看| 日韩av在线影视| 国产在线精品一区免费香蕉| 成人福利在线视频| 国产偷国产偷亚洲清高网站| 91精品国产777在线观看| 欧美精品亚州精品| 久久99亚洲精品| 97精品国产aⅴ7777| 久久精品色欧美aⅴ一区二区| 97国产精品人人爽人人做| 亚洲欧美日韩中文视频| 成人激情黄色网| 日本不卡高字幕在线2019| 亚洲第一精品久久忘忧草社区| 亚洲丝袜av一区| 久久久久免费精品国产| 亚洲综合社区网| 亚洲国产精品久久久久秋霞不卡| 日韩中文字幕精品视频| 亚洲欧美国产精品久久久久久久| 国产亚洲美女精品久久久| 亚洲精品小视频| 欧美激情精品久久久| 久久香蕉国产线看观看网| 成人av色在线观看| 日本在线观看天堂男亚洲| 日韩中文在线中文网三级| 久久国产精品电影| 久久精品99国产精品酒店日本| 久久久久久中文| 亚洲精品国产成人| 久久久999精品免费| 日韩精品免费在线视频观看| 不卡av日日日| 尤物精品国产第一福利三区| 日韩大片在线观看视频| 欧美日韩国产成人高清视频| 国产婷婷成人久久av免费高清| 日韩精品在线影院| 中日韩美女免费视频网站在线观看| 亚洲精品日韩丝袜精品| 午夜精品一区二区三区在线播放| 国产国语刺激对白av不卡| 最近中文字幕mv在线一区二区三区四区| 久久久精品2019中文字幕神马| 国产精品成人aaaaa网站| 日韩在线小视频| 最近2019中文字幕mv免费看| 久久精品国产免费观看| 亚洲成人精品视频在线观看| 最近中文字幕2019免费| 日本欧美一级片| 亚洲а∨天堂久久精品9966| 欧美日韩亚洲高清| 欧美国产激情18| 亚洲视频专区在线| 成人黄色在线免费| 97婷婷涩涩精品一区| 久久久久久久爱| 国产精品中文在线| 国产精品www网站| 久久免费精品视频| 成人乱人伦精品视频在线观看| 美女av一区二区三区| 国产91精品久久久久久| 成人有码在线播放| 日韩视频免费大全中文字幕| 中文字幕欧美精品日韩中文字幕| 日韩国产高清污视频在线观看| 国产精品久久久久久久电影| 亚洲精品视频中文字幕| 日本精品久久中文字幕佐佐木| 欧美成人午夜激情| 国产成人免费av| 亚洲免费中文字幕| 欧美激情三级免费| 啪一啪鲁一鲁2019在线视频| 亚洲精品欧美日韩| 亚洲高清免费观看高清完整版| 国产欧美日韩最新| 国产精品黄色av| 中文字幕亚洲欧美一区二区三区| 色综合久久天天综线观看| 成人黄色短视频在线观看| 成人久久一区二区三区| 欧美极品少妇全裸体| 在线观看日韩www视频免费| 亚洲一区中文字幕在线观看| 亚洲国产精品va在看黑人| 精品伊人久久97| 亚洲精品国产福利| 美女撒尿一区二区三区| 国产主播在线一区| 欧美一区三区三区高中清蜜桃| 视频一区视频二区国产精品| 97超级碰碰人国产在线观看| 欧美另类xxx| 国产脚交av在线一区二区|