事務的概念
事務是訪問并可能更新數據庫中各種數據項的1個程序履行單元。
事務的特點
原子性:1個事務是1個不可分割的工作單位,事務中包括的諸操作要末都做,要末都不做。
1致性:事務必須是使數據庫從1個1致性狀態變到另外一個1致性狀態。1致性與原子性是密切相干的。
隔離性:1個事務的履行不能被其他事務干擾。即1個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發履行的各個事務之間不能相互干擾。
持久性:持久性也稱永久性,指1個事務1旦提交,它對數據庫中數據的改變就應當是永久性的。接下來的其他操作或故障不應當對其有任何影響。
手動事務
在操作數據庫的時候,履行了1條增刪改的語句,但是磁盤里的數據沒有改變,需要手動提交事務以后,磁盤里的數據才產生改變。典型的數據庫就是oracle(默許的情況下,可以更改事務類型)。
自動事務
1天增刪改語句履行后,磁盤里的數據改變。典型的數據庫就是MySql(默許的情況下,可以更改事務類型)。
手動事務操作分3個部份,以下:
開啟事務:開啟1個事務
提交事務:從開啟到提交之間的有效SQL語句,將會履行,磁盤數據將會改寫,事務結束
事務回滾:從事務到回滾之間的有效SQL語句,將不生效,事務沒有結束
以MySql數據庫為例,實現1個銀行轉賬的小案例。假定有1張數據庫表,表有3個字段。
該表中有兩個用戶
假定張3要給李4轉賬,要想實現二者的轉賬,需要兩條SQL語句。如果完善的情況下,在程序履行了全部的語句,沒問題。但是,如果在只履行第1個語句后,服務器停電了,這樣就出現了詭異的現象,張3的錢轉出去了,而李4沒有收到。這是銀行系統不允許的。
結合上文的鋪墊,轉賬就能夠看成1個事務,這個事務由兩個操作組成。轉賬要末兩條SQL語句全部操作成功,要末全部失敗。這類結果才是我們想的。
理想狀態
兩條語句都履行:
結果:
非理想狀態
只履行1條語句:
結果:
可以看到自動事務,在非理想狀態下,相當的不靠譜,為了不出現這類情況,MySql提供的手動事務的語句
start transaction開啟事務
rollback回滾事務
commit提交事務
理想狀態
在履行更改操作前先履行start transaction
結果:
履行commit
結果:
非理想狀態(懶得更改表單數據了,在上次更改的基礎上進行)
在履行更改操作前先履行start transaction
履行rollback
履行commit
結果:
通過以上4個摹擬,手動事務的優越性就體現出來了。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF⑻">
<title>Insert title here</title>
</head>
<body>
<h1>轉賬窗口</h1>
<form action="/day19/TransAccServlet" method="post">
轉出人:<input type="text" name="outer"><br>
轉入人:<input type="text" name="inner"><br>
總金額:<input type="number" name="money"><br>
<input type="submit" value="肯定">
</form>
</body>
</html>
<servlet>
<description></description>
<display-name>TransAccServlet</display-name>
<servlet-name>TransAccServlet</servlet-name>
<servlet-class>com.itheima.servlet.TransAccServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TransAccServlet</servlet-name>
<url-pattern>/TransAccServlet</url-pattern>
</servlet-mapping>
package com.itheima.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DBUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
/**
* 取得數據源
*/
public static DataSource getDataSource() {
return dataSource;
}
/**
* 取得數據庫連接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 取得當前線程綁定的連接
* @throws SQLException
*/
public static Connection getCurrentConnection() throws SQLException {
Connection conn = tl.get();
if (conn==null) {
conn = getConnection();
tl.set(conn);
}
return conn;
}
/**
* 開啟事務
* @throws SQLException
*/
public static void beginTransaction() throws SQLException {
Connection conn = getCurrentConnection();
conn.setAutoCommit(false);
}
/**
* 回滾事務
* @throws SQLException
*/
public static void rollbackTransaction() throws SQLException {
Connection conn = getCurrentConnection();
conn.rollback();
}
/**
* 提交事務
* @throws SQLException
*/
public static void commitTransaction() throws SQLException {
Connection conn = getCurrentConnection();
conn.commit();
conn.close();
tl.remove();
}
}
package com.itheima.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.itheima.service.TransferService;
public class TransAccServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf⑻");
//獲得到要求的數據
String outer = request.getParameter("outer");
String inner = request.getParameter("inner");
String money = request.getParameter("money");
//將數據傳遞給業務邏輯
TransferService service = new TransferService();
boolean result = service.transferAcc(outer, inner, money);
//根據業務邏輯的反饋信息,處理后響應給閱讀器
if (result) {
//轉賬成功
response.getWriter().write("轉賬成功");
} else {
//轉賬失敗
response.getWriter().write("轉賬失敗");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package com.itheima.service;
import java.sql.SQLException;
import com.itheima.dao.TransferDao;
import com.itheima.utils.DBUtils;
public class TransferService {
public boolean transferAcc(String outer, String inner, String money) {
boolean flag = true;
TransferDao dao = new TransferDao();
try {
// 開啟事務
DBUtils.beginTransaction();
dao.out(outer, money);
dao.in(inner, money);
} catch (Exception e) {
try {
flag = false;
//回滾事務
DBUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
//提交事務
DBUtils.commitTransaction();
} catch (SQLException e) {
e.printStackTrace();
}
}
return flag;
}
}
package com.itheima.dao;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import com.itheima.utils.DBUtils;
public class TransferDao {
public void out(String outer, String money) throws SQLException {
QueryRunner qr = new QueryRunner();
Connection conn = DBUtils.getCurrentConnection();
String sql = "update account set money=money-? where username=?";
int line = qr.update(conn, sql, money, outer);
if (line<1) {
throw new SQLException();
}
}
public void in(String inner, String money) throws SQLException {
QueryRunner qr = new QueryRunner();
Connection conn = DBUtils.getCurrentConnection();
String sql = "update account set money=money+? where username=?";
int line = qr.update(conn, sql, money, inner);
if (line<1) {
throw new SQLException();
}
}
}
頭1次寫這么長的博文,算是簡單系統的梳理了1下事務。主要為了加深記憶,另外一方面希望能幫助1些初學者。