1.什么是存儲過程
存儲過程(Stored Procedure)是在大型數據庫系統中,一組為了完成特定功能的SQL 語句集,存儲在數據庫中,經過第一次編譯后再次調用不需要再次編譯,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。存儲過程是數據庫中的一個重要對象。
2.存儲過程的優點
(1)存儲過程只在創造時進行編譯,以后每次執行存儲過程都不需再重新編譯,而一般SQL語句每執行一次就編譯一次,所以使用存儲過程可提高數據庫執行速度。
(2)當對數據庫進行復雜操作時(如對多個表進行Update,Insert,Query,Delete時),可將此復雜操作用存儲過程封裝起來與數據庫提供的事務處理結合一起使用。
(3)存儲過程可以重復使用,可減少數據庫開發人員的工作量
(4)安全性高,可設定只有某此用戶才具有對指定存儲過程的使用權
3.存儲過程的缺點
(1)如果更改范圍大到需要對輸入存儲過程的參數進行更改,或者要更改由其返回的數據,則您仍需要更新程序集中的代碼以添加參數、更新 GetValue() 調用,等等,這時候估計比較繁瑣了。
(2)可移植性差
由於存儲過程將應用程序綁定到 SQL Server,因此使用存儲過程封裝業務邏輯將限制應用程序的可移植性。
4.存儲過程數據庫操作(Navicat 操作)
4.1調用入參的存儲過程
(1)在navicat 的Student中 創建添加數據的函數(add_date)
public static void add(Student stu) throws Exception{
//通過工具類,獲取數據庫鏈接對象
Connection conn= DBUtil.getConn();
//創建 sql 語句
String sql = "insert Student (name,age,address) value (?,?,?)";
//創建 預加載 的sql 語句執行對象
PreparedStatement ptmt=conn.prepareStatement(sql);
//給名字賦值
ptmt.setString(1, stu.getName());
//給年齡賦值
ptmt.setInt(2, stu.getAge());
ptmt.setString(3, stu.getAddress());
//執行 sql 語句
ptmt.execute();
}
4.2調用出參存儲過程
(1)創建記錄數據條數的函數(get_record_count)
4.3調用無參存儲過程
(1)創建一個打印所有數據的函數(select_all)
(2)代碼實例
public class StudentProcedureDao {
/**
* 1、查詢數據
*/
public ResultSet select_all() {
ResultSet rs = null;
// 1 獲取數據庫連接對象
Connection conn = DBUtil.getConn();
try {
// 2 創建存儲過程調用對象
CallableStatement cs = conn.prepareCall("call select_all()");
// 3 執行存儲過程
cs.execute();
// 4 獲取結果集
rs = cs.getResultSet();
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
5.jdbc 存儲過程實例代碼如下:

//DBUtil 連接數據庫 package com.j1702.db; import java.sql.*; public class DBUtil { private static final String URL="jdbc:mysql://127.0.0.1:3306/jdbc_test"; private static final String USER="root"; private static final String PASSWORD=""; private static Connection conn = null; static { try { // 1.加載mysql驅動 Class.forName("com.mysql.jdbc.Driver"); // 2.獲得數據庫鏈接對象conn setConn(DriverManager.getConnection(URL, USER, PASSWORD)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static Connection getConn() { return conn; } public static void setConn(Connection conn) { DBUtil.conn = conn; } } //model 層 package com.j1702.model; public class Student { private Integer id; private String name; private Integer age; private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } //dao 層 package com.j1702.dao; import java.sql.*; import java.util.*; import com.j1702.db.DBUtil; import com.j1702.model.Student; public class StudentDao { //1.增 public static void add(Student stu) throws Exception{ //通過工具類,獲取數據庫鏈接對象 Connection conn= DBUtil.getConn(); //創建 sql 語句 String sql = "insert Student (name,age,address) value (?,?,?)"; //創建 預加載 的sql 語句執行對象 PreparedStatement ptmt=conn.prepareStatement(sql); //給名字賦值 ptmt.setString(1, stu.getName()); //給年齡賦值 ptmt.setInt(2, stu.getAge()); ptmt.setString(3, stu.getAddress()); //執行 sql 語句 ptmt.execute(); } //2.刪 public static void delete(Integer id) throws Exception{ //通過工具類,獲取數據庫鏈接對象 Connection conn= DBUtil.getConn(); //創建 sql 語句 String sql = " delete from Student where id =?"; //創建 預加載 的sql 語句執行對象 PreparedStatement ptmt=conn.prepareStatement(sql); //給名字賦值 ptmt.setInt(1,id); ptmt.execute(); } //3.改 public static void update(Student stu) throws Exception{ //通過工具類,獲取數據庫鏈接對象 Connection conn= DBUtil.getConn(); //創建 sql 語句 String sql = "update Student set name=?,age=?,address=? where id =?"; //創建 預加載 的sql 語句執行對象 PreparedStatement ptmt=conn.prepareStatement(sql); //給名字賦值 ptmt.setString(1, stu.getName()); //給年齡賦值 ptmt.setInt(2, stu.getAge()); ptmt.setString(3, stu.getAddress()); ptmt.setInt(4, stu.getId()); //執行 sql 語句 ptmt.execute(); } //4.查所有 public static void query(Student stu ) throws Exception{ //通過工具類,獲取數據庫鏈接對象 Connection conn= DBUtil.getConn(); //創建 sql 語句 String sql = "select * from Student"; //創建 預加載 的sql 語句執行對象 Statement stmt=conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:"+rs.getString("name")+"\tage"+ rs.getInt("age")+"\taddress"+rs.getString("address")); } } //5.查某個 public static void queryWithId(Integer id) throws Exception{ Connection conn= DBUtil.getConn(); //創建 sql 語句 String sql = "select * from Student where id=?"; //創建 預加載 的sql 語句執行對象 PreparedStatement ptmt= conn.prepareStatement(sql); ptmt.setInt(1, id); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress"+rs.getString("address")); } } //6.條件查詢 // public static void queryWithParam(List<Map<String,Object>>param) throws Exception{ // // Connection conn= DBUtil.getConn(); // //創建 sql 語句 // StringBuffer sql = new StringBuffer(); // sql.append("select * from Student where 1=1"); // for (Map<String, Object> map : param) { // sql.append(" and "+map.get("key") // +"="+map.get("value")); //創建 預加載 的sql 語句執行對象 // PreparedStatement ptmt= conn.prepareStatement(sql.toString()); // // ResultSet rs=ptmt.executeQuery(); // // while(rs.next()){ // System.out.println("id:"+rs.getInt("id")+"\t \tname:" // +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); // } // } public static void queryWithParam1(List<Map<String,Object>>param) throws Exception{ Connection conn= DBUtil.getConn(); //創建 sql 語句 StringBuffer sql = new StringBuffer(); sql.append("select * from Student where 1=1"); for (Map<String, Object> map : param) { sql.append(" and "+map.get("key") +" like '%"+map.get("value")+"%'"); //創建 預加載 的sql 語句執行對象 System.out.println(sql); PreparedStatement ptmt= conn.prepareStatement(sql.toString()); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); System.out.println(sql); } } } public static void queryWithParam2(List<Map<String,Object>>param) throws Exception{ Connection conn= DBUtil.getConn(); //創建 sql 語句 StringBuffer sql = new StringBuffer(); sql.append("select * from Student where 1=1"); for (Map<String, Object> map : param) { sql.append(" and "+map.get("key") +"="+map.get("value")); //創建 預加載 的sql 語句執行對象 PreparedStatement ptmt= conn.prepareStatement(sql.toString()); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); } } } public static void queryWithParam3(List<Map<String,Object>>param) throws Exception{ Connection conn= DBUtil.getConn(); //創建 sql 語句 StringBuffer sql = new StringBuffer(); sql.append("select * from Student where 1=1"); for (Map<String, Object> map : param) { sql.append(" and "+map.get("key") +"="+map.get("value")); //創建 預加載 的sql 語句執行對象 PreparedStatement ptmt= conn.prepareStatement(sql.toString()); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); } } } } //action 行動層 package com.j1702.action; import com.j1702.dao.StudentDao; import com.j1702.model.Student; //這一層是我們的controller 層:他是view和model層的服務員 public class StudentAction { //想數據庫中插入一條數據 public static void insert(Student stu) throws Exception{ StudentDao.add( stu); } //根據傳入的id 刪除學生 public static void delete(Integer id) throws Exception{ StudentDao.delete(id); } //更新學生信息數據 public static void update(Student stu) throws Exception{ StudentDao.update(stu); } //查找Student public static void find(Integer id) throws Exception{ StudentDao.queryWithId(id); } } //view 視圖界面層 package com.j1702.view; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import com.j1702.dao.*; import com.j1702.db.DBUtil; import com.j1702.model.Student; public class TestView { public static void main(String[] args) throws Exception{ //編程以終端為view 層,實現數據的增、刪、改 /** * 請輸入你要做的操作:A-添加,D-刪除,U更新,F-查詢,exit-退出 * * A 請輸入插入的數據 name,age,address * */ login(); } public static void login(){ System.out.println("請輸入你要做的操作:A-添加,D-刪除,U更新,F-查詢,exit-退出"); Scanner in= new Scanner (System.in); String xz=in.next(); switch(xz){ case "A" : try { add(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case "D": try { delete(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case "U": try { update(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case "F" : find(); break; case "exit": exit(); break; default : System.out.println("沒有你輸入的選項,請重新選擇"); login(); } } public static void add() throws Exception{ Student stu=new Student(); System.out.println("請輸入插入的數據 name:"); Scanner cn=new Scanner (System.in); String name=cn.next(); System.out.println("請輸入插入的數據 age:"); Scanner ca=new Scanner (System.in); int age=ca.nextInt(); System.out.println("請輸入插入的數據 address :"); Scanner cd=new Scanner (System.in); String address = cd.next(); stu.setName(name); stu.setAge(age); stu.setAddress(address); StudentDao l1 =new StudentDao(); l1.add(stu); System.out.println("數據插入成功!"); System.out.println("請選擇:1 繼續插入數據, 2.返回上一層"); Scanner in= new Scanner (System.in); int x2=in.nextInt(); switch (x2) { case 1: add(); break; case 2: login(); break; default: System.out.println("沒有你要的選項,請重新輸入!"); } } public static void delete() throws Exception{ Connection conn= DBUtil.getConn(); System.out.println("請輸入你要刪除數據的ID"); Scanner in=new Scanner (System.in); Integer xid=in.nextInt(); StudentDao l2=new StudentDao(); l2.delete(xid); System.out.println("數據刪除成功!"); System.out.println("請選擇:1 繼續繼續數據, 2.返回上一層"); Scanner in1= new Scanner (System.in); int x2=in1.nextInt(); switch (x2) { case 1: delete(); break; case 2: login(); break; default: System.out.println("沒有你要的選項,請重新輸入!"); } } public static void update() throws Exception{ Student stu1=new Student(); System.out.println("請輸入修改的數據 ID:"); Scanner ci=new Scanner (System.in); int id=ci.nextInt(); System.out.println("請輸入修改的數據 name:"); Scanner cn1=new Scanner (System.in); String name=cn1.next(); System.out.println("請輸入修改的數據 age:"); Scanner ca1=new Scanner (System.in); int age=ca1.nextInt(); System.out.println("請輸入修改的數據 address :"); Scanner cd1=new Scanner (System.in); String address = cd1.next(); stu1.setId(id); stu1.setName(name); stu1.setAge(age); stu1.setAddress(address); StudentDao l3=new StudentDao(); l3.update(stu1); System.out.println("數據修改成功!"); System.out.println("請選擇:1 繼續修改數據, 2.返回上一層"); Scanner in1= new Scanner (System.in); int x2=in1.nextInt(); switch (x2) { case 1: update(); break; case 2: login(); break; default: System.out.println("沒有你要的選項,請重新輸入!"); } } public static void find(){ System.out.println("請選擇:1.查詢所有 2.查詢某個 3.條件查詢"); Scanner in= new Scanner (System.in); int xz3=in.nextInt(); switch(xz3){ case 1: Student stu=new Student(); StudentDao st=new StudentDao(); try { st.query(stu); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf(); break; case 2: System.out.println("請輸入要查詢的ID:"); Scanner cid=new Scanner(System.in); Integer id1=cid.nextInt(); try { StudentDao st1=new StudentDao(); st1.queryWithId(id1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf(); break; case 3: tiaojian(); break; } } public static void exit(){ System.out.println("謝謝使用,再見!"); return; } public static void cgf(){ System.out.println("數據查詢成功!"); System.out.println("請選擇:1 繼續查詢數據, 2.返回上一層"); Scanner in3= new Scanner (System.in); int x3=in3.nextInt(); switch (x3) { case 1: find(); break; case 2: login(); break; default: System.out.println("沒有你要的選項,請重新輸入!"); } } public static void cgf1(){ System.out.println("數據查詢成功!"); System.out.println("請選擇:1 繼續查詢數據, 2.返回上一層"); Scanner in3= new Scanner (System.in); int x3=in3.nextInt(); switch (x3) { case 1: tiaojian(); break; case 2: login(); break; default: System.out.println("沒有你要的選項,請重新輸入!"); } } public static void tiaojian(){ System.out.println("請選擇要查詢的字段:1.id 查詢 2.name 查詢 3.age 查詢 4.address 查詢"); Scanner tjcx=new Scanner(System.in); int tx3=tjcx.nextInt(); switch(tx3){ case 1: System.out.println("請輸入要查詢的ID:"); Scanner cid=new Scanner(System.in); Integer id1=cid.nextInt(); try { StudentDao st1=new StudentDao(); st1.queryWithId(id1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; case 2: System.out.println("請輸入你要查詢的 name:"); Scanner cn1=new Scanner(System.in); Map<String,Object> map = new HashMap<String,Object>(); map.put("key", "name"); map.put("value",cn1.next()); List<Map<String,Object>> li= new ArrayList<Map<String,Object>>(); li.add(map); try { StudentDao l4=new StudentDao(); l4.queryWithParam1(li); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; case 3: System.out.println("請輸入你要查詢的 age:"); Scanner cn2=new Scanner(System.in); String sage=cn2.next(); Map<String,Object> map1 = new HashMap<String,Object>(); map1.put("key", "age"); map1.put("value",sage); List<Map<String,Object>> li1= new ArrayList<Map<String,Object>>(); li1.add(map1); try { StudentDao l4=new StudentDao(); l4.queryWithParam2(li1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; case 4: System.out.println("請輸入你要查詢的 address:"); Scanner cn3=new Scanner(System.in); String sadd=cn3.next(); Map<String,Object> map2 = new HashMap<String,Object>(); map2.put("key", "address"); map2.put("value",sadd); List<Map<String,Object>> li2= new ArrayList<Map<String,Object>>(); li2.add(map2); try { StudentDao l4=new StudentDao(); l4.queryWithParam3(li2); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; } } }
6.java JDBC 事務管理
6.1事務的基本概念:事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
6.2數據庫開啟事務命令
方式一:利用SQL語句管理事務
start transaction;--開啟事務,這條語句之后的sql語句將處在一個事務當中,這些sql語句並不會立即執行
Commit--提交事務,一旦提交事務,事務中的所有sql語句才會執行。
Rollback -- 回滾事務,將之前所有的sql取消。
方式二:在數據庫中存在一個自動提交變量,通過show variables like '%commit%'-----autocommit 值是on,說明開啟了事務自動提交。
可以 set autocommint = off(set autocommint=0),關閉自動提交,此時輸入的sql語句就不會自動提交了,需要手動roolback或commit
6.3使用事務
(1)當Jdbc程序向數據庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行,可使用下列語句:
(2)JDBC控制事務語句
Connection.setAutoCommit(false); // 相當於start transaction
Connection.rollback(); rollback
Connection.commit(); commit
(3)事務管理實例代碼:
(1)當Jdbc程序向數據庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行,可使用下列語句:
(2)JDBC控制事務語句
Connection.setAutoCommit(false); // 相當於start transaction
Connection.rollback(); rollback
Connection.commit(); commit
7.事務的特性(ACID)
(1)原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
(2)一致性(Consistency)
事務前后數據的完整性必須保持一致。
(3)隔離性(Isolation)
事務的隔離性是指多個用戶並發訪問數據庫時,一個用戶的事務不能被其它用戶的事務所干擾,多個並發事務之間數據要相互隔離。
(4)持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響
8.事務隔離級別(Transaction Isolation Levels)
JDBC定義了五種事務隔離級別:
TRANSACTION_NONE JDBC 驅動不支持事務
TRANSACTION_READ_UNCOMMITTED 允許臟讀、不可重復讀和幻讀。
TRANSACTION_READ_COMMITTED 禁止臟讀,但允許不可重復讀和幻讀。
TRANSACTION_REPEATABLE_READ 禁止臟讀和不可重復讀,單運行幻讀。
TRANSACTION_SERIALIZABLE 禁止臟讀、不可重復讀和幻讀。
9.事務隔離性的設置語句
數據庫共定義了四種隔離級別:
Serializable:可避免臟讀、不可重復讀、虛讀情況的發生。(串行化)
Repeatable read:可避免臟讀、不可重復讀情況的發生。(可重復讀)不可以避免虛讀
Read committed:可避免臟讀情況發生(讀已提交)
Read uncommitted:最低級別,以上情況均無法保證。(讀未提交)
set [global/session] transaction isolation level 設置事務隔離級別
select @@tx_isolation查詢當前事務隔離級別
安全性來說:Serializable>Repeatable read>Read committed>Read uncommitted
效率來說:Serializable<Repeatable read<Read committed<Read uncommitted
通常來說,一般的應用都會選擇Repeatable read或Read committed作為數據庫隔離級別來使用。
mysql默認的數據庫隔離級別為:REPEATABLE-READ
如何查詢當前數據庫的隔離級別?select @@tx_isolation;
如何設置當前數據庫的隔離級別?set [global/session] transaction isolation level ...;
~此種方式設置的隔離級別只對當前連接起作用。
set transaction isolation level read uncommitted;
set session transaction isolation level read uncommitted;
~此種方式設置的隔離級別是設置數據庫默認的隔離級別
set global transaction isolation level read uncommitted;
10.JDBC事務的優缺點
JDBC為使用Java進行數據庫的事務操作提供了最基本的支持。通過JDBC事務,我們可以將多個SQL語句放到同一個事務中,保證其ACID特性。JDBC事務的主要優點就是API比較簡單,可以實現最基本的事務操作,性能也相對較好。
但是,JDBC事務有一個局限:一個 JDBC 事務不能跨越多個數據庫!!!
所以,如果涉及到多數據庫的操作或者分布式場景,JDBC事務就無能為力了。
11.java JDBC 事務實例代碼如下:

//db 層 package com.j1702.db; import java.sql.*; public class DBUtil { private static final String URL = "jdbc:mysql://127.0.0.1:3306/jdbc_trading"; private static final String USER = "root"; private static final String PASSWORD = ""; private static Connection conn = null; // (static{}(即static塊),會在類被加載的時候執行且僅會被執行一次,一般用來初始化靜態變量和調用靜態方法,) static { try { // 1 加載驅動程序 Class.forName("com.mysql.jdbc.Driver"); // 2 獲得數據的連接 conn = DriverManager.getConnection(URL, USER, PASSWORD); } catch (ClassNotFoundException e) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } catch (SQLException e) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } } // 靜態方法獲取連接 public static Connection geConnection() { return conn; } } //model 層 package com.j1702.model; // 轉賬記錄表的映射 public class Transfer { private Integer id; // 主鍵 private String from_where; // 轉賬人 private String to_where; // 收款人 private Integer money; // 轉賬金額 private String time; // 轉賬時間 // 無參構造 public Transfer(){} // 有參構造 public Transfer( String from_where, String to_where, Integer money) { this.from_where = from_where; this.to_where = to_where; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getFrom_where() { return from_where; } public void setFrom_where(String from_where) { this.from_where = from_where; } public String getTo_where() { return to_where; } public void setTo_where(String to_where) { this.to_where = to_where; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } } package com.j1702.model; public class User { private Integer id; // 主鍵 private String name; // 用戶名 private Integer money; // 用戶余額 public User(){} public User(String name, Integer money){ this.name = name; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } } // dao 層 package com.j1702.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import com.j1702.db.DBUtil; import com.j1702.model.Transfer; public class TransferDao { // 增加 public void addRecord(Transfer t) throws SQLException { Connection conn = DBUtil.geConnection(); String sql = "insert Transfer (from_where,to_where,money) values(?,?,?)"; PreparedStatement ptmt = conn.prepareStatement(sql); ptmt.setString(1, t.getFrom_where()); ptmt.setString(2, t.getTo_where()); ptmt.setInt(3, t.getMoney()); ptmt.execute(); } // 查詢 public List<Transfer> queryTransfers() throws Exception { Connection conn = DBUtil.geConnection(); Statement stmt = conn.createStatement(); ResultSet res = stmt.executeQuery("select * from Transfer"); List<Transfer> t_list = new ArrayList<Transfer>(); Transfer t = null; while (res.next()) { t = new Transfer(); t.setFrom_where(res.getString("from_where")); t.setTo_where(res.getString("to_where")); t.setMoney(res.getInt("money")); t.setTime(res.getString("time")); t_list.add(t); } return t_list; } } package com.j1702.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import com.j1702.db.DBUtil; import com.j1702.model.User; public class UserDao { // 修改(因為轉賬會發生 money 的增加或減少) public void updateUser(User u) throws SQLException { Connection conn = DBUtil.geConnection(); String sql = "update User set money=? where name=?"; PreparedStatement ptmt = conn.prepareStatement(sql); ptmt.setInt(1, u.getMoney()); ptmt.setString(2, u.getName()); ptmt.execute(); } // 查詢 public User queryUser(String name) throws Exception { Connection conn = DBUtil.geConnection(); String sql = "select * from User where name=?"; PreparedStatement ptmt = conn.prepareStatement(sql); ptmt.setString(1, name); ResultSet res = ptmt.executeQuery(); User u = new User(); while (res.next()) { u.setName(res.getString("name")); u.setMoney(res.getInt("money")); } return u; } } package com.j1702.action; import java.util.List; import com.j1702.dao.TransferDao; import com.j1702.model.Transfer; public class TransferAction { // 1 新增轉賬記錄 public void add(Transfer t) throws Exception{ TransferDao dao = new TransferDao(); dao.addRecord(t); } // 2 查詢轉賬記錄 public List<Transfer> query() throws Exception{ TransferDao dao = new TransferDao(); return dao.queryTransfers(); } } package com.j1702.action; import java.sql.SQLException; import com.j1702.dao.UserDao; import com.j1702.model.User; public class UserAction { public User query(String name) throws Exception{ UserDao dao = new UserDao(); return dao.queryUser(name); } public void update(User u) throws SQLException{ UserDao dao = new UserDao(); dao.updateUser(u); } } //view 層 package com.j1702.view; import java.sql.Connection; import java.util.List; import java.util.Scanner; import com.j1702.action.TransferAction; import com.j1702.action.UserAction; import com.j1702.db.DBUtil; import com.j1702.model.Transfer; import com.j1702.model.User; public class TestView { public static void main(String[] args) throws Exception { Scanner scan = new Scanner(System.in); while(true){ System.out.println("請操作指令:1-轉賬;2-查詢;其他任意鍵退出"); String operator = scan.nextLine(); switch (operator) { case "1": { System.out.println("請輸入付款人姓名:"); String from_name = scan.nextLine(); System.out.println("請輸入收款人姓名:"); String to_name = scan.nextLine(); System.out.println("請輸入轉賬金額:"); Integer money = scan.nextInt(); scan.nextLine(); Transfer tf = new Transfer(from_name, to_name, money); // 處理數據庫操作 UserAction ua = new UserAction(); // 查詢 User u1 = ua.query(from_name); User u2 = ua.query(to_name); // 操作金額的增減 u1.setMoney(u1.getMoney()-money); u2.setMoney(u2.getMoney()+money); // 更新 需要添加事務 Connection conn = DBUtil.geConnection(); //設置自動提交為false conn.setAutoCommit(false); //需要綁定執行的代碼 ua.update(u1); ua.update(u2); // 增加轉賬記錄 TransferAction ta = new TransferAction(); ta.add(tf); //手動提交事務 conn.commit(); } break; case "2": { TransferAction ta = new TransferAction(); List<Transfer> l_t = ta.query(); for (Transfer transfer : l_t) { System.out.println(transfer.getTime() + "\t->\t" + transfer.getFrom_where() + "\t->\t" + transfer.getTo_where() + "\t->\t" + transfer.getMoney() ); } } break; default: { System.out.println("退出!"); scan.close(); System.exit(0); } break; } } } }
12.JTA事務
為什么需要JTA
通常,JDBC事務就可以解決數據的一致性等問題,鑒於他用法相對簡單,所以很多人關於Java中的事務只知道有JDBC事務,或者有人知道框架中的事務(比如Hibernate、Spring)等。但是,由於JDBC無法實現分布式事務,而如今的分布式場景越來越多,所以,JTA事務就應運而生。
如果,你在工作中沒有遇到JDBC事務無法解決的場景,那么只能說你做的項目還都太小。拿電商網站來說,我們一般把一個電商網站橫向拆分成商品模塊、訂單模塊、購物車模塊、消息模塊、支付模塊等。然后我們把不同的模塊部署到不同的機器上,各個模塊之間通過遠程服務調用(RPC)等方式進行通信。以一個分布式的系統對外提供服務。
一個支付流程就要和多個模塊進行交互,每個模塊都部署在不同的機器中,並且每個模塊操作的數據庫都不一致,這時候就無法使用JDBC來管理事務。我們看一段代碼:
/** 支付訂單處理 **/ @Transactional(rollbackFor = Exception.class) public void completeOrder() { orderDao.update(); // 訂單服務本地更新訂單狀態 accountService.update(); // 調用資金賬戶服務給資金帳戶加款 pointService.update(); // 調用積分服務給積分帳戶增加積分 accountingService.insert(); // 調用會計服務向會計系統寫入會計原始憑證 merchantNotifyService.notify(); // 調用商戶通知服務向商戶發送支付結果通知 }
上面的代碼是一個簡單的支付流程的操作,其中調用了五個服務,這五個服務都通過RPC的方式調用,請問使用JDBC如何保證事務一致性?我在方法中增加了@Transactional
注解,但是由於采用調用了分布式服務,該事務並不能達到ACID的效果。
JTA事務比JDBC事務更強大。一個JTA事務可以有多個參與者,而一個JDBC事務則被限定在一個單一的數據庫連接。下列任一個Java平台的組件都可以參與到一個JTA事務中:JDBC
連接、JDO PersistenceManager
對象、JMS
隊列、JMS
主題、企業JavaBeans(EJB
)、一個用J2EE Connector Architecture
規范編譯的資源分配器。
JTA的定義
Java事務API(Java Transaction API
,簡稱JTA ) 是一個Java企業版 的應用程序接口,在Java環境中,允許完成跨越多個XA資源的分布式事務。
JTA和它的同胞Java事務服務(JTS;Java TransactionService),為J2EE平台提供了分布式事務服務。不過JTA只是提供了一個接口,並沒有提供具體的實現,而是由j2ee服務器提供商 根據JTS規范提供的,常見的JTA實現有以下幾種:
- 1.J2EE容器所提供的JTA實現(JBoss)
- 2.獨立的JTA實現:如JOTM,Atomikos.這些實現可以應用在那些不使用J2EE應用服務器的環境里用以提供分布事事務保證。如Tomcat,Jetty以及普通的java應用。
JTA里面提供了 java.transaction.UserTransaction
,里面定義了下面幾個方法
begin
:開啟一個事務
commit
:提交當前事務
rollback
:回滾當前事務
setRollbackOnly
:把當前事務標記為回滾
setTransactionTimeout
:設置事務的事件,超過這個事件,就拋出異常,回滾事務
這里,值得注意的是,不是使用了UserTransaction
就能把普通的JDBC操作直接轉成JTA操作,JTA對DataSource、Connection和Resource 都是有要求的,只有符合XA規范,並且實現了XA規范的相關接口的類才能參與到JTA事務中來,關於XA規范,請看我的另外一篇文章中有相關介紹。這里,提一句,目前主流的數據庫都支持XA規范。
要想使用用 JTA 事務,那么就需要有一個實現
javax.sql.XADataSource
、javax.sql.XAConnection
和javax.sql.XAResource
接口的 JDBC 驅動程序。一個實現了這些接口的驅動程序將可以參與 JTA 事務。一個XADataSource
對象就是一個XAConnection
對象的工廠。XAConnection
是參與 JTA 事務的 JDBC 連接。要使用JTA事務,必須使用
XADataSource
來產生數據庫連接,產生的連接為一個XA連接。XA連接(
javax.sql.XAConnection
)和非XA(java.sql.Connection
)連接的區別在於:XA可以參與JTA的事務,而且不支持自動提交。
public void JtaTransfer() { javax.transaction.UserTransaction tx = null; java.sql.Connection conn = null; try{ tx = (javax.transaction.UserTransaction) context.lookup("java:comp/UserTransaction"); //取得JTA事務,本例中是由Jboss容器管理 javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup("java:/XAOracleDS"); //取得數據庫連接池,必須有支持XA的數據庫、驅動程序 tx.begin(); conn = ds.getConnection(); // 將自動提交設置為 false, //若設置為 true 則數據庫將會把每一次數據更新認定為一個事務並自動提交 conn.setAutoCommit(false); stmt = conn.createStatement(); // 將 A 賬戶中的金額減少 500 stmt.execute("\ update t_account set amount = amount - 500 where account_id = 'A'"); // 將 B 賬戶中的金額增加 500 stmt.execute("\ update t_account set amount = amount + 500 where account_id = 'B'"); // 提交事務 tx.commit(); // 事務提交:轉賬的兩步操作同時成功