Spring学习 | 事务

24 篇文章 0 订阅


学习视频🎥:https://www.bilibili.com/video/BV1Vf4y127N5

一、简介

💬概述:事务时数据库操作最基本的单元,逻辑上表示一组操作要么都成功,如果出现失败就都失败

典型场景:银行转账

🔑事务的特点(ACID)

  1. 原子性(Atomicity):事务是最基本的数据库操作,不可再分(要么都成功,要么都失败)
  2. 一致性(Consistency):事务的整体保持不变,比如客户1和客户2两人的总余额为2千,那么他们两人无论怎么相互转账,两人的总额还是2千
  3. 隔离性(Isolation):各个事务之间不会相互影响
  4. 持久性(Durability):事务一旦提交,事务中的各个操作都会永久保存下来,即一旦提交事务,已修改的数据库信息就不能恢复

二、事务管理

2.1 概述

💡 事务管理一般添加到业务逻辑层(service层)

🔑底层原理:spring 事务管理底层是基于spring AOP 完成的,即AOP又是基于java的动态代理完成的

🔑事务管理方式

  1. 编程式事务管理(一般不采用):直接在事务的操作中添加try...catch()...语句捕获异常,在try{}语句块中开始处开启事务,最后提交事务,在catch(){}语句块(出现异常时)中回滚事务
  2. 声明式事务管理:利用spring事务管理相关的API,并使用注解或xml 文件进行配置即可

🔑事务管理API:spring提供一个接口PlatformTransactionManager,作为事务管理器,针对不同的框架又有不同的实现类

  • DataSourceTransactionManager实现类:适用于jdbc模板(jdbcTemplate)、spring整合Mybatis框架
  • HibernateTransactionManager实现类:适用于spring整合Hibernate框架

2.2 事务管理操作

💡 在进行spring事务管理前需要先完成jdbcTemplate的相关配置

🔑注解方式

① 在spring 配置文件中配置事务管理器(创建事务管理器对象),并注入dataSource属性

<bean id="tManager" class="org.springframework.jdbc.dataSource.DataSourceTransactionManager">
    <!-- 注入数据源dataSource属性 -->
    <property name="dataSource" ref="ds"></property>
</bean>

💡 <property>标签中的ref属性值为连接池的唯一标识id

② 在spring 配置文件中开启事务注解:需要先开启tx 名称空间,然后使用<tx:annotation-driven>

<tx:annotation-driven transaction-manager="tManager"></tx:annotation-driven>

💡 transaction-manager的属性值就是事务管理器对象的唯一标识id

③ 添加@Transactional注解

  • 在类上添加:类中的全部方法都添加事务
  • 在对应的方法上添加:只在该方法上添加事务
// 在类上添加
@Transactional
public class UserService {
    // code...
}

/*+------------------------------------------------+*/

// 在类中对应的方法中添加
public class UserService {

    @Transactional
    public void add() {
        // code...
    }
}

④ 注解参数设置

4.1 设置传播行为——propagation

  • 事务方法:对数据库数据进行修改的操作,如添加、修改、删除等

  • 传播行为:多事务方法之间相互调用(直接调用)的过程中事务的管理方式,如下例子

    // update()方法
    public void update() {
        // code...
    }
    
    /*+---------------------------------------------------+*/
    
    // add()方法中开启事务,并调用update()方法
    @Transactional
    public void add() {
        // 调用update()方法
        update();
    
        // code...
    }
    

    💡 事务方法不一定要开启事务(添加@Transactional注解),以上述为例,update()方法没有开启事务,add()方法开启了事务,但两个方法都是事务方法,所以add()中调用update()就是多事务方法之间的相互调用

  • spring 中事务传播行为:一共有7种,其中有两个比较常用(以上述例子为例)

    Ⅰ. REQUIRED:如果add()开启了事务,则在调用update()方法后,update()使用当前add()的事务完成操作;如果add()没有开启事务,则在调用update()方法后,update()创建一个新的事务来完成操作
    Ⅱ. REQUIRED_NEW:无论add()是否有开启事务,在调用update()方法后,都会创建一个新的事务来完成操作

4.2 设置隔离级别——isolation:针对事务的并发操作中遇到的三种读问题设置隔离级别

  • 三种读问题:脏读、不可重复读、幻读(虚读)

  • 四种隔离级别以及对应问题的解决

    隔离级别脏读不可重复读幻读
    读未提交(READ UNCOMMITTED)
    读已提交(READ COMMITTED)
    重复读(REPEATABLE READ)
    串行化(SERIALIZABLE)

    💡 Mysql中默认的隔离级别为重复读(REPEATABLE READ)

4.3 设置超时时间——timeout设定事务需要在一定时间内提交,如果超出这段时间还不进行提交就会回滚

  • 时间的设定:超时时间timeout的属性值以秒为单位默认值为-1,-1表示没有超时时间(即没有出现异常就正常提交事务,出现异常就回滚事务,而不是超时时间为0)

4.4 设置是否只读——readOnly:设置对数据库数据的操作是否只读

  • 只读:readOnly的属性值设为true,表示只能对数据库的数据进行查询操作
  • 非只读:readOnly的属性值设为false,false是默认值,表示能对数据库数据进行CRUD操作

4.5 设置是否回滚:对异常进行选择性回滚

  • rollbackFor:属性值为异常的类对象,表示遇到该异常就进行回滚,如Exception.class
  • noRollbackFor:属性值为异常的类对象,表示遇到该异常就不进行回滚,如Exception.class

⑤ 测试类

public class TranTest {

    /**
     * 测试不使用事务的转账操作
     */
    @Test
    public void testUpdateMoney() {
        // 创建工厂对象
        ApplicationContext context = new ClassPathXmlApplicationContext("transaction.xml");

        // 获取service(注意这里只能是service接口.class)
        CustomService cService = context.getBean("cService", CustomService.class);

        // 调用service方法
        cService.updateMoney();
    }
}

💡 测试类中通过工厂对象获取service对象时,在getBean()注入的是service类型受事务开启的影响

  1. 没有开启事务:getBean()中的参数可以是service接口.class,也可以是service实现类.class,因为在没有开启事务时,spring会为我们注入IOC容器中的对应service类型的bean对象
  2. 开启了事务:getBean()中的参数只能是service接口.class,因为spring 管理事务是基于spring AOP,而AOP又是基于Java的动态代理实现的,由于存在service接口,spring 会采用JDK动态代理,即使用Proxy 类来实现动态代理。因此通过getBean()获取的是接口的代理对象,通过这个代理对象来增强方法,实现事务控制。也就是说,如果开启事务(配置声明式事务),getBean()中就只能注入service接口而不能注入service实现类(如果写实现类会报错!)

🔑xml 文件配置(从xml 配置中可以明显看出spring事务管理底层是基于spring AOP)

① 配置事务管理器(创建事务管理器对象,与注解方式一样)

<bean id="tManager" class="org.springframework.jdbc.dataSource.DataSourceTransactionManager">
    <property name="dataSource" ref="ds"></property>
</bean>

② 配置通知(advice)

<!-- 配置通知 -->
<tx:advice id="txAdvice">
    <!-- 配置事务管理和事务参数 -->
    <tx:attributes>
        <!-- 在对应方法(可以多个)上开启事务,并设置事务的参数 -->
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<tx:method>标签中的name属性:属性值为指定规则的方法名,如update*就表示前面带有update的方法都开启事务

③ 配置切入点(pointcut)和切面(将通知应用到切入点上)

<aop:config>
    <!-- 配置切入点 -->
    <aop:pointcut id="pt" expression="execution(* com.Key.service.UserService.*(..))"/>

    <!-- 配置切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>

❓ 几个标签中属性说明

  1. <aop:pointcut>中的expression属性值为切入点表达式,指定要开启事务的方法和类
  2. <aop:advisor>中的advice-refpointcut-ref属性值分别为通知的唯一标识id和切入点的唯一标识id

2.3 完全注解开发⭐

① 创建配置类——TxConfig

② 在配置类上添加相关注解:@Configuration(设定为配置类)、@ComponentScan(basePackages = {"com.Key")(开启组件扫描)、@EnableTransactionManagement(开启事务)

③ 在配置类中创建各个类对象:添加每个类的getXxx()方法,返回对应的对象,并在方法上添加@Bean注解

💡 如果需要在某个类的getXxx()方法中使用其他类对象,只需把该对象作为参数传入即可。因为在配置类中创建了类对应的getXxx()方法,spring IOC容器中就会存在对应的Bean对象,所以在方法参数中添加需要使用的类对象,就能在方法中直接使用,而无需在方法中使用new创建(相当于使用@Autowired注解根据类型创建对象类型的属性)

@Configuration
@Component(basePackage = {"com.Key"})
@EnableTransactionManagement
public class TxConfig {

    // 创建数据库连接池对象
    @Bean
    public DruidDataSource getDruidDataSource() {
        // 先new 一个对象
        DruidDataSource ds = new DruidDataSource();

        // 引入外部属性文件
        Properties pro = new Properties();
        pro.load(TxConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"));

        // 注入外部属性文件中的对应属性值(也可以不采用引入的外部属性文件,直接注入对应属性值)
        ds.setDriverClassName(pro.getProperty("prop.driverClass"));
        ds.setUrl(pro.getProperty("prop.url"));
        ds.setUsername(pro.getProperty("prop.username"));
        ds.setPassword(pro.getProperty("prop.password"));

        // 最后返回注入好属性的对象
        return ds;
    }

    // 创建jdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DruidDataSource ds) {
        // 先new 一个对象
        JdbcTemplate jt = new JdbcTemplate();

        // 注入dataSource 属性
        jt.setDataSource(ds);

        return jt;
    }

    // 创建事务管理器对象
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource ds) {
        // 先new 一个对象
        DataSourceTransactionManager tManager = new DataSourceTransactionManager();

        // 注入dataSource 属性
        tManager.setDataSource(ds);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值