事务的概念
事务是访问并可能更新数据库中各种数据项的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些初学者。