事务回顾
1、事务的特性:
①原子性:事务是不可分割的,要么都成功,要么都失败
②一致性:事务发生前和事务发生后,事件的总数状态不变。(例如:转账操作)
③隔离性:多个事务在执行时可能会收到彼此的影响,所以采用了不同的隔离级别来避免这种影响
④持久性:事务一旦提交,它对数据的改变就是永久性的,即使数据库发生故障也不应该有什么影响。
2、多个事务造成的影响
①脏读:一个事务读取到了另一个事务还没有提交的数据(脏数据),导致了查询数据不一致的效果
②不可重复读:在同一个事务中,读取到了已经提交事务的数据,导致数据查询结果不一致。
③幻读:一个事务读取到了另一个事务提交的insert数据,导致查询出原来没有的数据。
3、幻读和不可重复读的区别
幻读是指读到了其他已经提交事务的新增数据,不可重复读是指读到了已经提交事务的更改数据(更改或者删除)
4、解决不可重复读的幻读的策略
》防止不可重复读到更改数据,只需要对操作的数据添加行级锁,阻止操作重的数据发生变化;
》防止读到新增数据,往往需要添加表级锁(将整张表锁定),防止新增数据(orcale使用多版本数据的方式实现)
Spring在数据操作上的支持:
spring在数据操作方面体提供了如下的支持:
》对流行的持久化技术的支持(Hibernate,MyBatis等)
》还提供了一个简化的JDBC API操作的spring JDBC框架。
》相面DAO制定了一个通用的异常体系,屏蔽了具体持久化技术的异常
》提供了模板类简化各种持久化技术的使用
Spring事务管理
spring提供了两种事务管理方式,一种是
编程式事务,另一种是基于AOP的
声明性事务,可以让程序从事务代码中解放出来。
不仅如此,Spring为事务管理提供了一致的编程模板,在高层次建立了统一的事务抽象。也就是说,不管是选择Spring JDBC、Hibernate还是MyBatis,Spring都可以让用户用统一的编程模型进行事务管理。
1、事务管理的抽象接口
Spring事务管理高层抽象主要包括3个接口,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/b8703a7d300287327855ffd697e3749d.png)
①PlatformTransactionManager(平台事务管理器)
事务管理器是真正用来管理事务的,比如说事务的提交、回滚。对于不同的持久化框架,Spring提供了不同的平台事务管理器
![](https://i-blog.csdnimg.cn/blog_migrate/e8e12009c31ace8b61bf978c9e5c1933.png)
②TransactionDefinition(事务定义信息)
描述事务的隔离级别、超时时间、是否为只读事务和事务传播规则等控制事务具体行为的事务属性,这些属性可以通过xml配置或者注解描述提供,也可通过手工编程设置。
》Isolation(隔离级别)
default: :使用底层数据库默认的隔离级别
read_uncommited :允许读取还未提交的并发事务还没提交的数据
read_commited :允许读取并发事务提交后的数据
repetable_read: :对相同事务的读取保证是一致的。
serializeable :事务串行进行,安全但是慢。
》propagation(传播行为)
主要解决业务层方法之间的相互调用问题,到底使用那个方法中的事务,事务是如何进行传递问题。
propagation_required:必须有事务,如果没有,就新建一个
propagation_supports:事务可有可没有,如果没有,则在非事务的环境中执行
propagation_mandatory:使用当前的事务,如果当前没有事务,抛出异常
propagation_required_new:不管有没有事务,都会新进一个事务,原先的会被挂起。
propagation_not_supported:如果当前存在事务,就把当前事务挂起
propagation_never:以非事务执行,如果当前存在事务,则抛出异常
propagation_nested:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则
与porpagation_required类似。
》read-only(只读事务)
只读事务不修改任何数据,也就是说不能有update、delete、insert
》timeout(事务超时)
事务在超时前能运行多久。超时时间后,事务回滚。
③TransactionStatus(事务运行状态)
代表了一个事务的具体运行状态。事务管理器可以通过该接口获取事务运行期的状态信息,也可以通过该接口间接地回滚事务。该接口继承与SavepointManager接口
![](https://i-blog.csdnimg.cn/blog_migrate/c13affd43057d7a9be840b72223da763.png)
createSavepoint():创建一个保存点对象。
realseSavepoint():释放一个保存点。如果事务提交,所有保存点将会自动释放。
rollbackSavepoint():将事务回滚到特定的保存点上,被回滚的保存点将会自动释放。
这三个接口的关系:
Spring在进行事务管理的时候,会首先通过事务定义的信息(比如,使用了什么隔离级别,什么传播行为),然后通过平台事务管理器真正的去管理事务,在进行事务的管理的过程中,会产生相应的状态,这些状态就保存在了TransactionStatus中
2、Spring事务同步管理器
原始的jdbc操作的时候,会将Connection等资源对象在同一时刻是不能多线程共享的。一般的操作是使用ThreadLocal为不同的事务线程提供一个副本。在Spring中当然也有这种考虑,Spring为不同的持久化技术提供了一套从TransactionSynchronizationManager中获取对应线程绑定资源的工具类
![](https://i-blog.csdnimg.cn/blog_migrate/25e346d5de785aa1a3bdc7093b6d95c7.png)
Spring支持两种方式事务管理
1、编程式的事务管理
Spring提供了TransactionTemplate模板类来支持编程式事务,
TransactionTemplate和那些持久化模板类一样是线程安全的,所以可以在多个业务类中共享TransactionTemplate实例来进行事务管理。
<!--基于数据源的事务管理器-->
<bean
id
="transactionManager"
class
="org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<property
name
="dataSource"
ref
="dataSource"
></property>
</bean>
<!--声明事务模板类-->
<bean
id
="transactionTemplate"
class
="org.springframework.transaction.support.TransactionTemplate"
>
<property
name
="transactionManager"
ref
="transactionManager"
/>
</bean>
|
/**
* 编程式的事务管理
*
@return
void
*/
public void
transforAccount
(
final
String out
, final
String in
, final
Double money){
transactionTemplate
.execute(
new
TransactionCallbackWithoutResult() {
@Override
protected void
doInTransactionWithoutResult
(TransactionStatus status) {
accountDao
.outMoney(
out
,
money
)
;
int
i=
1
/
0
;
accountDao
.inMoeny(
in
,
money
)
;
}
})
;
}
|
已一个简单的转账案例作为说明,如果转账过程中抛出异常,转账操作将不会完成。
2、使用xml配置声明式事务管理
Spring是通过AOP实现的。对代码的侵入性最小,可以让事务管理代码完全从业务代码中移除,非常符合非侵入式轻量级容器的理念。
Spring在基于schema的配置中添加了一个tx命令空间,在配置文件中以明确结构化的方式定义事务属性,配合切面工具,使得业务类方法事务的配置得到极大的简化。
<!-- 事务增强 -->
<
tx
:advice
id
="txAdvice"
transaction-manager
="transactionManager"
>
<
tx
:attributes>
<!-- 对那些方法进行增强 -->
<
tx
:method
name
="save*"
propagation
="REQUIRED"
/>
<
tx
:method
name
="insert*"
propagation
="REQUIRED"
/>
<
tx
:method
name
="add*"
propagation
="REQUIRED"
/>
<
tx
:method
name
="create*"
propagation
="REQUIRED"
/>
<
tx
:method
name
="delete*"
propagation
="REQUIRED"
/>
<
tx
:method
name
="update*"
propagation
="REQUIRED"
/>
<
tx
:method
name
="find*"
propagation
="SUPPORTS"
read-only
="true"
/>
<
tx
:method
name
="select*"
propagation
="SUPPORTS"
read-only
="true"
/>
<
tx
:method
name
="get*"
propagation
="SUPPORTS"
read-only
="true"
/>
</
tx
:attributes>
</
tx
:advice>
<!-- 使用切点表达式定义目标方法 -->
<
aop
:config>
<!--通过aop定义事务增强切面,并引入事务增强-->
<
aop
:advisor
advice-ref
="txAdvice"
pointcut
="execution(* cn.*.service..*.*(..))"
/>
</
aop
:config>
|
@Service
public class
AccountService {
@Autowired
private
AccountDao
accountDao
;
public void
updateAccount
(
final
String out
, final
String in
, final
Double money){
accountDao
.outMoney(out
,
money)
;
int
i=
1
/
0
;
accountDao
.inMoeny(in
,
money)
;
}
}
|
![](https://i-blog.csdnimg.cn/blog_migrate/460b8d77ee127c7694abc573a7c20c2f.png)
3、使用注解配置声明式事务(@Transaction)
Spring还提供了基于 注解的事务配置,只需要在同期总配置基于注解的事务增强驱动,即可启动基于注解的声明式事务。
<!--基于数据源的事务管理器-->
<bean
id
="transactionManager"
class
="org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<property
name
="dataSource"
ref
="dataSource"
></property>
</bean>
<!--开启注解事务,对标注@Transaction注解的Bean进行加工处理,织入事务管理切面-->
<
tx
:annotation-driven
transaction-manager
="transactionManager"
proxy-target-class
="false"
order
="1"
/>
|
@Transactional
@Repository
public class
AccountDao {
@Autowired
private
JdbcTemplate
jdbcTemplate
;
public void
outMoney
(String out
,
Double money){
String sql=
"update account set money=money - ? where name = ?"
;
jdbcTemplate
.update(sql
,
money
,
out)
;
}
public void
inMoeny
(String in
,
Double money){
String sql=
"update account set money=money + ? where name = ?"
;
jdbcTemplate
.update(sql
,
money
,
in)
;
}
}
|
①<tx:annotation-driven>注解驱动属性说明
transaction-manager:引用指定的事务管理器
proxy-target-class:为true,表示通过cglib创建子类的方式来代理业务类;为false,表示使用jdk动态代理的方式。
order:如果业务类除事务切面外,还需要织入其他的切面,可以通过该属性来空值目标连接点的织入顺序。
② @Transaction注解的作用位置:
可被应用于
接口定义和接口方法、
类定义和类的public方法上。但是注解是不能继承的,所以在接口中添加的注解是不会被业务实现类继承的,所以一般添加在
具体的业务实现类上。
》类级别的注解只对public的方法起作用。
Spring的事务管理是基于接口管理或动态字节码技术的。对于基于动态代理的AOP事务来说,代理的方法只能是public或public final修饰的,不能使用static修饰。对于基于CGLIB字节码动态代理的方案,由于final、static、private修饰符的方法子类都不能覆盖,所以无法实施AOP 增强。
》方法上的注解会覆盖类上的注解
注意;
那些不被Spring事务增强的特殊方法并非就不工作在事务环境中。由于Spring事务管理的传播行为,内部的方法可以工作在外部方法所启动的事务上下文中,只是这些方法不能启动事务罢了。
③ @Transaction注解的默认属性:
事务隔离级别:isolation_default
事务传播行为:propagation_required
读写事务属性:读/写事务
超时时间: 依赖于底层的事务系统的默认值
回滚设置: 任何运行期异常引发回滚,任何检查性异常不会引发回滚
![](https://i-blog.csdnimg.cn/blog_migrate/ea57957df5e75578ebc1cf6098349a00.png)
总结:
》
Spring默认的事务回滚规则为:“运行期异常回滚,检查性异常不会滚”。
》Spring的事务管理是Spring AOP技术的典型应用。事务管理作为切面织入目标业务方法的周围,使得业务方法完全从事务代码中解脱出来,代码的复杂度大大降低。
》Spring的事务配置相对来说比较简单,主要提供两方面的信息:第一,
切点的信息,用于定位实施事务切面的业务类方法;其二,
控制事务行为的事务属性,这些属性包括隔离级别、事务的传播行为等
》Spring的传播机制可以很好的应对事务方法嵌套调用的情况。
》因为单例对象不存在线程安全问题,所以经过事务管理增强的单实例Bean可以很好的工作在多线程环境中
》Spring AOP事务增强有两个方案:一个是基于接口的动态代理,另一个是基于CGLIB董改生成子类的代理。注意有些特殊的方法不能被Spring AOP代理,所以无法享受事务增强
》在使用Spring JDBC如何直接获取Connection,可能会造成连接泄露。应尽量使用
DataSourceUtils获取数据连接