基于Java回顧之JDBC的使用詳解
盡管在實際開發(fā)過程中,我們一般使用ORM框架來代替?zhèn)鹘y(tǒng)的JDBC,例如Hibernate或者iBatis,但JDBC是Java用來實現(xiàn)數(shù)據(jù)訪問的基礎(chǔ),掌握它對于我們理解Java的數(shù)據(jù)操作流程很有幫助。
JDBC的全稱是Java Database Connectivity。
JDBC對數(shù)據(jù)庫進行操作的流程:
•連接數(shù)據(jù)庫
•發(fā)送數(shù)據(jù)請求,即傳統(tǒng)的CRUD指令
•返回操作結(jié)果集
JDBC中常用的對象包括:
•ConnectionManager
•Connection
•Statement
•CallableStatement
•PreparedStatement
•ResultSet
•SavePoint
一個簡單示例
我們來看下面一個簡單的示例,它使用JDK自帶的Derby數(shù)據(jù)庫,創(chuàng)建一張表,插入一些記錄,然后將記錄返回:
一個簡單的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();
}
}
如何建立數(shù)據(jù)庫連接
上面的示例代碼中,建立數(shù)據(jù)庫連接的部分如下:
String driver = "org.apache.derby.jdbc.EmbeddedDriver";
String dbURL = "jdbc:derby:EmbeddedDB;create=true";
Class.forName(driver);
con = DriverManager.getConnection(dbURL);
建立數(shù)據(jù)庫連接的過程,可以分為兩步:
1)加載數(shù)據(jù)庫驅(qū)動,即上文中的driver以及Class.forName(dirver)
2)定位數(shù)據(jù)庫連接字符串, 即dbURL以及DriverManager.getConnection(dbURL)
不同的數(shù)據(jù)庫,對應(yīng)的dirver和dbURL不同,但加載驅(qū)動和建立連接的方式是相同的,即只需要修改上面driver和dbURL的值就可以了。
自動加載數(shù)據(jù)庫驅(qū)動
如果我們每次建立連接時,都要使用Class.forName(...)來手動加載數(shù)據(jù)庫驅(qū)動,這樣會很麻煩,我們可以通過配置文件的方式,來保存數(shù)據(jù)庫驅(qū)動的信息。
我們可以在classpath中,即編譯出來的.class的存放路徑,添加如下文件:
META-INF\services\java.sql.Driver
對應(yīng)的內(nèi)容就是JDBC驅(qū)動的全路徑,也就是上面driver變量的值:
org.apache.derby.jdbc.EmbeddedDriver
接下來,我們在程序中,就不需要再顯示的用Class.forName(...)來加載驅(qū)動了,它會被自動加載進來,當(dāng)我們的數(shù)據(jù)庫發(fā)生變化時,只需要修改這個文件就可以了,例如當(dāng)我們的數(shù)據(jù)庫由Derby變?yōu)镸ySQL時,只需要將上述的配置修改為:
com.mysql.jdbc.Driver
但是,需要注意一點,這里只是配置了JDBC驅(qū)動的全路徑,并沒有包含jar文件的信息,因此,我們還是需要將包含該驅(qū)動的jar文件手動的放置到程序的classpath中。
JDBC中的基本操作
對于數(shù)據(jù)庫操作來說,CRUD操作應(yīng)該是最常見的操作了, 即我們常說的增、刪、查、改。
JDBC是使用Statement和ResultSet來完成這些操作的。
如何實現(xiàn)CRUD
下面是一個實現(xiàn)CRUD的示例:
JDBC實現(xiàn)基本的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();
}
我們順序調(diào)用上面的測試方法:
insertTest();
deleteTest();
updateTest();
執(zhí)行結(jié)果如下:
=====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表中的所有記錄打印出來。
如何調(diào)用存儲過程
存儲過程是做數(shù)據(jù)庫開發(fā)時經(jīng)常使用的技術(shù),它可以通過節(jié)省編譯時間的方式來提升系統(tǒng)性能,我們這里的示例使用MySQL數(shù)據(jù)庫。
如何調(diào)用不帶參數(shù)的存儲過程
假設(shè)我們現(xiàn)在有一個簡單的存儲過程,它只是返回user表中的所有記錄,存儲過程如下:
CREATE DEFINER=`root`@`localhost` PROCEDURE `GetUser`()
BEGIN
select ID,NAME from user;
END
我們可以使用CallableStatement來調(diào)用存儲過程:
調(diào)用存儲過程示例一
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();
}
它的執(zhí)行結(jié)果如下:
ID:1; NAME=Zhang San
ID:2; NAME=TEST
如何調(diào)用帶參數(shù)的存儲過程
MySQL的存儲過程中的參數(shù)分為三種:in/out/inout,我們可以把in看做入力參數(shù),out看做出力參數(shù),JDBC對這兩種類型的參數(shù)設(shè)置方式不同:
1)in, JDBC使用類似于cst.set(1, 10)的方式來設(shè)置
2)out,JDBC使用類似于cst.registerOutParameter(2, Types.VARCHAR);的方式來設(shè)置
我們來看一個in參數(shù)的示例,假設(shè)我們希望返回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的調(diào)用代碼如下:
JDBC調(diào)用存儲過程示例二
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();
}
我們執(zhí)行下面的語句:
execStoredProcedureTest2(1);
結(jié)果如下:
ID:1; NAME=Zhang San
對于out類型的參數(shù),調(diào)用方式類似,不再贅述。
獲取數(shù)據(jù)庫以及結(jié)果集的metadata信息
在JDBC中,我們不僅能夠?qū)?shù)據(jù)進行操作,我們還能獲取數(shù)據(jù)庫以及結(jié)果集的元數(shù)據(jù)信息,例如數(shù)據(jù)庫的名稱、驅(qū)動信息、表信息;結(jié)果集的列信息等。
獲取數(shù)據(jù)庫的metadata信息
我們可以通過connection.getMetaData方法來獲取數(shù)據(jù)庫的元數(shù)據(jù)信息,它的類型是DatabaseMetaData。
獲取數(shù)據(jù)庫的元數(shù)據(jù)信息
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("數(shù)據(jù)庫:" + dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion());
System.out.println("驅(qū)動程序:" + 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")));
}
}
這里我們使用的數(shù)據(jù)庫是MySQL中自帶的默認數(shù)據(jù)庫:mysql,它會記錄整個數(shù)據(jù)庫服務(wù)器中的一些信息。上述代碼執(zhí)行結(jié)果如下:
數(shù)據(jù)庫:MySQL 5.5.28
驅(qū)動程序: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中表比較多,上述結(jié)果只截取了一部分。
獲取結(jié)果集的元數(shù)據(jù)信息
我們可以通過使用resultset.getMetaData方法來獲取結(jié)果集的元數(shù)據(jù)信息,它的類型是ResultSetMetaData。
獲取結(jié)果集的元數(shù)據(jù)信息
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));
}
}
它的執(zhí)行結(jié)果如下:
Column Name:ID; Column Type:INTEGER UNSIGNED
Column Name:NAME; Column Type:VARCHAR
可以看到,它返回類結(jié)果集中每一列的名稱和類型。
基于ResultSet的操作
當(dāng)我們需要對數(shù)據(jù)庫進行修改時,除了上述通過Statement完成操作外,我們也可以借助ResultSet來完成。
需要注意的是,在這種情況下,我們定義Statement時,需要添加參數(shù)。
Statement構(gòu)造函數(shù)可以包含3個參數(shù):
•resultSetType,它的取值包括:ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE,默認情況下,該參數(shù)的值是ResultSet.TYPE_FORWARD_ONLY。
•resultSetConcurrency,它的取值包括:ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE,默認情況下,該參數(shù)的值是ResultSet.CONCUR_READ_ONLY。
•resultSetHoldability,它的取值包括:ResultSet.HOLD_CURSORS_OVER_COMMIT 或 ResultSet.CLOSE_CURSORS_AT_COMMIT。
為了使得ResultSet能夠?qū)?shù)據(jù)進行操作我們需要:
•將resultSetType設(shè)置為ResultSet.TYPE_SCROLL_SENSITIVE。
•將resultSetConcurrency設(shè)置為ResultSet.CONCUR_UPDATABLE。
在通過ResultSet對數(shù)據(jù)進行調(diào)整的過程中,下面方法可能會被調(diào)用:
•resultset.last()
•resultset.first()
•resultset.moveToInsertRow()
•resultset.absolute()
•resultset.setxxx()
•resultset.updateRow()
•resultset.insertRow()
下面是一個通過ResultSet對數(shù)據(jù)進行增、刪、改的示例:
通過ResultSet對數(shù)據(jù)進行增、刪、改
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("返回結(jié)果的條數(shù):"+ 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();
}
分別調(diào)用上述方法:
getResultCount();
insertDataToResultSet();
updateDataToResultSet();
delDataFromResultSet();
執(zhí)行結(jié)果如下:
=====Result Count=====
返回結(jié)果的條數(shù):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的記錄進行了插入、更新和刪除操作。
預(yù)處理以及批處理
預(yù)處理和批處理都是用來提升系統(tǒng)性能的方式,一種是利用數(shù)據(jù)庫的緩存機制,一種是利用數(shù)據(jù)庫一次執(zhí)行多條語句的方式。
預(yù)處理
數(shù)據(jù)庫服務(wù)器接收到Statement后,一般會解析Statement、分析是否有語法錯誤、定制最優(yōu)的執(zhí)行計劃,這個過程可能會降低系統(tǒng)的性能。一般的數(shù)據(jù)庫服務(wù)器都這對這種情況,設(shè)計了緩存機制,當(dāng)數(shù)據(jù)庫接收到指令時,如果緩存中已經(jīng)存在,那么就不再解析,而是直接運行。
這里相同的指令是指sql語句完全一樣,包括大小寫。
JDBC使用PreparedStatement來完成預(yù)處理:
預(yù)處理示例
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();
}
執(zhí)行結(jié)果如下:
=====Insert a single record by PreparedStatement=====
ID:1; NAME=Zhang San
ID:2; NAME=TEST
ID:5; NAME=Lei Feng
批處理
批處理是利用數(shù)據(jù)庫一次執(zhí)行多條語句的機制來提升性能,這樣可以避免多次建立連接帶來的性能損失。
批處理使用Statement的addBatch來添加指令,使用executeBatch方法來一次執(zhí)行多條指令:
批處理示例
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();
}
執(zhí)行結(jié)果如下:
=====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
預(yù)處理和批處理相結(jié)合
我們可以把預(yù)處理和批處理結(jié)合起來,利用數(shù)據(jù)庫的緩存機制,一次執(zhí)行多條語句:
預(yù)處理和批處理相結(jié)合的示例
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();
}
執(zhí)行結(jié)果如下:
=====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
數(shù)據(jù)庫事務(wù)
談到數(shù)據(jù)庫開發(fā),事務(wù)是一個不可回避的話題,JDBC默認情況下,是每一步都自動提交的,我們可以通過設(shè)置connection.setAutoCommit(false)的方式來強制關(guān)閉自動提交,然后通過connection.commit()和connection.rollback()來實現(xiàn)事務(wù)提交和回滾。
簡單的數(shù)據(jù)庫事務(wù)
下面是一個簡單的數(shù)據(jù)庫事務(wù)的示例:
簡單的數(shù)據(jù)庫事務(wù)示例
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();
}
}
連續(xù)執(zhí)行上述方法兩次,我們可以得出下面的結(jié)果:
=====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)
可以看到,第一次調(diào)用時,操作成功,事務(wù)提交,向user表中插入了一條記錄;第二次調(diào)用時,發(fā)生主鍵沖突異常,事務(wù)回滾。
帶有SavePoint的事務(wù)
當(dāng)我們的事務(wù)操作中包含多個處理,但我們有時希望一些操作完成后可以先提交,這樣可以避免整個事務(wù)的回滾。JDBC使用SavePoint來實現(xiàn)這一點。
帶有SavePoint的事務(wù)示例
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();
}
}
執(zhí)行結(jié)果如下:
=====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
可以看到最終事務(wù)報出了主鍵沖突異常,事務(wù)回滾,但是依然向數(shù)據(jù)庫中插入了ID為13和14的記錄。
另外,在確定SavePoint后,ID為15的記錄并沒有被插入,它是通過事務(wù)進行了回滾。
- java jdbc連接mysql數(shù)據(jù)庫實現(xiàn)增刪改查操作
- Java使用JDBC連接數(shù)據(jù)庫的實現(xiàn)方法
- Java開發(fā)Oracle數(shù)據(jù)庫連接JDBC Thin Driver 的三種方法
- java實現(xiàn)jdbc批量插入數(shù)據(jù)
- Java加載JDBC驅(qū)動程序?qū)嵗斀?/a>
- java使用JDBC動態(tài)創(chuàng)建數(shù)據(jù)表及SQL預(yù)處理的方法
- JAVA使用JDBC技術(shù)操作SqlServer數(shù)據(jù)庫實例代碼
- 完整java開發(fā)中JDBC連接數(shù)據(jù)庫代碼和步驟
- java的jdbc簡單封裝方法
- Java原生操作JDBC連接以及原理詳解
相關(guān)文章
Springboot?格式化LocalDateTime的方法
這篇文章主要介紹了Springboot格式化LocalDateTime的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05Java JVM原理與調(diào)優(yōu)_動力節(jié)點Java學(xué)院整理
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設(shè)備的規(guī)范,它是一個虛構(gòu)出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。下面通過本文給大家介紹jvm原理與調(diào)優(yōu)相關(guān)知識,感興趣的朋友一起學(xué)習(xí)吧2017-04-04