Spring6 - 事务管理

一、事务是什么?

事务是一组操作的集合,它是一个不可分割的工作单位,简单来说就是

这些操作它们要么同时成功,要么同时失败   

四大特性(ACID)

  1. 原子性(Atomicity):原子性是指事务是一个不可分割的操作单元,要么全部执行成功,要么全部失败回滚。事务中的所有操作要么都被执行,要么都不执行,不会出现部分执行的情况。如果事务在执行过程中发生错误,系统必须能够将数据库恢复到事务开始前的一致状态,即回滚(Rollback)。

  2. 一致性(Consistency):一致性是指事务执行前后,数据库的状态必须保持一致。事务对数据库所做的修改必须满足各种约束、限制和触发器等规定的条件,保证数据的完整性和有效性。任何违反完整性约束的事务操作都会被自动回滚。

  3. 隔离性(Isolation):隔离性是多个事务并发执行时互相之间不可见的特性。每个事务在执行过程中对其他事务的操作是隔离的,不受其他事务的干扰。隔离性可以防止并发执行时出现各种数据异常和并发冲突的问题。

  4. 持久性(Durability):持久性是指一旦事务提交后,其所做的修改将会永久保存在数据库中,即使发生系统错误或者系统崩溃,数据库仍能够恢复到提交事务后的状态。持久性通过将事务的操作日志记录到磁盘中来实现。

二、声明式事务

准备工作

前提需要学会使用jdbcTemplate

 JdbcTemplatehttps://blog.csdn.net/m0_56308072/article/details/131653055?spm=1001.2014.3001.5501

我们首先来创建如下三层架构

 

连接数据库

在xml文件中配置数据源,打开自动扫描

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.wal.jdbc.tx"/>

<!--    引入外部属性文件,创建数据源对象-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

<!--    创建jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

</beans>

现在来书写三层架构中的逻辑,这里仅展示实现类的代码,自己书写时要把接口也写上

Controller层

@Controller
public class BookController {

    @Autowired
    private BookService bookService;

    //买书
    private void buyBook(Integer bookId,Integer userId){
        bookService.buyBook(bookId,userId);
    }
}

Service层

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    @Override
    public void buyBook(Integer bookId, Integer userId) {
        //根据图书的id查询价格
        Integer price = bookDao.getBookPriceByBookId(bookId);

        //更新图书库存量
        bookDao.updateStock(bookId);

        //更新用户表用户余额 - 图书价格
        bookDao.updateUserBalance(userId,price);

    }
}

Dao层 

@Repository
public class BookDaoImpl implements BookDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 根据图书id查找价格
     * @param bookId
     * @return
     */
    @Override
    public Integer getBookPriceByBookId(Integer bookId) {
        String sql = "select price from t_book where book_id=?";
        Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
        return price;
    }

    /**
     * 更新库存
     * @param bookId
     */
    @Override
    public void updateStock(Integer bookId) {
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql,bookId);
    }

    /**
     * 更新余额
     * @param userId
     * @param price
     */
    @Override
    public void updateUserBalance(Integer userId, Integer price) {
        String sql = "update t_user set balance = balance - ? where user_id = ?";
        jdbcTemplate.update(sql,price,userId);
    }
}

现在模拟一下1号user 买了 1号book

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TxTest {
    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
        bookController.buyBook(1,1);
    }
}

正常的结果是,1号book库存减一,1号user余额-book价格

正常运行

 异常情况

假设我们现在余额不足,由于余额设置了无符号不能能为负数,当然会出现异常报错,但是我们此时运行查看结果

虽然出现报错,余额没有减少,但是我们查看书的库存

书的库存依旧减少了,这显然是不正常的,我们不希望发生这种情况,由此引出事务操作

基于注解的事务操作

添加事务配置

在Spring的配置文件中添加配置

<!--    创建事务管理的bean-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

<!--    开启事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

添加事务注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理

添加注解@Transactional

添加位置的影响

  • @Transactional标识在方法上,则只会影响该方法
  • @Transactional标识的类上,则会影响类中的所有的方法

我们直接在service层添加注解

测试发现正常

 注解内相关属性

<1>、只读:只能查询,不能增删改

@Transactional(readOnly = true)

<2>、超时:在设置超时时间内没有完成,抛出异常回滚

//超时时间单位秒,默认是-1,代表永不超时
@Transactional(timeout = -1)

<3>、回滚策略:设置哪些异常不回滚

@Transactional(noRollbackFor = 异常类.class)

<4>、隔离级别:解决读问题

  • 脏读(Dirty Read)是指一个事务在读取了另一个未提交事务的数据后,如果另一个事务回滚,那么读取到的数据就是被修改了的。换句话说,脏读允许事务读取到尚未被持久化的临时数据。
  • 不可重复读(Non-repeatable Read)是指一个事务内多次读取同一数据,但在这个事务还没结束时,其他事务对该数据进行了修改并提交,导致第一个事务两次读取到的数据不一致。
  • 幻读(Phantom Read)是指在一个事务中执行了查询操作,然后另一个事务在此期间插入了符合查询条件的新数据,当第一个事务再次执行相同的查询时,会发现有新增的数据出现,就像是出现了幻觉一样。

  1. 读未提交(Read Uncommitted):最低的隔离级别。一个事务可以读取另一个未提交事务的未提交的修改。这种级别可能会导致脏读、幻读和不可重复读的问题。

  2. 读已提交(Read Committed):在这个级别下,一个事务只能读取到已经提交的数据。事务在执行过程中可以看到其他已提交事务的更新,避免了脏读问题。然而,仍然可能出现幻读和不可重复读的问题,这个是Oracle的默认隔离级别。

  3. 可重复读(Repeatable Read):这个级别确保了在一个事务中多次读取同一数据时,将会得到一致的结果。在这个级别下,事务在启动时创建一个快照,并在整个事务执行期间使用该快照。这阻止了脏读和不可重复读问题,但仍然可能出现幻读,这个是MySql的默认隔离级别

  4. 串行化(Serializable):最高的隔离级别。在这个级别下,事务顺序执行,相当于将并发访问变成了串行访问。这种级别避免任何并发问题,但性能十分底下。

隔离级别越高,事务的并发性能就越低,因为需要锁定数据以避免并发问题。选取适当的隔离级别需要根据具体应用场景和需求来决定。

<5>、传播行为:事务方法之间的调用,事务如何使用

在service类中有方法A与方法B,其中都有各自的事务,当我们A方法执行中调用了B方法那么事物的传递就是事务传播行为

其中我们常用的就是RequiredRequired_NEW

演示事务传播行为

我们重新创建一个checkoutService类,加上事务方法调用bookService的事务方法

我们将用户的余额只够购买一本书的情况,测试结果

发现虽然报了错,但是stock库存量并没有改变

这是因为,我们开启了@Transactional之后的默认传播行为是REQUIRED,表示如果当前线程上有已经开启的事务可以用,那么就在这个事务中运行。

购买图书的方法buyBook方法在checkout方法中被调用,checkout上有事务注解,因此在此事务中执行,当余额只够购买一本书时,导致整个checkout回滚,

一本买不了,全部都买不了了

我们再使用REQUIRED_NEW查看结果

虽然第二本书买不了,但是第一本依旧买了而且也付了款

这是因为,@Transactional(propagation = Propagation.REQUIRES_NEW)表示不管当前线程上是否有已经开启的事务,都要开启新事务。

同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本购买失败只在第二次的buyBook中回滚,

购买第一本图书不受影响,即能买几本就买几本

基于全注解开发

我们来将之前的xml配置文件改为配置类

@Configuration
@ComponentScan("com.wal.jdbc.tx")
@EnableTransactionManagement
public class SpringConfig {
    @Bean
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("jdbc:mysql://localhost:3306/springTest");
        dataSource.setUrl("jdbc:mysql://localhost:3306/springTest");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");

        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDateSourceTransaction(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager =
                new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

对比一下之前的xml文件,看看替换了什么

<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.wal.jdbc.tx"/>

<!--    引入外部属性文件,创建数据源对象-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

<!--    创建jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

<!--    创建事务管理的bean-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

<!--    开启事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
功能标签注解
开启扫描<context:component-scan base-package="com.wal.jdbc.tx"/>
@ComponentScan("com.wal.jdbc.tx")
开启事务
<tx:annotation-driven transaction-manager="transactionManager"/>
@EnableTransactionManagement
创建数据源对象
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
@Bean
public DataSource getDataSource()
创建JdbcTemplate
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource)
创建事务管理的bean
    
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
@Bean
public DataSourceTransactionManager getDateSourceTransaction(DataSource dataSource)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

An1ong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值