1、事务概述
事务是一系列的动作,他们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会会滚到最开始的状态,仿佛什么都没发生过一样,用来保证数据的完整性和一致性。
特性:
1、原子性:事务中包含的所有操作要么全部成功,要么全部失败回滚,不可再分。
2、一致性:就是说一个事务执行之前和执行之后都必须处于一个一致性状态。,比如a和b两个用户,a向b转账后,ab两个用户的总额前后相等。
3、隔离性:多个事务之间相互隔离。互不影响
4、持久性:事务一旦提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
2、Spring事务管理
2.1 编程式事务
手动控制事务,细粒度的事务控制,可以对指定的方法、或者其某几行添加事务控制,好处比较灵活,但是开发比较繁琐,光是什么begin、commit、rollback就够烦的。
依赖:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
applicationContext.xml:
<!--1、数据源对象: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>
<!-- 2、JdbcTemplate工具类实例-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务-->
<bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
transaction管理:
@Component
public class TransactionManagerUtils {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
public TransactionStatus begin(){
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());
System.out.println("开启事务");
return transaction;
}
public void commit(TransactionStatus transaction){
dataSourceTransactionManager.commit(transaction);
System.out.println("提交事务");
}
public void rollback(TransactionStatus transaction){
dataSourceTransactionManager.rollback(transaction);
System.out.println("回滚事务");
}
}
service:
@Autowired
private TransactionManagerUtils transactionManagerUtils;
public void add(){
TransactionStatus begin = transactionManagerUtils.begin();
try{
userEntityDao.add("xiaoming",23);
transactionManagerUtils.commit(begin);
}catch (Exception e){
transactionManagerUtils.rollback(begin);
}
}
dao:
@Autowired
private JdbcTemplate jdbcTemplate;
public void add(String name,Integer age){
String sql="insert into users(name,age) values (?,?);";
int update=jdbcTemplate.update(sql,name,age);
System.out.println("updateResult:"+update);
}
test:
UserEntityService userEntityDao = (UserEntityService) applicationContext.getBean("userEntityService");
userEntityDao.add();
2.2 声明式事务
事务原理底层就是通过AOP+环绕通知+异常通知实现的。
@Autowired
private TransactionManagerUtils transactionManagerUtils;
private TransactionStatus transactionStatus;
public void begin(){
}
public void after(){
}
public void afterReturning(){
}
public void afterThrowing(){
transactionManagerUtils.rollback(transactionStatus);
System.out.println("回滚事务");
}
public void aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
transactionStatus = transactionManagerUtils.begin();
System.out.println("开启事务");
System.out.println("我是环绕通知-前");
proceedingJoinPoint.proceed();
System.out.println("我是环绕通知-后");
transactionManagerUtils.commit(transactionStatus);
}
注意:这里千万不要在After里面commit,因为这个是总是执行的,所以总会commit成功!
还有在操作的时候,不要在dao层以下try、catch,只有将其抛出才能在上层捕获然后进行异常通知。
2.2.1 xml方式
<!-- 配置事务-->
<bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务增强-->
<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配置:拦截那些方法(切入点表达式)+应用上面的事务增强-->
<aop:config>
<aop:pointcut id="pt2" expression="execution(* com.spring4.service.UserEntityService.add3())"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"/>
</aop:config>
2.2.2 注解方式
<!-- 配置事务-->
<bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="DataSourceTransactionManager"/>
@Transactional
public void add4(){
userEntityDao.add("xiaoming2",23);
// int i=1/0;
}
3 事务传播行为
举个例子:
@Service
public class LogService {
@Autowired
private LogDao logDao;
@Transactional
public void addLog(){
logDao.addLog();
}
}
@Transactional
public void add4(){
logService.addLog();
userEntityDao.add("xiaoming2",23);
int i=1/0;
}
其他代码就不粘贴了,对于这样的一个事务,logService.addLog()是日志的操作记录,而按照事务的处理规则,此时log和user都要回滚,那我们怎么去追踪日志啊,难道失败了就不记录这个日志了吗???所以事务传播行为应运而生。
事务传播行为指的就是当以一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
比如methodA事务方法调用methodB事务方法时,methodB是继续在调用者的事务中运行呢,还是为自己开启一个新的事务运行,这就是有methodB的事务传播行为决定的。
Spring定义了七种传播行为:
Propagation.REQUIRED:支持当前事务,如果没有当前事务,就新建一个事务,如果有就使用当前事务。
比如上述的logService中@Transactional(propagation=Propagation.REQUIRED),那么如果userService有声明事务,则两者共用这个事务,userService抛出异常两者都会回滚,事务默认传播行为也是这种。
Propagation.REQUIRES_NEW:简单来说就是两个使用的是不同的事务,如果User没有事务,那么log里直接新建一个事务,如果有,则把当前事务挂起然后新建一个事务。比如上述的logService中@Transactional(propagation=Propagation.REQUIRES_NEW),则log的不会回滚。
Propagation.SUPPORTS:支持当前事务,如果User已经有事务了,那么log就用User的事务,比如上述的logService中@Transactional(propagation=Propagation.SUPPORTS)就不会回滚,如果User没有事务那么就以非事务进行。
@Transactional(propagation = Propagation.SUPPORTS)
public void addLog(){
logDao.addLog();
int i=1/0;
}
// @Transactional(propagation = Propagation.REQUIRED)
public void add4(){
logService.addLog();
userEntityDao.add("xiaoming2",23);
int i=1/0;
}
此时如果上述代码如此,那么addLog这条日志还是会添加成功,所以如果当前没有事务,就以非事务形式运行。
Propagation.NOT_SUPPORTS:不支持当前事务,如果当前有事务,直接挂起当前事务,然后自己非事务运行。
@Transactional(propagation = Propagation.REQUIRED)
public void add4(){
logService.addLog();
userEntityDao.add("xiaoming2",23);
int i=1/0;
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addLog(){
logDao.addLog();
int i=1/0;
}
不管add4有没有事务(也就是是否取消注解),addLog都不会用你的,也不用自己的,所以即使他自己抛出异常了,这条记录也是会添加成功的(非事务)。
Propagation.MANDATORY:支持当前事务,如果当前没有事务,那么就会抛出异常。
@Transactional(propagation = Propagation.REQUIRED)
public void add4(){
logService.addLog();
userEntityDao.add("xiaoming2",23);
int i=1/0;
}
@Transactional(propagation = Propagation.MANDATORY)
public void addLog(){
logDao.addLog();
}
如果add4已经有事务了,那么addLog就共用这个事务,如果add4没有就会抛出异常,取消add4的事务注解即可看到这些效果。
Propagation.NEVEVR:不支持当前事务,如果当前有事务,抛出异常,和MANATORY正好相反
Propagation.NESTED:如果当前存在事务,那么事务就嵌套在当前事务,并且有自己的commit和rollback。如果当前没有事务,则使用自己的事务进行commit和rollback(REQUIRED).
@Transactional(propagation = Propagation.NESTED)
public void addLog(){
logDao.addLog();
int i=1/0;
}
@Transactional(propagation = Propagation.REQUIRED)
public void add4(){
logService.addLog();
userEntityDao.add("xiaoming2",23);
int i=1/0;
}
比如这样,addLog的事务嵌套在add4的事务中执行,addLog的事务自己会commit和rollback,但是add4的回滚也会引起addLog的回滚。但是addLog的rollback不会引起add4的回滚。
===》内层不影响外层,外层影响内层。
4 Spring事务隔离级别
4.1 事务的安全
1、丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖第一个事务的更新,从而导致第一个事务更新的诗句丢失,这是由于没有加锁造成的。
2、幻读:同样的事务操作过程中,不同时间段多次(不同事务)读取同一数据,读取到的数据不一致(一般是行数变多或者变少,主要是删除或者插入了嘛)。解释一下,就是说A事务删除一条数据后,B事务在删除前读到10条,而C事务在删除后只读到9条。
3、脏读:一个事务读取到另一个未提交事务的内容
4、不可重复读:同一事务中,多次读取内容不一致(一般行数不变,而内容变了,更新时候)
幻读和不可重复读区别:幻读的重点在于插入和删除,即第二次查询会发现比第一次查询数据变少或者变多了,以至于给人一种幻象一样,而不可重复读重点在于修改,即第二次查询会发现查询结果比第一次结果不一致,也就是第一次结果不可重现了。
4.2 事务隔离级别
spring有五大隔离级别,其在TransactionDefinition接口中定义,其默isolation_default(底层数据库默认级别),其他四个隔离级别跟数据库隔离级别一致。
1、ISOLATION_DEFAULT:用底层数据的默认级别,数据库管理员设置什么就是什么。
2、ISOLATION_READ_UNCOMMITTED(未提交读):最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)。
3、ISOLATION_READ_COMMITTED(提交读):一个事务提交后才能被其他事务读取到(该隔离界别禁止其他事务读取到未提交事务的数据,但是还是会出现幻读、不可重复读)、SQLServer默认级别
4、ISOLATION_REPEATABLE_READ(可重复读):保障多次读取同一数据时,其值都和事务开始时候的内容一致,禁止读取到别的事务未提交的数据(改隔离可防止脏读、不可重复读、但会出现幻读)MySql默认级别,更改可通过set transaction isolation level 级别
5、ISOLATION_SERIALIZABLE(序列化):代价最高的最可靠的隔离级别(该隔离级别能防止脏读、不可重复读、幻读)。
数据库隔离级别越高,执行代价越高,并发执行能力越差,因此在实际项目开发使用时要综合考虑,为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。