1. 程式人生 > >JDBC高階開發事務

JDBC高階開發事務

1.事務的引入

1.1 轉賬業務
案例分析:之前給大家介紹過三層開發,相信大家對三層有了基本的瞭解,接下來我們就使用三層的開發模式來完成轉賬的案例1 首先在這裡,我們web層就直接通過main方法,直接填充資料,呼叫service層的業務邏輯。2 而service業務邏輯層主要完成轉賬的核心業務邏輯操作,包括轉出和轉入的2個核心操作。3 很明顯,轉入和轉出是和資料庫打交道的,所以需要將轉入和轉出這2個操作放在dao層。大體分工如下圖所示:
在這裡插入圖片描述

1.2 程式碼實現
(1)準備資料

create table account(
 id int primary key auto_increment,
 name varchar(20),
 money double
);

insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
 <default-config>
  <property name="driverClass">com.mysql.jdbc.Driver</property>
     <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
     <property name="user">root</property>
     <property name="password">root</property>
 </default-config>
</c3p0-config>

(2)dao層

public class AccountDao {
 
 /**
  * 在開發中,dao層的異常一般都拋,在service層抓
  * @param outUser 付款人
  * @param money 付款金額
  * @throws SQLException
  */
 public void outMoney(String outUser,int money) throws SQLException{
  //1.建立QueryRunner物件
  QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
  //2.執行sql語句
  String sql = "update account set money = money-? where name=?";
  queryRunner.update(sql, money,outUser);
 }
 
 /**
  * 收款操作
  * @param inUser 收款人的姓名
  * @param money  收款金額
  * @throws SQLException
  */
 public void inMoney(String inUser,int money) throws SQLException{
  //首先建立QueryRunner物件
  QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
  //執行sql語句
  String sql = "update account set money = money+? where name=?";
  queryRunner.update(sql, money,inUser);
 }
}

(3)service層

public class AccountService {
 /**
  * 注意:我們需要保證service層的connection物件和dao層的是同一個.
  * dao層不能關閉connection物件,connection物件的關閉放在servcie層中
  * @param outUser
  * @param inUser
  * @param money
  */
 public void transfer(String outUser,String inUser,int money){
  AccountDao accountDao = new AccountDao();
  try {
   //付款
   accountDao.outMoney(outUser, money);
   //收款
   accountDao.inMoney(inUser, money);
   con.commit();
   System.out.println("轉賬成功");
  } catch (SQLException e) {
   System.out.println("轉賬失敗");
   e.printStackTrace();
  }
 }
}

(4)web層

public class WebTest {
 public static void main(String[] args) {
  String outUser = "jack";
  String inUser = "rose";
  int money = 100;
  //直接呼叫service層的方法完成轉賬的操作
  AccountService accountService = new AccountService();
  accountService.transfer(outUser, inUser, money);
 }
}

思考:在上面的案例中,在業務層裡面,假設在一個人付款成功,另一個人還沒有收款的時候,中間有其他業務操作的話,出現了異常,也就是在付款和收款之間加上如下圖所示程式碼,模擬轉賬的異常。
在這裡插入圖片描述
那麼這時候很明顯,程式終止,而後面收款的操作並不會去做,那麼另一個人就不會收到錢,而前一個人確確實實轉賬了。所以最終得出。上面的轉賬業務邏輯是存在bug的。那麼針對這樣的一個bug我們該如何去處理呢?

針對上面出現的問題的分析:上面的bug,因為異常的原因,導致了我們的一個完整的業務邏輯被中斷了。所以導致只執行了一條sql語句,後一條sql語句沒有去執行。所以,我們要保證,我們一個完整的業務邏輯要麼全部跑完,如果中間出現異常的話,為了保證最終的結果符合我們實際生活的一個情況,所以我們需要撤銷與當前業務邏輯有關的操作。所以這就需要學習接來下的事務來保證這樣一個情況。

2.事務概述

事務指的是邏輯上的一組操作,組成這組操作的各個單元要麼全都成功,要麼全都失敗.
事務作用:保證在一個事務中多次操作要麼全都成功,要麼全都失敗.

事務的出現 解決上面的問題。
1.事務是如何處理正常情況的呢?
在這裡插入圖片描述
2.事務是如何處理異常情況的呢?
在這裡插入圖片描述

2.1 mysql事務操作
在這裡插入圖片描述
MYSQL中可以有兩種方式進行事務的管理:

  • 自動提交:Mysql預設自動提交,執行一條sql語句可提交一次事務
  • 手動提交:先開啟,再提交

方式1:手動提交

-- 開啟事務
start transaction;
update account set money = 100;
select * from account;
-- 提交事務
commit;
-- 事務一旦提交就不能回滾了。
rollback;
start transaction;
update account set money = 100;
-- 回滾事務
rollback;
select * from account;

方式2:自動提交,通過修改mysql全域性變數“autocommit”進行控制
show variables like ‘%commit%’;
在這裡插入圖片描述

  • 設定自動提交的引數為OFF:set autocommit = 0; – 0:OFF 1:ON

擴充套件:Oracle資料庫事務不自動提交

2.2 JDBC事務操作
在這裡插入圖片描述
注意:在jdbc事務操作用,事務的控制都是通過Connection物件完成的,當一個完整的業務操作前,我們首先使用connection.setAutoCommit(false)來開啟事務,當業務操作完成之後,我們需要使用connection.commit()來提交事務。當然了,如果出現了異常,我們需要撤銷所有的操作,所以出現異常,需要進行事務的回滾。

2.3 DBUtils事務操作
在這裡插入圖片描述
過程與jdbc操作一樣,之不過需要注意的是,connection必須手動控制,不能交給DBUtils去控制。

2.4 事務管理:傳遞Connection
修改service和dao,service將connection傳遞給dao,dao不需要自己獲得連線
在這裡插入圖片描述
很明顯,我們的事務是針對整個業務邏輯的。所以事務的管理應該在業務層進行管理。並且,需要注意的是,我們在整個事務的控制過程中應該是一個connection物件。所以,在業務層我們需要拿到connection物件,用connection物件對業務層進行事務處理。而既然在業務層獲取了connection物件,那麼就必須要保證我們在dao層拿到的connection物件和業務層拿到的connection物件是同一個,那麼這裡我們可以使用傳遞引數的方式將connection物件傳遞給dao層。
所以dao層需要修改,connection物件需要從業務層傳遞過來。並且需要注意的是,connection物件是不能在dao層關閉的,因為我們業務層還需要繼續使用connection物件。所以dao層程式碼的修改如下:

dao層:

public class AccountDao {
 
 public void outMoney(Connection con,String outUser,int money) throws SQLException{
  PreparedStatement prepareStatement = con.prepareStatement("update account set money=money-? where name=?");
  prepareStatement.setInt(1, money);
  prepareStatement.setString(2,outUser);
  prepareStatement.executeUpdate();
  JdbcUtils.release(null, null , prepareStatement);
 }
 
 public void inMoney(Connection con,String inUser,int money) throws SQLException{
  PreparedStatement prepareStatement = con.prepareStatement("update account set money=money-? where name=?");
  prepareStatement.setInt(1, money);
  prepareStatement.setString(2,inUser);
  prepareStatement.executeUpdate();
  JdbcUtils.release(null, null , prepareStatement);
 }
}

通過ThreadLocal來儲存connection

public class JdbcUtils {
 //建立一個數據庫連線池
 private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 //建立一個threadLocal物件
 public static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
 
 public static DataSource getDataSource(){
  return dataSource;
 }
 //連線的獲取從資料庫連線池獲取
 public static Connection getConnection(){
  Connection con = null;
  try {
   //connection物件先從local中獲取
   con = local.get();
   if(con == null){
    //表示第一次在當前執行緒中獲取connection物件。第一次獲取連線需要從資料庫連線池拿。
    con = dataSource.getConnection();
    local.set(con);
   }
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return con;
 }
 }