Spring事务管理

1. Spring事务简介【重点】

Spring提供的事务管理是数据层的事务还是业务层的事务?

1.1 Spring事务作用

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败

  • Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败

2. 声明式事务控制

2.1 编程式事务控制相关对象

PlatformTransactionManager

PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

在这里插入图片描述

注意:

PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager

Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

TransactionDefinition

TransactionDefinition 是事务的定义信息对象,里面有如下方法:
在这里插入图片描述

事务隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

  • ISOLATION_DEFAULT

  • ISOLATION_READ_UNCOMMITTED

  • ISOLATION_READ_COMMITTED

  • ISOLATION_REPEATABLE_READ

  • ISOLATION_SERIALIZABLE

2.2 什么是声明式事务控制

Spring 的声明式事务顾名思义就是采用声明的方式来处理事务,是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替编程式的处理事务。

声明式事务处理的作用

  • 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可

  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便

注意:Spring 声明式事务控制底层就是AOP。

2.2.1 声明式事务控制的实现

声明式事务控制明确事项:

  • 谁是切点?

  • 谁是通知?

  • 配置切面?

2.2.2 基于xml转账业务案例:

需求

  • 需求:实现任意两个账户间转账操作

  • 需求微缩:A账户减钱,B账户加钱

实现分析:

  1. 构建实体数据

  2. 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)

  3. 业务层提供转账操作(transfer),调用减钱与加钱的操作

  4. 开启注解扫描,开启事务管理

  5. 事务增强配置

  6. 配置事务 AOP 织入

  7. 测试代码运行

结果分析:

  • 程序正常执行时,账户金额A减B加,没有问题

  • 程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

代码实现步骤

1、构建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class IAccount {
    private Integer id;
    private String name;
    private double money;
}

2、构建Dao接口及实现类,实现数据读取

public interface IAccountDao {
    //转入
    public void in(String inMan,Double money);
    //转出
    public void out(String outMan,Double money);
}

实现类

@Repository
public class IAcountDaoImpl implements IAccountDao {@Autowired
    private JdbcTemplate jdbcTemplate;
    //转入多少钱
    @Override
    public void in(String inMan, Double money) {
        jdbcTemplate.update("update by_account set money=money+? where name=?",money,inMan);
    }
    //转出多少钱
    @Override
    public void out(String outMan, Double money) {
        jdbcTemplate.update("update by_account set money=money-? where name=? ",money,outMan);
    }
}

3、构建service接口及实现类,实现数据业务逻辑处理

public interface IAccountService {
    //转账业务方法
    public void transfer(String inMan,String outMan,double money);
}

ServiceImpl实现类

@Service("iAccountService")
public class IAccountServiceImpl implements IAccountService {@Autowired
    private IAccountDao iAccountDao;@Override
    public void transfer(String inMan, String outMan, double money) {
        iAccountDao.out(outMan,money);
        int i=1/0;
        iAccountDao.in(inMan,money);
    }
}

4、开启注解扫描,开启事务管理

命名空间修改:
在这里插入图片描述

开启注解扫描

<!-- 开启注解扫描-->
    <context:component-scan base-package="com.by"/>
    <!--加载jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties" system-properties-mode="NEVER"/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" >
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
<!--    创建jdbctemplate模板对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

开启事务管理

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

5、开启事务增强

        <!--事务增强配置-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--设置事务的属性信息-->
        <tx:attributes>
        <!-- method 代表切入点
            name=”代表方法“
            isolation=”代表隔离级别“
            propagation=”传播行为“
            timeout=”失效时间“
            read-only=”是否只读“
        -->
<!-- 
    <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
    <tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
    <tx:method name="find" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
-->
            <tx:method name="*"/>
        </tx:attributes>
</tx:advice>

6、配置事务 AOP 织入

<!--    事务aop增强-->
   <aop:config>
       <aop:pointcut id="myPointcut" expression="execution(* com.by.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
   </aop:config>

7、测试代码运行

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class SpringTxTest {
    @Autowired
    private IAccountService iAccountService;
    @Test
    public void TestTx(){
        iAccountService.transfer("Tom","Jerry",500);
    }
}

2.3 切点方法的事务参数的配置说明

<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

其中,tx:method 代表切点方法的事务参数的配置,例如:

<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
  • name:切点方法名称

  • isolation:事务的隔离级别

  • propogation:事务的传播行为

  • timeout:超时时间

  • read-only:是否只读

2.4 知识要点

声明式事务控制的配置要点

  • 平台事务管理器配置

  • 事务通知的配置

  • 事务aop织入的配置

2.5 基于注解事务处理案例

代码实现:

前期工作准备
1、编辑jdbcConfig配置对事务处理的加载
2、SpringConfig配置类的构建
3、使用注解@Transactional 处理业务层转账操作
4、测试运行

实现步骤

1、编辑jdbcConfig配置对事务处理的加载

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;@Bean
    public DataSource dataSource(){DruidDataSource ds=new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }//配置jdbcTemplate模板对象
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){return new JdbcTemplate(dataSource);
    }
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        //创建datasource支持的 事务对象
        DataSourceTransactionManager transactionManager=new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);return transactionManager;
    }
}

2、SpringConfig配置类的构建

@Configuration
@ComponentScan("com.by")
@EnableAspectJAutoProxy //开启aop
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class)
//开启注解事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

3、业务层添加注解

@Repository
@Transactional
public class IAcountServiceImpl implements IAcountService
{

4、编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class SpringAnnTest {@Autowired
    private IAccountService service;
    @Test
    public void Testann(){
        service.transfer("Tom","Jerry",500);
    }}

2.6 注解配置声明式事务控制解析

①使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。

②注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。

③使用在方法上,不同的方法可以采用不同的事务参数配置。

④Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />

3. Spring事务角色【理解】

什么是事务管理员,什么是事务协调员?

3.1 Spring事务角色

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法

  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

在这里插入图片描述

4. Spring事务相关配置

什么样的异常,Spring事务默认是不进行回滚的?

4.1 事务配置

在这里插入图片描述

说明:对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于编译器异常,Spring事务是不进行回滚的,所以需要使用rollbackFor来设置要回滚的异常。

4.2 案例:转账业务追加日志

需求和分析

  • 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕

  • 需求微缩:A账户减钱,B账户加钱,数据库记录日志

  • 分析:①:基于转账操作案例添加日志模块,实现数据库中记录日志

  • ②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能

  • 实现效果预期: 无论转账操作是否成功,均进行转账操作的日志留痕

  • 存在的问题: 日志的记录与转账操作隶属同一个事务,同成功同失败

  • 实现效果预期改进: 无论转账操作是否成功,日志必须保留

  • 事务传播行为:事务协调员对事务管理员所携带事务的处理态度

在这里插入图片描述

【准备工作】环境整备

USE spring_db;
CREATE TABLE tbl_log(
    id INT PRIMARY KEY AUTO_INCREMENT,
    info VARCHAR(255),
    createDate DATE
);
//logDao 接口
public interface LogDao {
    public void log(Log log);
}
//Log Impl实现类
@Repository
public class LogDaoImpl implements LogDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;@Override
    public void log(Log l) {String sql="insert into tbl_log (info,createDate) values(info=?,createDate=?)";
        Object[] obj={l.getInfo(),l.getCreateDate()};
        int update = jdbcTemplate.update(sql,obj);
        if(update>0){
            System.out.println("成功");
        }
    }
}
//service 接口 和实现类public interface LogService {
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional
    public void log(String outMon, String inMon, Double money);
}@Service
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;
    @Override
    public void log(String outMon, String inMon, Double money) {
        Log l=new Log();
        l.setInfo("转账操作由"+outMon+"到"+inMon+"金额:"+money);
        l.setCreateDate(new Date());
        logDao.log(l);
    }
}

【第一步】在AccountServiceImpl中调用logService中添加日志的方法

@Service
public class IAccountServiceImpl implements IAccountService {@Autowired
    private IAccountDao iAccountDao;
    @Autowired
    private LogService logService;@Transactional
    @Override
    public void transfer(String outMan, String inMan , double money) throws IOException{
        try{
        iAccountDao.out(outMan,money);
        int i=1/0;
        iAccountDao.in(inMan,money);
    }finally {
        logService.log(outMan,inMan,money);
        }
    }
}

【第二步】在LogService的log()方法上设置事务的传播行为

public interface LogService {
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String outMon, String inMon, Double money);
}

【第三步】运行测试类,查看结果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class SpringAnnTest {@Autowired
    private IAccountService service;
   
    @Test
    public void testTransfer() throws IOException {
    service.transfer("Tom","Jerry",500);
    }
}

4.3 事务传播行为

在这里插入图片描述

REQUIRED

REQUIRED是Spring默认的传播机制。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。REQUIRED传播机制最常用的情况是在一个事务中进行多个操作,要么全部成功,要么全部失败。如果其中一个操作失败,整个事务都将被回滚。

SUPPORTS

SUPPORTS传播机制表示当前方法如果在一个事务中被调用,则加入该事务;否则,以非事务的方式运行。SUPPORTS传播机制适用于对事务要求不高的操作,例如读取操作。

MANDATORY

MANDATORY传播机制表示当前方法必须在一个事务中被调用,否则将抛出异常。MANDATORY传播机制适用于在需要事务的情况下调用方法。

REQUIRES_NEW

REQUIRES_NEW传播机制表示当前方法必须开启一个新事务运行,如果当前存在事务,则挂起该事务。REQUIRES_NEW传播机制适用于对事务要求较高的操作,例如更新操作。

NOT_SUPPORTED

NOT_SUPPORTED传播机制表示当前方法不应该在事务中运行,如果存在事务,则挂起该事务。NOT_SUPPORTED传播机制适用于对事务没有要求的操作,例如日志记录等。

NEVER

NEVER传播机制表示当前方法不应该在事务中运行,如果存在事务,则抛出异常。NEVER传播机制适用于禁止在事务中运行的操作,例如安全检查等。

NESTED

NESTED传播机制表示当前方法必须在一个嵌套事务中运行,如果当前存在事务,则在该事务内开启一个嵌套事务;如果当前没有事务,则创建一个新事务。NESTED传播机制适用于需要分步操作的场景,例如订单中创建订单和订单项的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值