Spring中事务

Spring提供编程和声明两种方式的事务管理。spring事务本质式对数据库事务的进一步封装,spring事务还新增了事务传播机制,二者作用都是为了保证在并发情况下的CURD安全。事务的实现需要锁的支持,过多的使用事务会影响整个系统的并发性,尤其使用高级别的事务隔离级别。

编程方式管理事务

第一步:添加事务依赖包

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${version}</version>
</dependency>

第二步:将事务管理器注入IOC容器

<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
</bean>

第三步:在业务代码中添加事务

 		//1 开启事务
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
	try {
		// 进行需要处在同一事务中的数据库操作
		......
		// 2 正常结束,提交事务
		transactionManager.commit(status);
	} catch (SomeException) {
		// 错误处理
		......
		// 3 事务回滚
		transactionManager.rollback(status);
	}

声明方式管理事务

Spring声明方式的事务管理,实现方式有两种,第一种通过xml配置文件注入,第二种通过注解注入,二者都是通过spring AOP完成事务拦截和通过动态代理完成事务注入。

基于xml声明事务实现

xml中配置
<!--1 配置事务管理器,事务管理器需要和具体的数据源关联-->
    <bean id="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
    </bean>
     <!--2 配置事务通知,事务通知和事务管理器相关联-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务属性,关联要开始事务的方法,只有一个method子组件-->
        <tx:attributes>
            <!--设置事务属性:
                           isolation:事务隔离级级别,默认值为DEFAULT和数据库事务隔离级别一致,如果此处设置隔离级别和数据库隔离级别不一致,以spring隔离级别为准。
                           time-out:事务执行超时时间,默认值为-1表示永不超时,如果设置了值,单位秒。
                           read-only:事务是否为制度,默认是false,表示可以进行读写。
                           rollback-for:用来指定触发事务回滚的异常类型,可以是JDK中Throwable、Exception、Error及其子类,也可以是自定义类
                           no-rollback-for:用来指定哪些异常虽然发生,但事务依然正常提交。
                           propagation:事务传播行为,默认值为REQUIRED:如果有当前事务,则加入当前事务,否则创建一个新的事务;
                                                         REQUIRES_NEW:如果当前事务存在,则阻塞当前事务,新创建一个事务,否则直接新创建一个事务。
                                                         SUPPORTS:如果存在当前事务,则加入当前事务,否则以不加事务方式执行;
                                                         NOT_SUPPORT:始终以非事务方式执行,存在当前事务,则挂起当前事务。
                                                         NEVER:始终以事务方式执行,如果存在当前事务,会抛出一个异常。
                                                         MANDATORY:如果存在当前事务,则加入当前事务,否则抛出异常。
                                                         NESTED:如果当前事务存在,新创建一个子事务,当前事务回滚,子事务也要回滚,子事务回滚,当前事务将异常捕捉后,不必回滚。
            -->
            <tx:method name="*" rollback-for="Exception.class" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!-- 3 配置事务切入点,以及将事务切入点和事务属性关联起来-->
    <aop:config >
        <!--配置切入点-->
        <aop:pointcut id="accountPointCut"  expression="execution(* com.noodles.service.impl.*.*(..))"/>
        <!--关联切入点和事务属性-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="accountPointCut"></aop:advisor>
    </aop:config>

事务传播介绍
业务调用过程中,习惯将调用方称为上游,被调用方称为下游,下游的事务称为当前事务,上下游业务调用过程,上下游都有可能发生异常,Spring提高7种事务传播方式,用户可以自己具体业务内从,灵活设置事务传播方式。
在这里插入图片描述
配置的切入点AccountServiceImpl

public class AccountServiceImpl implements AccountService {
    @Override
    public void transferMoney(Account from, Account to,double amount) throws Exception {
        //1 查询账户from
        Account accountFrom = accountDao.getAccountByName(from.getName());
        //2 查询账户to
        Account accountTo = accountDao.getAccountByName(to.getName());
        //3 from 账户扣钱 amount
        if(accountFrom.getMoney() < amount){
            throw new Exception("账户余额不足,accountName="+accountFrom+",余额="+accountFrom.getMoney());
        }
        accountFrom.setMoney(accountFrom.getMoney()-amount);
        accountDao.updateAccount(accountFrom);
        int i =1/0;
        //4 to 账户加钱
        accountTo.setMoney(accountTo.getMoney()+amount);
        accountDao.updateAccount(accountTo);
    }

}

测试代码

public void transferMoney() throws Exception {
        Account from = new Account();
        from.setName("张三");
        Account to = new Account();
        to.setName("李四");
        double amount = 100.0;
        accountService.transferMoney(from,to,amount);
    }

测试结果:
抛出除数为零的异常,已经扣除金额的账户进行回滚。
在这里插入图片描述
在这里插入图片描述

基于注解声明式事务实现

在xml中开启基于注解的事务

<!--第一步 配置事务管理器,事务管理器需要和具体的数据源关联-->
    <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>

配置的切入点使用@Transactional注解

@Transactional添加到方法是对单个方法添加事务@Transactional添加类上,对类中所有的方法注入事务。事务的所有属性都可以在Transanctional的属性值中添加。

public class AccountServiceImpl implements AccountService {
		@Transactional
    @Override
    public void transferMoney(Account from, Account to,double amount) throws Exception {
        //1 查询账户from
        Account accountFrom = accountDao.getAccountByName(from.getName());
        //2 查询账户to
        Account accountTo = accountDao.getAccountByName(to.getName());
        //3 from 账户扣钱 amount
        if(accountFrom.getMoney() < amount){
            throw new Exception("账户余额不足,accountName="+accountFrom+",余额="+accountFrom.getMoney());
        }
        accountFrom.setMoney(accountFrom.getMoney()-amount);
        accountDao.updateAccount(accountFrom);
        int i =1/0;
        //4 to 账户加钱
        accountTo.setMoney(accountTo.getMoney()+amount);
        accountDao.updateAccount(accountTo);
    }

}

测试代码不变。

事务失效情况

1 使用注解方法非public
spring在方法进行动态代理时,会判断方法的修饰符是否为public。一般,被外部调用的方法需要设置成public方法,非public方法一般是类内部调用,根据动态代理实现方式,被代理类方法被外部方法调用时才会生成动态代理类,对应为方法添加事务,需要在新的动态代理类实现。
解决办法:可以使用编程方式在非public方法中添加事务。
2 同一个类内部同时被Transactional修饰的方法进行调用
失效原因时内部类调用,不会生成新的动态代理类,无法为被调用方法添加事务。动态代理实现
解决办法:将当前类注入自己内部,使用该注入对象进行类内方法调用。
3 方法被final或者static修饰
都不能在动态内部类中重写。
4 被@Transactional修饰的方法没有注册到IoC容器
5 多线程调用

public methodA(Account account){
   new Thread(()->{
	userDao.update(account);
	})
	...
}

一个线程对应一个数据连接,拥有不同数据库连接的线程无法同时提交和回滚。spring中的事务是通过数据库连接实现的,每个线程中维护着一个map,map的key是数据源,value是数据库连接。

private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");

6 try catch被滥用
发生的异常被捕捉到后,程序会继续成长运行。
解决办法:添加事务操作的代码不要使用try。
6 表本身不持支事务
数据库引擎使用myisam不支持事务。
解决办法:数据库切换成innodb存储引擎。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值