Spring中事务的一些理解

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) {
 //....
 }
发布了63 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/myt0929/article/details/105039098