世界萬事無簡單一說, 每個事情基本上由多個小的事情來完成。有的事情會存在若小的事情不能同時完成的情況就取消所有的小的事情,直至都完成達到預期的效果才算完成!這樣就用到了事務操作。在所有的sql語句完成之前,若發生異常,則讓事務回滾到開始事務的時候,讓事務結束;並且讓已執行的sql語句作廢。但是連接數據庫的connnection與開啟事務的connnection必須是一個。這樣原來在dao層開啟連接必須到service層一起執行了開啟事務和處理異常。
一、事務:
一件事情有n個組成單元 :要不這n個組成單元同時成功, 要不n個單元就同時失敗。就是將n個組成單元放到一個事務中!
二、mysql事務:
1、默認的事務:一條sql語句就是一個事務 。默認就開啟事務並提交事務!
2、手動事務:
①、顯示的開啟一個事務:start transaction
此時進行的所有的修改都是內存里修改的,具體數據庫內到底修改了沒還是由是否提交來決定的!!!
當提交了就代表數據庫修改了,當回滾了則沒有修改!
但是需要注意的是無論數據庫到底修改了沒有,只要執行了SQL語句,在庫內的表里的自增的序號會自動被占用了!!!
②、事務提交:commit
代表從開啟事務到事務提交中間的所有的sql都認為有效! 真正的更新數據庫!
③、事務的回滾:rollback
代表事務的回滾--在其之前的所有的操作都作廢了!回滾到start的地方,同時當前的事務結束了!
三、JDBC事務操作:
1、默認是自動事務:
執行sql語句:executeUpdate() ---- 每執行一次executeUpdate方法 代表 事務自動提交(默認)<執行一句sql就是一個提交>
2、通過jdbc的API手動事務:
①、開啟事務:conn.setAutoCommit(false);
此為設置為自動提交(改為false,也就是改為手動提交)
②、提交事務:conn.commit();
③、回滾事務:conn.rollback(); (都被封裝成了方法)
注意:
控制事務的connnection必須是同一個!
執行sql的connection與開啟事務的connnection必須是同一個才能對事務進行控制!!!
public static void main(String[] args) { Connection conn = JDBCUtils.getConn(); Statement sta = null; // Statement sta = conn.createStatement(); String sql = "insert into account(aname,money) values('wangwu',1000)"; // 手動開啟事務 try { sta = conn.createStatement(); conn.setAutoCommit(false); sta.executeUpdate(sql); } catch (SQLException e) { // 回滾 try { conn.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } finally {//注意好位置 try { // 提交事務 conn.commit(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }
注:
Statement 接口提供了執行語句和獲取結果的基本方法。PreparedStatement 接口添加了處理 IN 參數的方法;而 CallableStatement 添加了處理 OUT 參數的方法。
四、DBUtils事務操作:
QueryRunner:
1、有參構造:QueryRunner runner = new QueryRunner(DataSource dataSource);
有參構造將數據源(連接池)作為參數傳入QueryRunner,QueryRunner會從連 接池中獲得一個數據庫連接資源操作數據庫,所以直接使用無Connection參數 的update方法即可操作數據庫
2、無參構造:QueryRunner runner = new QueryRunner();-
無參的構造沒有將數據源(連接池)作為參數傳入QueryRunner,那么我們在使 用QueryRunner對象操作數據庫時要使用有Connection參數的方法
<因為有參無法保證connection的唯一性,所以需要無參構造---並且需要手動獲取連接>
public class MyDBUtils { public static final String DRIVER = "com.mysql.jdbc.Driver"; public static final String URL = "jdbc:mysql://localhost:3306/java0603?useUnicode=true&characterEncoding=UTF-8"; public static final String USERNAME = "root"; public static final String PASSWORD = "123456"; /* * 創建連接池BasicDataSource */ public static BasicDataSource dataSource = new BasicDataSource(); //靜態代碼塊(優先只執行一次) static { //對連接池對象 進行基本的配置 dataSource.setDriverClassName(DRIVER); // 這是要連接的數據庫的驅動 dataSource.setUrl(URL); //指定要連接的數據庫地址 dataSource.setUsername(USERNAME); //指定要連接數據的用戶名 dataSource.setPassword(PASSWORD); //指定要連接數據的密碼 } /* * 返回連接池對象 */ public static DataSource getDataSource(){ return dataSource; } //返回一個連接對象 public static Connection getConn(){ Connection conn=null; try { conn= dataSource.getConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return conn; } }
public static void main(String[] args) { //獲取連接池對象 QueryRunner qr = new QueryRunner(); //獲取連接對象 Connection conn = MyDBUtils.getConn(); String sql = "update account set money = money - ? where aname = ?"; try { //開啟事務 conn.setAutoCommit(false); qr.update(conn,sql,100,"zhangsan"); } catch (SQLException e) { //回滾事務 try { conn.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // TODO Auto-generated catch block e.printStackTrace(); }finally{ //提交事務 try { conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
五、轉賬實例:
public class AccountDao { // 轉出--需要和service層同一個conn 所以需要傳值 public void Moneyout( Connection conn,String out, double money) throws SQLException { QueryRunner qr = new QueryRunner(); String sql = "update account set money = money - ? where aname = ?"; qr.update(conn, sql, money, out); } // 轉入 public void Moneyin(Connection conn,String in, double money) throws SQLException { QueryRunner qr = new QueryRunner(); String sql = "update account set money = money + ? where aname = ?"; qr.update(conn, sql, money, in); } }
public class AccountService { private AccountDao accountDao = new AccountDao(); // 轉賬--需要在此層處理事務,所以在此層獲得CONN,再把這個參數傳給dao層 public boolean transfer(String out, String in, double money) { // 定義變量 boolean flag = true; Connection conn = MyDBUtils.getConn(); try { // 開啟事務 conn.setAutoCommit(false); accountDao.Moneyout(conn, out, money); accountDao.Moneyin(conn, in, money); } catch (SQLException e) { flag = false; // 如果出現異常,則flag返回一個false // 回滾(當try出現異常后會執行catch,然后回滾到開啟事務之前) try { conn.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } e.printStackTrace(); } finally { // 提交 try { conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return flag; } }
public class TransferServlet extends HttpServlet { private AccountService accountService =new AccountService(); public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲取頁面上值 //解決中文亂碼 request.setCharacterEncoding("UTF-8"); //獲取轉出賬戶 String out = request.getParameter("out"); //獲取轉入賬戶 String in = request.getParameter("in"); //獲取轉賬金額(從前台獲取的都是String類型) String moneyStr = request.getParameter("money"); //將字符串金額轉成double double money = Double.parseDouble(moneyStr); //調用 Service層的轉賬方法 boolean flag = accountService.transfer(out, in, money); //解決response亂碼 response.setContentType("text/html;charset=utf-8"); if(flag){ response.getWriter().write("轉賬成功"); }else{ response.getWriter().write("轉賬失敗"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
五、事務的特性和隔離級別:事務的特性ACID
1、原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作 要么都發生,要么都不發生。
2、一致性(Consistency)
一個事務中,事務前后數據的完整性必須保持一致。
3、隔離性(Isolation)
多個事務,事務的隔離性是指多個用戶並發訪問數據庫時,一個用戶的事務不能被其它用戶的事務所干擾,多個並發事務之間數據要相互隔離。
4、持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變 就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。