事務
事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功
數(shù)據(jù)庫開啟事務命令
- start transaction 開啟事務
- Rollback 回滾事務
-
Commit 提交事務
使用事務
//方式一
//創(chuàng)建表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
//創(chuàng)建事務
start transaction;
update account money=money-500 where id=1;
update account money=money+500 where id=1;
update account money=money+500 where id=1;
rollback;//事務回滾,回滾到最開始的樣子
commit;//只有commit后,以上才會操作
//方式二
//可以查看當前autocommit值,默認“ON”打開狀態(tài),任意一條語句都是一個事務
show variable like '%commit%';
//關閉自動事務,需要手動commit
set autocommit = off;
//JDBC方式
public class TransactionTest1 {
public static void main(String[] args) throws Exception {
//修改id=2的money
String sql = "update account set money=100 where id=2";
Connection con = jdbcUtils.getConnection();
con.setAutoCommit(false);//開啟事務
Statement st = con.createStatement();
st.executeUpdate(sql);
con.rollback();//事務回滾
con.commit();//事務提交
st.close();
con.close();
}
}
事務特性
- 原子性
- 一致性
- 隔離性
- 持久性
事務隔離性的設置語句

設置事務隔離性
- 臟讀 dirty read 指一個事務讀取了另一個事務未提交的數(shù)據(jù)
- 不可重復讀 指在一個事務內(nèi)讀取表中的某一行數(shù)據(jù),多次讀取結(jié)果不同(update)
- 虛讀 指在一個事務內(nèi)讀取到了別的事務插入的數(shù)據(jù),導致前后讀取不一致( insert )
- 丟失更新 lost update 后提交的事務把先提交的事務給覆蓋掉
在MySql中設置事務隔離級別
select @@tx_isolation 查詢當前事務隔離幾倍
默認級別是 Repeatable read.
查看事務隔離級別
set session transaction isolation level 設置事務隔離級別
比如:set session transaction isolation level read uncommitted;
在jdbc中設置事務隔離級別
調(diào)用java.sql.Connection中的方法
void setTransactionIsolation(int level);
- level取值可以看API,設置的有常量
演示
//演示臟讀dirty read,一個事務讀到另一個事務未提交的數(shù)據(jù)
set session transaction isolation level read uncommitted; //設置隔離級別
//在A事務中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb'; //先不提交事務
//在B事務中
start transaction;
select * from account; //此時,B事務讀取到變化后的數(shù)據(jù),就出現(xiàn)了臟讀
//當A事務提交前,執(zhí)行rollback,commit. B事務再查詢就會發(fā)現(xiàn),數(shù)據(jù)恢復原樣。
//出現(xiàn)兩次查詢結(jié)果不一致問題,出現(xiàn)了不可重復讀。
//解決臟讀問題,將事務的隔離級別設置為read committed;
set session transaction isolation level read committed; //設置隔離級別
//此時解決了臟讀,但還存在不可重復讀,兩次讀取的數(shù)據(jù)不一樣。A事務commit前和commit后
//解決不可重復讀,設置隔離級別為repeatable read;
set session transaction isolation level repeatable read; //設置隔離級別
//當A事務提交后,B事務查詢的與上次提交前的結(jié)果相同。
//Serializable可以解決所有問題
//該設置可以鎖表,A事務未提交前,其它事務無法對此表進行操作。性能較差

轉(zhuǎn)賬匯款操作
代碼
//jsp
<body>
<form action="${ pageContext.request.contextPath }/account" method="POST">
轉(zhuǎn)入賬戶:<input type="text" name="accountin"><br>
轉(zhuǎn)出賬戶:<input type="text" name="accountout"><br>
金額:<input type="text" name="money"><br>
<input type="submit" name="提交"><br>
</form>
</body>
//ServletAccount.java
public class ServletAccount extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//得到請求參數(shù)
String accountIn = request.getParameter("accountin");
String accountOut = request.getParameter("accountout");
double money = Double.parseDouble(request.getParameter("money"));
//調(diào)用service
AccountService service = new AccountService();
try {
service.account(accountIn,accountOut,money);
response.getWriter().write("轉(zhuǎn)賬成功");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("轉(zhuǎn)賬失敗");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
//AccountService.java
public class AccountService {
//匯款方法
public void account(String accountIn, String accountOut, double money) throws Exception {
//調(diào)用AccountDao中的兩個方法
AccountDao dao = new AccountDao();
Connection con = null;
try {
con = jdbcUtils.getConnection();
//開啟事務
con.setAutoCommit(false);
dao.accountIn(con,accountIn,money);
dao.accountOut(con,accountOut,money);
} catch (Exception e) {
e.printStackTrace();
//出現(xiàn)問題,進行事務回滾
if(con!=null){
try {
con.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
throw e;
}finally{
//事務提交
if(con!=null){
try {
con.commit();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
//AccountDao.java
public class AccountDao {
//轉(zhuǎn)入
public void accountIn(Connection con, String accountIn, double money) throws SQLException, AccountException {
String sql = "update account set money=money+? where name=?";
PreparedStatement pst = con.prepareStatement(sql);
pst.setDouble(1, money);
pst.setString(2, accountIn);
int row = pst.executeUpdate();
if(row == 0){
throw new AccountException("轉(zhuǎn)入失敗");
}
pst.close();
}
//轉(zhuǎn)出
public void accountOut(Connection con, String accountOut, double money) throws SQLException, AccountException {
String sql = "update account set money=money-? where name=?";
PreparedStatement pst = con.prepareStatement(sql);
pst.setDouble(1, money);
pst.setString(2, accountOut);
int row = pst.executeUpdate();
if(row == 0){
throw new AccountException("轉(zhuǎn)出失敗");
}
pst.close();
}
}
//自定義異常 AccountException.java
public class AccountException extends Exception{
public AccountException() {
super();
}
public AccountException(String message, Throwable cause) {
super(message, cause);
}
public AccountException(String message) {
super(message);
}
public AccountException(Throwable cause) {
super(cause);
}
}
用ThreadLocal解決問題
如何在另一個類中的兩個方法中共享另一個類中的Connection,比如
public interface AccountDao{
//這兩個方法都需要使用同一個Connection對象,需要從調(diào)用方法的地方傳遞,
//但沒有傳參,此時就要用ThreadLocal來解決
public void accountOut(String accountOut, double money) throws Exception;
public void accountIn(String accountInt, double monye) throws Exception;
}
丟失更新

丟失更新問題
解決方案詳解:
- 悲觀鎖(假設丟失更新一定會發(fā)生)
- 利用數(shù)據(jù)庫內(nèi)部鎖機制,管理事務
- 允許一張數(shù)據(jù)表添加多個共享鎖,只能添加一個排他鎖
- 事務在修改記錄過程中,鎖定記錄,別的事務無法并發(fā)修改
- update 語句默認添加排他鎖
- 樂觀鎖(假設丟失更新不會發(fā)生)
- 采用程序中添加版本字段解決丟失更新問題
- timestamp 時間戳自動更新
create table product(
id int,
name varchar(20),
updatetime timestamp; //時間戳
);
// timastamp 在插入和修改時,都會自動更新當前時間
//如果讀取時版本字段與修改時版本字段不一致,說明別人進行修改過數(shù)據(jù)
