Spring的事务管理是依靠底层数据库的,也就是说他自己不管理事务,只提供接口,由底层数据库实现。 Spring定义了一个接口,PlatformTransactionManager 接口来统一标准,对不同的框架又有不同的实现类。JDBC使用DataSourceTransactionManager,Hibernate 时使用 HibernateTransitionManager 对象。
一、Spring 事务的传播属性
1、REQUIRED(0,这个是默认的属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
2、SUPPORTS(1)
支持当前事务,如果当前没有事务,就以非事务方式执行。
3、MANDATORY(2)
支持当前事务,如果当前没有事务,就抛出异常。
4、REQUIRES_NEW(3)
新建独立事务,如果当前存在事务,把当前事务挂起。 内外层事务相互无关联。
5、NOT_SUPPORTED(4)
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6、NEVER(5)
以非事务方式执行,如果当前存在事务,则抛出异常。
7、NESTED(6)
新起一个嵌套事务,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
二、Spring事务的隔离级别
说道事务的隔离级别,先看下事务的并发引起的问题:
- 脏读
- 不可重复读
- 幻读
脏读:一个事务正在对数据进行更新操作,但是更新还未提交,另一个事务这时也来操作这组数据,并且读取了前一个事务还未提交的数据,而前一个事务如果操作失败进行了回滚,后一个事务读取的就是错误数据,这样就造成了脏读。
事务一 | 事务二 |
---|---|
读A为1 | |
更新A为2 | |
读到A为2 | |
事务回滚 | |
读到数据为脏数据 |
不可重复读:一个事务多次读取同一数据,在该事务还未结束时,另一个事务也对该数据进行了操作,而且在第一个事务两次次读取之间,第二个事务对数据进行了更新,那么第一个事务前后两次读取到的数据是不同的,这样就造成了不可重复读。简单一点:在于事务2在事务1第二次读取时,提交了数据。导致事务1前后两次读取的数据不一致。
事务一 | 事务二 |
---|---|
读A为1 | |
更新A为2 | |
读到A为2 | |
事务提交 | |
前后两次读到数据不一致 |
幻读:第一个数据正在查询符合某一条件的数据,这时,另一个事务又插入了一条符合条件的数据,第一个事务在第二次查询符合同一条件的数据时,发现多了一条前一次查询时没有的数据,仿佛幻觉一样,这就是幻读。简单点:前后两次读取的数据量不一致。不可重复读重点在于update和delete,而幻读的重点在于insert。
事务一 | 事务二 |
---|---|
读A为1数据为1条 | |
添加一条A为1 | |
读到A为1数据为2条 | |
事务提交 | |
前后两次读到数据集不一致 |
Spring隔离级别:
0、DEFAULT (默认)
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
1、Read UnCommitted(读未提交)
最低的隔离级别。一个事务可以读取另一个事务并未提交的更新结果。
2、Read Committed(读提交)
大部分数据库采用的默认隔离级别。一个事务的更新操作结果只有在该事务提交之后,另一个事务才可以的读取到同一笔数据更新后的结果。
3、Repeatable Read(重复读)
MySQL的默认级别。整个事务过程中,对同一笔数据的读取结果是相同的,不管其他事务是否在对共享数据进行更新,也不管更新提交与否。
4、Serializable(序列化)
最高隔离级别。所有事务操作依次顺序执行。注意这会导致并发度下降,性能最差。通常会用其他并发级别加上相应的并发锁机制来取代它。
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | 不可避免 | 不可避免 | 不可避免 |
读提交 | 避免 | 不可避免 | 不可避免 |
重复读 | 避免 | 避免 | 不可避免 |
序列化 | 避免 | 避免 | 避免 |
为什么重复读不能避免幻读:行锁。当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
三、Spring事务实现方式
1、tx切面
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="false">
<context:annotation-config />
<context:component-scan base-package="com" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="initialSize" value="2" />
<property name="minIdle" value="${dataPools.minIdle}"/>
<property name="maxActive" value="${dataPools.maxActive}"/>
<!--超时等待时间以毫秒为单位-->
<property name="maxWait" value="${dataPools.maxWait}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="poolPreparedStatements" value="false"/>
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="filters" value="vault" />
<property name="connectionProperties" value="config.decrypt=true" />
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="validationQuery" value="SELECT 1"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan">
<list>
<value>com.demo.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.query.substitutions">true 'Y', false 'N'</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.jdbc.batch_size">50</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.use_sql_comments">false</prop>
</props>
</property>
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<aop:config>
<aop:advisor id="serviceTx1" advice-ref="txAdvice" pointcut="execution(* *..service.*(..))"/>
</aop:config>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>
2、用注解
@Transactional
public void insertIntoData(String name, Long id) {
//....
}