文章目录
一、Spring事务理解
事务的特性:
- 原子性:一个错误,全体回滚
- 一致性:总量不变(如A和B加起来共有1000,他们之间不管怎么转,都是这1000)
- 隔离性:多用户同时操作数据库时,数据库开启事务,事务与事务之间互不干扰
- 持久性:事务提交后,被保存在外存。
从Spring的TransactionDefinition回顾事务
public interface TransactionDefinition{
int getIsolationLevel(); //获取隔离级别
int getPropagationBehavior(); //获取传播属性
int getTimeout(); //获取超时时间
boolean isReadOnly(); //是否只读
}
1、隔离级别
1.1、READ_UNCOMMITTED(读未提交)
该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别
1.2、READ_COMMITTED(读已提交)
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
1.3、REPEATABLE_READ(可重复读)
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
MySQL默认的隔离级别。
1.4、SERIALIZABLE(串行化)
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
1.5、没有隔离会出现的问题
操作数据库命令:
select @@transaction_isolation; --查询数据库的隔离级别
select @@global.transaction_isolation; --查看系统当前隔离级别
set session transaction isolatin level [隔离级别]; --设置当前会话隔离级别
set global transaction isolation level [隔离级别]; --设置系统当前隔离级别
start transaction --开启事务
commit --提交事务
rollback --回滚
1.5.1 脏读
指一个事务读取了另外一个事务未提交的数据。
我们使用两个命令行窗口进行模拟。
1.5.2 不可重复读
在一个事务内读取表中的某一行数据,多次读取结果不同。(这个不一定是错误,只是某些场合不对)。
不可重复读和脏读的区别是,脏读读取到的是一个未提交的数据,而不可重复读读取到的是前一个事务提交的数据。
1.5.3 幻读
幻读是事务非独立执行时发生的一种现象。是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
在上图中,先开启事务,读到的price是100,无论其他事务是该这个price,还是删除这一行,这个事务中读到的永远是100,可以猜测,这里图一读到的price是第一次读到的副本,不会再去数据库中查询新值,这也就是幻读的一种形式。
而不开启事务,则不会存在这种情况。由此可以证明普通查询的场景解决了幻读的问题。
注意:在MySQL8.0中已经解决了可重复的下的幻读问题。
1.6 隔离级别解决隔离问题总结
5.7
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | × | × | × |
读已提交 | √ | × | × |
可重复读 | √ | √ | × |
串行读 | √ | √ | √ |
8.0
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | × | × | × |
读已提交 | √ | × | × |
可重复读 | √ | √ | √ |
串行读 | √ | √ | √ |
2.传播属性
属性 | 解释 |
---|---|
PROPAGATION_REQUIRED | 需要事务。如果有事务则加入当前事务,如果没有事务则创建一个事务。 |
PROPAGATION_REQUIRED_NEW | 需要新事务。不管当前有没有事务都会新创建一个事务。 |
PROPAGATION_SUPPORTS | 支持事务。如果当前有事务,则加入当前事务,如果没有事务则按照非事务方式运行。 |
PROPAGATION_NOT_SUPPORTED | 不支持事务。如果当前有事务,则挂起当前事务。 |
PROPAGATION_MANDATORY | 将事务委托给调用方,因此需要调用方以事务方式运行。如果当前没有事务则会报错。 |
PROPAGATION_NESTED | 不管当前有没有事务都会启动一个新事务,如果当前有事务则与当前事务共用一个commit操作。 |
PROPAGATION_NEVER | 不能运行在事务中,如果当前存在事务则抛出异常。 |
3.超时时间
指定事务的最大运行时间。使用int指定,单位是秒。
4.是否只读
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
二、编程式事务
1、依赖的类
PlatformTransactionManager.java
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException; //获取事务的状态信息
void commit(TransactionStatus status) throws TransactionException; //提交事务
void rollback(TransactionStatus status) throws TransactionException; //回滚事务
}
TransactionTemplate.java
public <T> T execute(TransactionCallback<T> action) throws TransactionException();
private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException ;
该类继承了接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模板类定义,主要是通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
2.xml配置
配置
<!-- 引入外部文件-->
<context:property-placeholder location="dbConfig.properties"></context:property-placeholder>
<!-- 配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<bean id= "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
3.调用
Dao层
public int updatePrice(String isbn, int price) {
String sql = "UPDATE book set price = ? where isbn = ?";
return jdbcTemplate.update(sql, price, isbn);
}
Service层
public void updatePrice(final String isbn, final int price) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
bookDao.updatePrice(isbn, price);
int i = 1 / 0;
}
});
}
controller层
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tx.xml");
BookService bean = applicationContext.getBean(BookService.class);
bean.updatePrice("ISBN-001", 80);
TransactionTemplate的execute方法提供一个内部匿名类,用来我们写transaction代码,然后提供一个transactionStatus的参数,这样你可以控制回滚。这样一来,我们就不用写任何关于事务API的代码了。格式大概是 Boolean b = transactionTempate.execute(new TransactionCallBack() { 执行方法(TransactionStatus transactionStatus){} },当执行完成后返回一个boolean的值. 还有一个方法,就是不提供返回结果的。
由于Service层有异常,那么整个事务都需要进行回滚,数据库的值不变。
三、声明式事务
Spring的声明式事务控制即通过配置替代编码来处理事务,声明式事务控制不侵入开发的组件。管理建立在AOP基础上,本质是对方法前后进行拦截,所以声明式事务是方法级别的,使用的时候只需要在方法前面加上@Transactional注解
1.基于注解的声明式事务
1.1:xml配置:需要引入contex空间和tx空间
<!-- 配置声明式事务-->
<!-- 1.Spring中提供事务管理器(事务切面),配置事务管理器
2.开启基于注解的事务式事务,依赖tx名称空间
3.给事务加注解
-->
<bean id= "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
<!-- 包扫描-->
<context:component-scan base-package="com.shang"></context:component-scan>
<!-- 引入外部文件-->
<context:property-placeholder location="dbConfig.properties"></context:property-placeholder>
<!-- 配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
1.2:使用方法
@Transactional
public void checkout(String username, String isbn) {
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username, price);
System.out.println("结账完成");
}
1.3:关于Transactional注解
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED; //定义事务传播方式
Isolation isolation() default Isolation.DEFAULT; //定义隔离级别
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; //定义超时时间
String timeoutString() default ""; //字符串型超时时间
boolean readOnly() default false; //是否可读
Class<? extends Throwable>[] rollbackFor() default {};//需要回滚的类
String[] rollbackForClassName() default {};//需要回滚的类名
Class<? extends Throwable>[] noRollbackFor() default {}; //不需要回滚的类
String[] noRollbackForClassName() default {};//不需要回滚的类名
}
这里补充一点:Spring对于异常是否回滚是有默认情况的:编译时异常不回滚,运行时异常回滚。
我们可以通过rollbackFor()来自定义哪些编译时异常回滚,noRollbackFor()来定义哪些运行时异常不回滚。
2.基于xml注解的声明式事务
<!-- 配置事务切面 -->
<aop:config>
<!--切面模板-->
<aop:pointcut
expression="execution(* com.shang.service.*.*(..))"
id="txPointCut"/>
<!-- 将切入点表达式和事务属性配置关联到一起 ,织入-->
<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 配置基于 XML 的声明式事务,事务增强 -->
<tx:advice id="myTx" transaction-manager="transactionManager">
<tx:attributes>
<!-- 设置具体方法的事务属性 -->
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase"
isolation="READ_COMMITTED" no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException" propagation="REQUIRES_NEW" read-only="false"
timeout="10"/>
</tx:attributes>
</tx:advice>