Spring事务

1、事务概述

事务是一系列的动作,他们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会会滚到最开始的状态,仿佛什么都没发生过一样,用来保证数据的完整性和一致性。

特性:

1、原子性:事务中包含的所有操作要么全部成功,要么全部失败回滚,不可再分。

2、一致性:就是说一个事务执行之前和执行之后都必须处于一个一致性状态。,比如a和b两个用户,a向b转账后,ab两个用户的总额前后相等。

3、隔离性:多个事务之间相互隔离。互不影响

4、持久性:事务一旦提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作

2、Spring事务管理

2.1 编程式事务

手动控制事务,细粒度的事务控制,可以对指定的方法、或者其某几行添加事务控制,好处比较灵活,但是开发比较繁琐,光是什么begin、commit、rollback就够烦的。

依赖:

        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>

applicationContext.xml:

    <!--1、数据源对象:C3P0连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 2、JdbcTemplate工具类实例-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务-->
    <bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

transaction管理:

@Component
public class TransactionManagerUtils {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    public TransactionStatus begin(){
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());
        System.out.println("开启事务");
        return transaction;
    }
    public void commit(TransactionStatus transaction){
        dataSourceTransactionManager.commit(transaction);
        System.out.println("提交事务");
    }
    public void rollback(TransactionStatus transaction){
        dataSourceTransactionManager.rollback(transaction);
        System.out.println("回滚事务");
    }
}

service:

    @Autowired
    private TransactionManagerUtils transactionManagerUtils;
    public void add(){
        TransactionStatus begin = transactionManagerUtils.begin();
        try{
            userEntityDao.add("xiaoming",23);
            transactionManagerUtils.commit(begin);
        }catch (Exception e){
            transactionManagerUtils.rollback(begin);
        }
    }

dao:


    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void add(String name,Integer age){
        String sql="insert into users(name,age) values (?,?);";
        int update=jdbcTemplate.update(sql,name,age);
        System.out.println("updateResult:"+update);
    }

test:

        UserEntityService userEntityDao = (UserEntityService) applicationContext.getBean("userEntityService");
        userEntityDao.add();

 

2.2 声明式事务

事务原理底层就是通过AOP+环绕通知+异常通知实现的。

    @Autowired
    private TransactionManagerUtils transactionManagerUtils;
    private TransactionStatus transactionStatus;
    public void begin(){
    }

    public void after(){
    }

    public void afterReturning(){
    }

    public void afterThrowing(){
        transactionManagerUtils.rollback(transactionStatus);
        System.out.println("回滚事务");
    }

    public void aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        transactionStatus = transactionManagerUtils.begin();
        System.out.println("开启事务");
        System.out.println("我是环绕通知-前");
        proceedingJoinPoint.proceed();
        System.out.println("我是环绕通知-后");
        transactionManagerUtils.commit(transactionStatus);
    }

注意:这里千万不要在After里面commit,因为这个是总是执行的,所以总会commit成功!

还有在操作的时候,不要在dao层以下try、catch,只有将其抛出才能在上层捕获然后进行异常通知。

2.2.1 xml方式

    <!-- 配置事务-->
    <bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务增强-->
    <tx:advice id="txAdvice" transaction-manager="DataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" read-only="false"/>
        </tx:attributes>
    </tx:advice>
    <!--AOP配置:拦截那些方法(切入点表达式)+应用上面的事务增强-->
    <aop:config>
        <aop:pointcut id="pt2" expression="execution(* com.spring4.service.UserEntityService.add3())"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"/>
    </aop:config>

2.2.2 注解方式

    <!-- 配置事务-->
    <bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="DataSourceTransactionManager"/>

    @Transactional
    public void add4(){
        userEntityDao.add("xiaoming2",23);
//        int i=1/0;
    }

3 事务传播行为

举个例子:

@Service
public class LogService {
    @Autowired
    private LogDao logDao;
    
    @Transactional
    public void addLog(){
        logDao.addLog();
    }
}
    @Transactional
    public void add4(){
        logService.addLog();
        userEntityDao.add("xiaoming2",23);
        int i=1/0;
    }

其他代码就不粘贴了,对于这样的一个事务,logService.addLog()是日志的操作记录,而按照事务的处理规则,此时log和user都要回滚,那我们怎么去追踪日志啊,难道失败了就不记录这个日志了吗???所以事务传播行为应运而生。

事务传播行为指的就是当以一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

比如methodA事务方法调用methodB事务方法时,methodB是继续在调用者的事务中运行呢,还是为自己开启一个新的事务运行,这就是有methodB的事务传播行为决定的。

Spring定义了七种传播行为:

Propagation.REQUIRED:支持当前事务,如果没有当前事务,就新建一个事务,如果有就使用当前事务。

比如上述的logService中@Transactional(propagation=Propagation.REQUIRED),那么如果userService有声明事务,则两者共用这个事务,userService抛出异常两者都会回滚,事务默认传播行为也是这种。

Propagation.REQUIRES_NEW:简单来说就是两个使用的是不同的事务,如果User没有事务,那么log里直接新建一个事务,如果有,则把当前事务挂起然后新建一个事务。比如上述的logService中@Transactional(propagation=Propagation.REQUIRES_NEW),则log的不会回滚。

Propagation.SUPPORTS:支持当前事务,如果User已经有事务了,那么log就用User的事务,比如上述的logService中@Transactional(propagation=Propagation.SUPPORTS)就不会回滚,如果User没有事务那么就以非事务进行。

    @Transactional(propagation = Propagation.SUPPORTS)
    public void addLog(){
        logDao.addLog();
        int i=1/0;
    }

//    @Transactional(propagation = Propagation.REQUIRED)
    public void add4(){
        logService.addLog();
        userEntityDao.add("xiaoming2",23);
        int i=1/0;
    }

此时如果上述代码如此,那么addLog这条日志还是会添加成功,所以如果当前没有事务,就以非事务形式运行。

Propagation.NOT_SUPPORTS:不支持当前事务,如果当前有事务,直接挂起当前事务,然后自己非事务运行。

    @Transactional(propagation = Propagation.REQUIRED)
    public void add4(){
        logService.addLog();
        userEntityDao.add("xiaoming2",23);
        int i=1/0;
    }


    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void addLog(){
        logDao.addLog();
        int i=1/0;
    }

不管add4有没有事务(也就是是否取消注解),addLog都不会用你的,也不用自己的,所以即使他自己抛出异常了,这条记录也是会添加成功的(非事务)。

Propagation.MANDATORY:支持当前事务,如果当前没有事务,那么就会抛出异常。


    @Transactional(propagation = Propagation.REQUIRED)
    public void add4(){
        logService.addLog();
        userEntityDao.add("xiaoming2",23);
        int i=1/0;
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void addLog(){
        logDao.addLog();
    }

如果add4已经有事务了,那么addLog就共用这个事务,如果add4没有就会抛出异常,取消add4的事务注解即可看到这些效果。

Propagation.NEVEVR:不支持当前事务,如果当前有事务,抛出异常,和MANATORY正好相反

Propagation.NESTED:如果当前存在事务,那么事务就嵌套在当前事务,并且有自己的commit和rollback。如果当前没有事务,则使用自己的事务进行commit和rollback(REQUIRED).

    @Transactional(propagation = Propagation.NESTED)
    public void addLog(){
        logDao.addLog();
        int i=1/0;
    }



    @Transactional(propagation = Propagation.REQUIRED)
    public void add4(){
        logService.addLog();
        userEntityDao.add("xiaoming2",23);
        int i=1/0;
    }

比如这样,addLog的事务嵌套在add4的事务中执行,addLog的事务自己会commit和rollback,但是add4的回滚也会引起addLog的回滚。但是addLog的rollback不会引起add4的回滚。

===》内层不影响外层,外层影响内层。

4 Spring事务隔离级别

4.1 事务的安全

1、丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖第一个事务的更新,从而导致第一个事务更新的诗句丢失,这是由于没有加锁造成的。

2、幻读:同样的事务操作过程中,不同时间段多次(不同事务)读取同一数据,读取到的数据不一致(一般是行数变多或者变少,主要是删除或者插入了嘛)。解释一下,就是说A事务删除一条数据后,B事务在删除前读到10条,而C事务在删除后只读到9条。

3、脏读:一个事务读取到另一个未提交事务的内容

4、不可重复读:同一事务中,多次读取内容不一致(一般行数不变,而内容变了,更新时候)

幻读和不可重复读区别:幻读的重点在于插入和删除,即第二次查询会发现比第一次查询数据变少或者变多了,以至于给人一种幻象一样,而不可重复读重点在于修改,即第二次查询会发现查询结果比第一次结果不一致,也就是第一次结果不可重现了。

4.2 事务隔离级别

spring有五大隔离级别,其在TransactionDefinition接口中定义,其默isolation_default(底层数据库默认级别),其他四个隔离级别跟数据库隔离级别一致。

1、ISOLATION_DEFAULT:用底层数据的默认级别,数据库管理员设置什么就是什么。

2、ISOLATION_READ_UNCOMMITTED(未提交读):最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)。

3、ISOLATION_READ_COMMITTED(提交读):一个事务提交后才能被其他事务读取到(该隔离界别禁止其他事务读取到未提交事务的数据,但是还是会出现幻读、不可重复读)、SQLServer默认级别

4、ISOLATION_REPEATABLE_READ(可重复读):保障多次读取同一数据时,其值都和事务开始时候的内容一致,禁止读取到别的事务未提交的数据(改隔离可防止脏读、不可重复读、但会出现幻读)MySql默认级别,更改可通过set transaction isolation level 级别

5、ISOLATION_SERIALIZABLE(序列化):代价最高的最可靠的隔离级别(该隔离级别能防止脏读、不可重复读、幻读)。

数据库隔离级别越高,执行代价越高,并发执行能力越差,因此在实际项目开发使用时要综合考虑,为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值