事务的介绍
事务是逻辑上的一组操作,要么都执行,要么都不执行。
简单来说事务的目的就是为了保证数据的一致性。在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。事务的主要职责是管理我们对数据库的insert
,update
,delete
操作,保证SQL要么全部执行,要么全部不执行。
事务的特性ACID
原子性(A
tomicity)
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(C
onsistency)
执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
隔离性(I
solation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。(事务隔离的级别在下文可以继续看到详细内容)
持久性(D
urability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务的类别
事务分为两种:你可以理解为轿车的手动挡和自动挡。
自己手动控制事务,就叫做编程式事务控制。Spring
提供了对事务的管理, 这个就叫声明式事务管理。
编程式事务(手动)
在业务逻辑方法中,手动的开启/关闭/回滚事务。
自己手动控制事务,就叫做编程式事务控制。通过JDBC
或者是Session
设置事务的开启和关闭及发生异常时的回滚。
优点: 控制的粒度细,非常灵活的指定具体是那个方法,那几行代码添加事务控制。
缺点: 比较繁琐,频繁的写开启、提交回滚的代码。
声明式事务(自动)
xml方式,注解方式。
Spring
整合了事务,核心思想是基于AOP
(拦截方法) 来控制事务。可以通过xml文件方式配置事务,对事务的控制最大化的解耦。
Spring
声明式事务管理器类:JDBC
由DataSourceTransactionManager
管理(其他的也不太记得了)。
事务的管理
编程式事务:
核心主要是通过DataSourceTransactionManager
类管理事务的开启/关闭/回滚。一般情况下抽象成一个工具类,然后在业务中使用即可。
@Component
public class TransactionUtils {
// 事物管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
public TransactionStatus begin() {
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());
return transaction;
}
public void commit(TransactionStatus transaction) {
dataSourceTransactionManager.commit(transaction);
}
public void rollback(TransactionStatus transaction) {
dataSourceTransactionManager.rollback(transaction);
}
}
// 核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启支持注解 -->
<context:component-scan base-package="com.itmayiedu"></context:component-scan>
<!-- 数据源使用的C3P0连接池,可更换阿里的或者别的 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置事物 -->
<bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
声明式事务:
不需要我们手动的去显示控制事务的开/关/回滚,在配置文件中配置即可。
- XML方式,就是在核心xml配置文件中加入事务增强以及AOP配置。
<!—配置事物增强-->
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- Aop配置: execution表达式指定增强的类的那些方法 + 上面的事务增强配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.darian.service.*.*(..))"
id="pointcut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>
- 注解方式
在核心配置文件xml中指定注解方式实现声明式事务管理以及应用的事务管理器类。
然后在需要添加事务控制的地方,写上:@Transactional
。
<!-- 配置事物 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事物 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
@Transactional 注解
该注解可以使用在:方法(当前方法使用)、类(类的所有方法使用)。
@Transactional
注解可以控制传播行为方式、超时设置、事务隔离级别等属性,更多属性可以自己去了解。
事务的传播行为
比较常用的是:PROPAGATION_REQUIRED
、PROPAGATION_REQUIRES_NEW
事务隔离级别有哪些?MySQL的默认隔离级别是?
MYSQL: 默认为REPEATABLE_READ
(可重复读)级别。
事务隔离分为不同级别,包括:读未提交(Read uncommitted
)、读提交(Read committed
)、可重复读(Repeatable read
)和串行化(Serializable
)。
READ-UNCOMMITTED
(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED
(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ
(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE
(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
事务是什么时候提交的?
方法结束就提交事物,声明式事物当抛出异常的时候才会回滚。
嵌套事务(子事务)
即事务中嵌套事务,举例:我有个订单开据的功能,我需要在每次订单开据的时候有log记录日志到数据库中。我们抽象化下具体的角色:
// 省略Log表和Order表
// 省略LogDao持久化操作实现
@Service
public class LogService {
@Autowired
private LogDao logDao;
// 新建事务,如果当前存在事务,就把当前事务挂起;如果当前方法没有事务,就新建事务;
@Transactional(propagation=Propagation.REQUIRED_NEW)
public void paioJuLog() {
// 记录订单开据行为的日志方法(该数据要入库)
logDao.insertPiaoJuLog();
}
}
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private LogService logService;
//支持当前事务,如果当前没有事务,就新建一个事务。
@Transactional(propagation = Propagation.REQUIRED_NEW)
//@Transactional(propagation = Propagation.SUPPORTS)
// ...省略其它行为的propagation
public void generatePiaoJu() {
// 记录订单开据的时间等信息记录的方法...
logService.paioJuLog();
// 开订单票据的操作(数据库要入库一条信息)
orderDao.openPiaoJu(...);
}
}
public class Test {
public static void main(String[] args){
// 省略实例化容器Context对象即getBean等代码...
OrderService.generatePiaoJu();
}
}
如果运行上面的代码会发生什么?你可以动手去模拟一下,模拟propagation的不同情况,以及在遇到异常等情况下,它们过程和结果是怎样?