事务
事务是以一种可靠、一致的方式、访问和操作数据库中数据的程序单元。
事务的4大特性ACID
- 原子性
事务中的多个操作要么都完成,要不都不完成,不会只完成其中的一部分 - 一致性
事务执行之后,状态改变是一致的或者说结果是完整的。 - 隔离性
当前事务的操作对于其他事务是否可见 - 持久性
事务提交之后,操作的结果会进入数据库被永久保存。如果事务没有提交,出现数据库宕机,数据不会被保存到数据库中。
eg: 张三给李四转100元钱
image.png原子性:转钱成功后,张三的账户一定是扣掉了100元,李四的账户一定是增加了100元,不会出现单方面账户变化。
一致性:转账成功后,张三的账户扣掉的一定是100元,李四的账户一定是增加了100元,不会出现张三的账户被扣了120元而李四的账户增加了80。
隔离性:张三给李四转账执行的时候的中间状态是否对李四可见,比如张三的账户被扣了100元后,李四的账户还没有加100元这个中间状态是否对李四可见。
持久性:转账成功后(事务提交后)数据被永久存储在数据库中,不会出现数据丢失的情况。
用SQL实现事务
- 创建数据库和表数据
create database test;
use test;
create table t_user(id int PRIMARY key auto_increment,username varchar(32),amount int);
insert t_user(username,amount) values('张三',500),('李四',500);
image.png
- 转账事务
START TRANSACTION;
UPDATE t_user SET amount = amount - 100 WHERE username = '张三';
UPDATE t_user SET amount = amount + 100 WHERE username = '李四';
COMMIT;
mysql 的默认隔离级别是可重复读,事务执行的任何中间状态都不会被其他事务看到,也就是说,在转账事务执行的时候,如果另一个事务进行查询操作,查到的账户余额都是事务开始时的余额即,张三,500;李四,500。
使用JDBC实现事务
public class LocalJdbcTransaction {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Connection connection = getConnection();
//关闭自动提交,相当于开启事务
connection.setAutoCommit(false);
String plusSQL = "UPDATE t_user SET amount = amount + 100 WHERE username = ?";
PreparedStatement plusPS = connection.prepareStatement(plusSQL);
plusPS.setString(1, "张三");
plusPS.execute();
String minusSQL = "UPDATE t_user SET amount = amount - 100 WHERE username = ?";
PreparedStatement minusPS = connection.prepareStatement(minusSQL);
minusPS.setString(1, "李四");
minusPS.execute();
plusPS.close();
minusPS.close();
//提交事务
connection.commit();
}
/**
* 获取数据库连接
*
* @return Connection
* @throws SQLException SQLException
* @throws ClassNotFoundException ClassNotFoundException
*/
private static Connection getConnection() throws SQLException, ClassNotFoundException {
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&serverTimezone=UTC";
String username = "root";
String password = "******";
Class.forName(driver);
return DriverManager.getConnection(url, username, password);
}
}
FOR UPDATE
image.png
[场景]
张三和王五都想给李四转账100元钱。
初始情况下,理想情况下,转账后,李四的账户应该是300元。
转账的逻辑是这样的:
- 查询李四账户的余额。
- 李四的账户余额增加100元。
张三和王五的转账逻辑分别在不同的事务中执行。
image.png
- 张三开启事务,查询到李四的账户余额是100元。
- 王五开启事务,查询到李四的账户余额也是100元。
- 张三给李四的余额增加了100元,李四的余额变成了200元,张三提交事务。
- 王五给李四的余额也增加了100元,但是是在第一个查询到余额的基础上增加的100元,即100+100=200元。王五提交事务。
转账完成后,发现李四的账户只多了100元。有人可能说王五别先查询李四的账户,等到张三执行完转转操作后王五在查询李四的账户就好了,但是!!!首先我们无法控制这个先后顺序,因为这在两个线程中,而且最重要的是mysql的隔离级别是可重复读,也就是说张三在没有提交事务之前,王五查询的结果将一直都是100元。[解决方案]
解决这样的问题最好的方式是加for update
锁。
for update
锁,会在查询的时候锁定该行,知道事务提交的时候才释放锁。
- 如果没有用在事务中使用
for update
锁,那么该锁不会生效如果使用该锁的时候,查询语句没有索引,那么该锁会锁定整张表。造成资源的大量浪费
image.png
王五在查询李四余额的时候会阻塞查询语句,等待张三事务提交,张三提交事务后,王五查询到正确的余额,即:200,王五继续给李四转账,李四收获300。
[具体实现]
public void test() throws SQLException, ClassNotFoundException {
Connection connection = getConnection();
//关闭自动提交,相当于开启事务
connection.setAutoCommit(false);
String selectSQL = "SELECT * from t_user where username = ? for update";
PreparedStatement selectQuery;
selectQuery = connection.prepareStatement(selectSQL);
selectQuery.setString(1,"李四");
ResultSet resultSet = selectQuery.executeQuery();
Long amount = null;
while (resultSet.next()){
String username = resultSet.getString(2);
amount = resultSet.getLong(3);
System.out.println(username+"==>"+amount);
}
String minusSQL = "UPDATE t_user SET amount = ? + 100 WHERE username = ?";
PreparedStatement minusPS = connection.prepareStatement(minusSQL);
minusPS.setLong(1,amount);
minusPS.setString(2, "李四");
minusPS.execute();
minusPS.close();
//提交事务
connection.commit();
}
Spring 事务
Spring事务抽象
-
PlatFormTransactionManager
事务管理器的接口;包括事务开启,提交,回滚等操作
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
-
TransactionDefinition
事务的定义,可以通过该接口定义事务的属性,例如:传播属性,隔离属性
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
@Nullable
String getName();
}
-
TransactionStatus
相当于事务运行过程中的句柄。
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
@Override
void flush();
boolean isCompleted();
}
编程形式使用Spring事务
@Service
public class OrderService {
@Resource
private PlatformTransactionManager txManager;
void buyTicket(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// todo 执行业务代码
txManager.commit(status);
}catch (Exception e){
txManager.rollback(status);
}
}
}
声明式事务管理
方法体上加@Transactional
网友评论