事务的四个特征
CAID是事务的四个特征,所有事务都必须满足以下特性。
- 原子性(Atomicity):一个事务要么全部执行,要么不执行
- 一致性(Consistency):事务的运行并不改变数据库中数据的一致性
- 隔离性(Isolation):事务的隔离性也称作独立性,是指两个以上的事务不会出现交错执行的状态。因为这样可能会导致数据不一致。
- 持久性(Durtability):事务执行成功以后,该事务对数据库所作的更改便是持久的保存在数据库之中,不会无缘无故的回滚。
数据库隔离级别
- READ UNCOMMITTED:不可提交读
- READ CONMMITTED:可提交读
- REPEATABLE READ:可重复读
- SERIAIZABLE:序列化
查询数据库隔离级别的sql语句
select @@global.tx_isolation
各种场景
下面是不同隔离级别各种场景出现的问题,括号内的是默认隔离级别的数据库
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | √ | √ | √ |
READ CONMMITTED(Sql Server , Oracle) | × | √ | √ |
REPEATABLE READ(MySQL) | × | × | √ |
SERIAIZABLE | × | × | × |
各种场景的详细分析
spring事务
spring事务总共有两种,编程式事务和声明式事务,其中编程式事务因为代码具有侵入性,开发效率很慢,所以很少会在开发中应用,仅作了解。
编程式事务了解
声明式事务
可以通过两种方法开启和使用,分别是基于xml的和基于注解的
基于xml
在spring的applicationContext.xml配置文件中加入下面三类配置
<!-- spring中基于XML的声明式事务控制配置步骤1、配置事务管理器2、配置事务的通知此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的使用tx:advice标签配置事务通知属性:id:给事务通知起一个唯一标识transaction-manager:给事务通知提供一个事务管理器引用3、配置AOP中的通用切入点表达式4、建立事务通知和切入点表达式的对应关系5、配置事务的属性是在事务的通知tx:advice标签的内部-->
<!-- 一:配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property>
</bean><!-- 二:配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><!-- 配置事务的属性isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。测试:no-rollback-for="java.lang.ArithmeticException",遇到算数异常不回滚--><tx:attributes><tx:method name="*" propagation="REQUIRED" read-only="false"/><tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method></tx:attributes>
</tx:advice><!-- 三:配置aop-->
<aop:config><!-- 配置切入点表达式--><aop:pointcut id="pc" expression="execution(* com.itheima.service..*.*(..))"></aop:pointcut><!--建立切入点表达式和事务通知的对应关系 --><aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
基于注解
首先需要开启spring事务,这个只需要在spring配置类上面加上@EnableTransactionManagement注解就可以了
/*** @EnableTransactionManagement用以开启spring事务* 同样也开启了事务的传播特性*/
@Configuration
@EnableTransactionManagement
public class SpringConfig {/*** 配置数据源* @return*/@Beanpublic DataSource dataSource(){DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUsername("root");dataSource.setPassword("454424");return dataSource;}/*** 配置事务管理器* 将数据源注入* @param dataSource* @return*/@Beanpublic DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}/*** 配置jdbcTemplate* 同样需要注入数据源* @param dataSource* @return*/@Beanpublic NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource){NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);return namedParameterJdbcTemplate;}
}
对于想要加上spring事务传播特性的事务方法只需要在方法上加上@Transactional注解就可以了,可以指定事务的传播特性。
@Component
public class PersonDaoImpl implements PersonDao {@AutowiredNamedParameterJdbcTemplate template;@Override@Transactional(propagation = Propagation.REQUIRED)public void update() {int update = template.update("insert into person(person_id,name,person_addr,gender) values(5,'cc','pp',1)", new HashMap<String, Object>());}
}
注意,事务的传播特性默认是REQUIRED
spring的事务传播特性
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
需要注意的是,如果方法中出现异常就会回滚,但是如果已经将异常捕捉住(try/catch),那么在捕捉的那一层就不会回滚数据。
jdk动态代理带来的spring事务失效
在同一个类中spring事务失效的问题
https://blog.csdn.net/bntX2jSQfEHy7/article/details/79040349
在事务方法中执行其他的事务方法和直接调用事务方法会出现两种情况
因为在事务方法中调用的其他事务方法其实是原方法,而直接调用事务方法其实已经是被spring通过jdk动态代理的代理对象,在代理对象中实现了事务的开启与提交,但是如果调用的原生方法就不会开启事务
所以在这种情况下如果在事务方法调用的事务方法抛出异常,可是如果调用的原生方法,所以因为没有开启事务,就没能回滚数据
@Component
public class PersonDaoImpl implements PersonDao {@AutowiredNamedParameterJdbcTemplate template;@Override@Transactional(propagation = Propagation.REQUIRED)public void update() {int update = template.update("insert into person(person_id,name,person_addr,gender) values(5,'cc','pp',1)", new HashMap<String, Object>());try {update1();} catch (Exception e) {e.printStackTrace();}}@Transactional(propagation = Propagation.REQUIRES_NEW)public void update1() {int update = template.update("insert into person(person_id,name,person_addr,gender) values(6,'zz','dd',1)", new HashMap<String, Object>());System.out.println(1/0);}
}
解决方法有
1.实现ApplicationContextAware接口得到applicationContext对象,然后如果要在事务方法在中调用其他的事务方法,那么可以从applicationContext获得bean对象(就是代理对象),直接调用bean对象的事务方法,不过这种方法比较麻烦
2.
@Component
public class PersonDaoImpl implements PersonDao, ApplicationContextAware {ApplicationContext applicationContext;@AutowiredNamedParameterJdbcTemplate template;@Override@Transactional(propagation = Propagation.REQUIRED)public void update() {int update = template.update("insert into person(person_id,name,person_addr,gender) values(5,'cc','pp',1)", new HashMap<String, Object>());try {//通过bean对象调用方法,这样这个方法就是代理方法applicationContext.getBean(PersonDao.class).update1();} catch (Exception e) {e.printStackTrace();}}@Transactional(propagation = Propagation.REQUIRES_NEW)public void update1() {int update = template.update("insert into person(person_id,name,person_addr,gender) values(6,'zz','dd',1)", new HashMap<String, Object>());System.out.println(1/0);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}