事务,一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元。这些单元要么全都成功,要么全都不成功。
事务在开发中的作用
下面我们来举例说明什么是事务,如下所示:
现实生活中的银行转账业务,张三要给李四转账1000元,而在程序员眼中两条SQL语句就可以搞定,如下:
给张三的账户减去1000元;
给李四的账户加上1000元;
如果在转账的业务中,成功的将张三的账户减去1000元,而在给李四的账户加1000元的时候,程序出现了问题,李四的账户没有加上1000元,而张三的账户却减掉了1000元,在现实生活中,这种情况肯定是不允许存在的。当我们将这个转账业务放在一个事务中,就不会出现以上情况了。
事务中有多个操作,这些操作要么全部成功,要么全部失败,也就是说给张三的账户减去1000元如果成功了,那么给李四的账户加上1000元的操作也必须是成功的,否则给张三减去1000元,以及给李四加上1000元都必须是失败的。
一、 mysql中的事务
mysql中默认事务处理
在mysql登录的情况下执行以下命令
show variables like '%commit%'; ---- autocommint 值是 on,说明开启自动提交
mysql数据库默认是开启事务的,一条sql一个事务.
oracle它默认情况下autocommit是off,就需要手动提交事务.
关闭mysql的自动事务处理命令
set autocommit = off;( set autocommit = 0)
如果设置autocommit 为 off,意味着以后每条SQL 都会处于同一个事务中,相当于第一条SQL执行前执行了 start transaction
mysql中手动事务处理
start transaction:-- 开启事务 一旦手动开启了事务,事务自动提交失效.
commit;-- 提交事务
rollback;-- 事务回滚
二、jdbc中的事务
jdbc中事务处理api
java.sql.Connection接口中提供了关于事务操作的API
setAutoCommit(boolean flag);`参数为false相当于start transaction
commit(); 事务提交
rollback(); 事务回滚
回滚(Rollback)指的是程序或数据处理错误,将程序或数据恢复到上一次正确状态的行为
回滚点介绍
Savepoint setSavepoint(String name)
在当前事务中创建一个具有给定名称的保存点,并返回表示它的新 Savepoint 对象。
例如:
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
三、事务的特性
事务的四大特性是面试官经常问的问题,简称ACID(Atomicity Consistency Isolation Durability),分别是:
原子性:原子性对应的英文是Atomicity,即表示事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败;
一致性:一致性对应的英文是Consistency,事务执行后,数据库状态与其它业务规则保持一致。例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的;
隔离性:隔离性对应的英文是Isolation,是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰;
持久性:持久性对应的英文是Durability,指的是一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
不同的事务,其一致性的表现形式是不同的,事务的其他三大特性其实都是为了事务的一致性服务的。
四、事务的隔离级别与问题
不考虑隔离性产生的问题介绍
- 脏读 一个事务读取到了另一个事务未提交数据.
- 不可重复读 一个事务内,两次读取到的数据不一致.(update)
- 虚读(幻读) 两次读取的数据不一致(insert)
事务的4种隔离级别介绍
数据库内部定义了四种隔离级别,用于解决三种隔离问题
- Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
- Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
- Read committed:可避免脏读情况发生(读已提交)
- Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
mysql数据库默认的事务隔离级别-----repeatable read级别.
oracle数据默认的事务隔离级别 ----read committed
设置事务隔离级别
-
mysql中设置
数据库默认有事务的隔离级别,mysql 中查看与修改事务的隔离级别
set session transaction isolation level
隔离级别;设置事务隔离级别
select @@tx_isolation;
查询当前事务隔离级别 -
jdbc中设置事务隔离级别
在java.sql.Connection接口中提供
setTransactionIsolation(int level) ;
参数可以取 Connection 常量之一:
Connection.TRANSACTION_READ_UNCOMMITTED、
Connection.TRANSACTION_READ_COMMITTED、
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE。
(注意,不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。)
安全性:serializable > repeatable read > read committed > read uncommitted
效率 :serializable< repeatable read < read committed < read uncommitted
在开发中,一般使用 read committed、repeatable read两种。
MYSQL :repeatable read
Oracle :read committed
脏读分析与解决
脏读:一个事务读取到另一个事务未提交数据.
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
select @@tx_isolation
步骤三:设置A窗口的隔离级别为read uncommitted
set session transaction isolation level read uncommitted;
步骤四:在两个窗口中分别开启事务:
start transaction;
步骤五:在B窗口中转账操作:
update account set money = money - 1000 where name='守义';
update account set money = money + 1000 where name='凤儿';
在B窗口中没有提交事务的!!!
步骤六:在A窗口中进行查询:
已经转账成功!!!(脏读:一个事务中读到了另一个事务未提交的数据)
解决脏读 设置事务的隔离级别为 read committed
不可重复读分析与解决
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
select @@tx_isolation
步骤三:设置A窗口的隔离级别为read committed;
set session transaction isolation level read committed;
步骤四:在两个窗口中分别开启事务:
start transaction;
步骤五:在B窗口中完成转账的操作:
update account set money = money - 1000 where name='守义';
update account set money = money + 1000 where name='凤儿';
在B窗口中没有提交事务!!!
步骤六:在A窗口中进行查询:
没有转账的结果!!!(已经避免了脏读)
步骤七:在B窗口中提交事务!!!
commit;
步骤八:在A窗口中进行查询:
转账成功!!!(不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致一次事务中多次查询结果不一致.)
解决不可重复读 设置事务的隔离级别为 Repeatable read
虚读分析
它主要强调的是多次查询的结果的条不一样,而不可重复读强调的是结果不一样。
串行化
设置隔离级别为 Serializable
五、JDBC的隔离级别的设置
Connection中的方法:
Connection中的方法.png
Connection中提供了隔离级别的常量:
Connection中提供了隔离级别的常量.png
使用DBUtils的进行事务的管理:
QueryRunner:
* 构造:
QueryRunner();
QueryRunner(DataSource ds);
* 方法:
T query(String sql,ResultSetHanlder<T> rsh,Object... params);
T query(Connection conn,String sql,ResultSetHanlder<T> rsh,Object... params);
int update(String sql,Object... params);
int update(Connection conn,String sql,Object... params);
方法分类:
* 没有事务:
QueryRunner(DataSource ds);
T query(String sql,ResultSetHanlder<T> rsh,Object... params);
int update(String sql,Object... params);
* 有事务:
QueryRunner();
T query(Connection conn,String sql,ResultSetHanlder<T> rsh,Object... params);
int update(Connection conn,String sql,Object... params);
六、ThreadLocal
public class ThreadLocal<T>extends Object
该类提供了线程局部 (thread-local) 变量。
这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。
ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
它的底层是使用了一个Map集合
Map<Thread,Object>
它的key就是当前的线程对象.
set(Object obj) 它就相当于 map.put(Thread.currentThread(),obj);
get()它就相当于 map.get(Thread.currentThread()));
七、转账案例
设计转账的页面:
<h1>转账的页面</h1>
<form action="" method="post">
<table border="1" width="400">
<tr>
<td>付款人:</td>
<td><input type="text" name="from"/></td>
</tr>
<tr>
<td>收款人:</td>
<td><input type="text" name="to"/></td>
</tr>
<tr>
<td>转账金额:</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="转账"/></td>
</tr>
</table>
</form>
Servlet:
public class AccountServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 1.接收数据:
* 2.封装数据:
* 3.调用业务层:
* 4.页面跳转作出响应:
*/
request.setCharacterEncoding("UTF-8");
// 接收数据:
String from = request.getParameter("from");
String to = request.getParameter("to");
double money = Double.parseDouble(request.getParameter("money"));
// 调用业务层:
AccountService accountService = new AccountService();
accountService.transfer(from,to,money);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
Service:
public class AccountService {
/**
* 业务层转账的方法:
* @param from :付款人
* @param to :收款人
* @param money :转账金额
*/
public void transfer(String from, String to, double money) {
// 调用DAO:
AccountDao accountDao = new AccountDao();
try {
accountDao.outMoney(from, money);
// int d = 1/0;
accountDao.inMoney(to, money);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
DAO:
public class AccountDao {
/**
* 付款的方法
* @param name
* @param money
* @throws SQLException
*/
public void outMoney(String name,double money) throws SQLException{
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写一个SQL:
String sql = "update account set money = money-? where name=?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setDouble(1, money);
pstmt.setString(2, name);
// 执行SQL:
pstmt.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
pstmt.close();
conn.close();
}
}
/**
* 收款的方法
* @param name
* @param money
* @throws SQLException
*/
public void inMoney(String name,double money) throws SQLException{
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写一个SQL:
String sql = "update account set money = money+? where name=?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setDouble(1, money);
pstmt.setString(2, name);
// 执行SQL:
pstmt.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
pstmt.close();
conn.close();
}
}
}
网友评论