JDBC應用程式連線資料庫--事務處理
JDBC應用程式連線資料庫–事務處理
事務概念
事務是指一組最小邏輯操作單元,裡面有多個操作組成,組成事務的每一部分必須要同時提交成功,如果有一個操作失敗,整個操作就需要回滾,事務要保證ACID特性。這在資料庫中已經涉及到,不在程式設計中再次強調。我們的任務是使用Java應用程式去模擬操作資料庫中的事務處理,本文將詳述該問題。
問題引出
首先解決這個問題的所有的API均在java.sql.Connection中,要注意對這個核心API的學習。
- 情景建立,模擬轉賬過程,張三給李四轉賬1000元,那麼要求張三的錢要減1000,而李四的錢要加1000,其SQL語句為:
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
accountName VARCHAR(20),
money DOUBLE
);
UPDATE account SET money=money-1000 WHERE accountName="zs";
UPDATE account SET money=money+1000 WHERE accountName="ls";
實現該過程的Java程式如下:
package com.jpzhutech.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import org.junit.runners.ParentRunner;
public class transaction {
@SuppressWarnings("resource")
@Test
public void test(){
String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName=?";
String sql_ls = "UPDATE account SET money=money+1000 WHERE accountName=?" ;
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
try {
String url = "jdbc:mysql://192.168.101.44/amon";
String user = "root";
String password = "560128";
connection = DriverManager.getConnection(url, user, password);
//System.out.println(connection);
// 執行sql_zs
//實際上當不寫時相當於下面的這行程式碼自動執行
//connection.setAutoCommit(true);
preparedStatement = connection.prepareStatement(sql_zs);
preparedStatement.setString(1, "zs");
int count_zs = preparedStatement.executeUpdate();
System.out.println("影響了"+count_zs+"行");
// 執行sql_ls
preparedStatement = connection.prepareStatement(sql_ls);
preparedStatement.setString(1, "ls");
int count_ls = preparedStatement.executeUpdate();
System.out.println("影響了"+count_ls+"行");
} catch (SQLException e) {
throw new RuntimeException(e);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
preparedStatement.close();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
分析這段程式碼我們知道,如果在我的程式碼中有一個部分寫錯,比如ls的SQL語句寫錯沒有正確的執行,那麼會發生的情況是張三的錢少了1000元,但是李四的錢並沒有增加,看下面的程式碼:
package com.jpzhutech.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import org.junit.runners.ParentRunner;
public class transaction {
@SuppressWarnings("resource")
@Test
public void test(){
String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName=?";
//明顯我們可以看出李四的這段更新程式碼存在很大的問題,並不會被執行,但是張三的程式碼會被執行,結果只能是張三的錢繼續少1000元,而李四的錢並沒有增加
String sql_ls = "UPDATE1 account SET money=money+1000 WHERE accountName=?";
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
try {
String url = "jdbc:mysql://192.168.101.44/amon";
String user = "root";
String password = "560128";
connection = DriverManager.getConnection(url, user, password);
//System.out.println(connection);
// 執行sql_zs
//實際上當不寫時相當於下面的這行程式碼自動執行
//connection.setAutoCommit(true);
preparedStatement = connection.prepareStatement(sql_zs);
preparedStatement.setString(1, "zs");
int count_zs = preparedStatement.executeUpdate();
System.out.println("影響了"+count_zs+"行");
// 執行sql_ls
preparedStatement = connection.prepareStatement(sql_ls);
preparedStatement.setString(1, "ls");
int count_ls = preparedStatement.executeUpdate();
System.out.println("影響了"+count_ls+"行");
} catch (SQLException e) {
throw new RuntimeException(e);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
preparedStatement.close();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
從上面的兩段程式碼的對比我們可以知道,在我們寫SQL語句或者在其它的情況下可能會發生某個應該同時執行的語句並沒有做到我們想要的效果,那麼此時就會發生錯誤,程式設計師應該避免這種情況的發生,怎麼避免呢?在資料庫中使用事務的ACID特效能夠解決這種問題,下面將詳細的說明該怎麼解決。
解決方案
使用事務的ACID特效能夠避免此種情況的發生,但是用Java應用程式該怎麼做到呢?是否Java提供了這種機制呢?答案是肯定的,Java確實提供了這種機制,在Connection介面中有幾個方法能夠實現該種功能。
用到的API
Connection中存在下面幾個與事務相關的方法,分別是:
void commit()
void rollback()
void rollback(Savepoint savepoint)
void setAutoCommit(boolean autoCommit)
Savepoint setSavepoint(String name)
API使用方法
package com.jpzhutech.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import org.junit.Test;
import org.junit.runners.ParentRunner;
public class transaction {
@Test
public void test1(){
String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName=?";
String sql_ls = "UPDATE account SET money=money+1000 WHERE accountName=?";
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
try {
String url = "jdbc:mysql://192.168.101.44/amon";
String user = "root";
String password = "560128";
connection = DriverManager.getConnection(url, user, password);
//System.out.println(connection);
// 執行sql_zs
//實際上當不寫時相當於下面的這行程式碼自動執行
//connection.setAutoCommit(true);
//一 設定事務手動提交,也就是將自動提交關閉
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql_zs);
preparedStatement.setString(1, "zs");
int count_zs = preparedStatement.executeUpdate();
System.out.println("影響了"+count_zs+"行");
// 執行sql_ls
preparedStatement = connection.prepareStatement(sql_ls);
preparedStatement.setString(1, "ls");
int count_ls = preparedStatement.executeUpdate();
System.out.println("影響了"+count_ls+"行");
} catch (SQLException e) {
try {
//二 出現異常我希望回滾
connection.rollback();
} catch (SQLException e1) {
throw new RuntimeException(e);
}
throw new RuntimeException(e);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
//三 如果所有的操作執行成功
try {
connection.commit();
} catch (SQLException e1) {
}
try {
preparedStatement.close();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
@SuppressWarnings("resource")
@Test
public void test2(){
String sql_zs1 = "UPDATE account SET money=money-1000 WHERE accountName=?";
String sql_ls1 = "UPDATE account SET money=money+1000 WHERE accountName=?";
String sql_zs2 = "UPDATE account SET money=money-500 WHERE accountName=?";
String sql_ls2 = "UPDATE account SET money=money+500 WHERE accountName=?";
Connection connection = null;
PreparedStatement preparedStatement = null;
Savepoint savepoint = null;
try {
Class.forName("com.mysql.jdbc.Driver");
try {
String url = "jdbc:mysql://192.168.101.44/amon";
String user = "root";
String password = "560128";
connection = DriverManager.getConnection(url, user, password);
//System.out.println(connection);
// 執行sql_zs
//實際上當不寫時相當於下面的這行程式碼自動執行
//connection.setAutoCommit(true);
//一 設定事務手動提交,也就是將自動提交關閉
connection.setAutoCommit(false);
//第一次操作(此次操作是正確的)
preparedStatement = connection.prepareStatement(sql_zs1);
preparedStatement.setString(1, "zs");
preparedStatement.executeUpdate();
// 執行sql_ls
preparedStatement = connection.prepareStatement(sql_ls1);
preparedStatement.setString(1, "ls");
preparedStatement.executeUpdate();
//設定回滾位置
savepoint = connection.setSavepoint("transaction");
//第二次操作(此次操作是錯誤的)
preparedStatement = connection.prepareStatement(sql_zs2);
preparedStatement.setString(1, "zs");
preparedStatement.executeUpdate();
// 執行sql_ls
preparedStatement = connection.prepareStatement(sql_ls2);
preparedStatement.setString(1, "ls");
preparedStatement.executeUpdate();
} catch (SQLException e) {
try {
//二 出現異常我希望回滾
connection.rollback(savepoint);
} catch (SQLException e1) {
throw new RuntimeException(e);
}
throw new RuntimeException(e);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
//三 如果所有的操作執行成功
try {
connection.commit();
} catch (SQLException e1) {
}
try {
preparedStatement.close();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
總結
究竟什麼時候會用到事務程式設計,依具體情況而定,請讀者自己衡量,為了不失完整性我將此部分也全部進行了講解,後續部分會主鍵增加關於JDBC優化的知識。