Spring学习(五):事务管理

概述

什么是事务

事务是数据库操作最基本单元。逻辑上一组操作,要么都成功,如果有一个失败所有操
作都失败。

事务的四个特性

  • 原子性 Atomicity
  • 一致性 Consistency
  • 隔离性 Isolation
  • 持久性 Durability

搭建环境

模拟转账场景:Lucy给Mary转账,Lucy少钱,Mary多钱。

配置步骤

  • 引入相关jar包
    druid-1.1.9.jar
    mysql-connector-java-5.1.7-bin.jar
    spring-jdbc-5.2.6.RELEASE.jar
    spring-orm-5.2.6.RELEASE.jar
    spring-tx-5.2.6.RELEASE.jar
    引入后所有包:
    com.springsource.net.sf.cglib-2.2.0.jar
    com.springsource.org.aopalliance-1.0.0.jar
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    commons-logging-1.1.1.jar
    druid-1.1.9.jar
    mysql-connector-java-5.1.7-bin.jar
    spring-aop-5.2.6.RELEASE.jar
    spring-aspects-5.2.6.RELEASE.jar
    spring-beans-5.2.6.RELEASE.jar
    spring-context-5.2.6.RELEASE.jar
    spring-core-5.2.6.RELEASE.jar
    spring-expression-5.2.6.RELEASE.jar
    spring-jdbc-5.2.6.RELEASE.jar
    spring-orm-5.2.6.RELEASE.jar
    spring-tx-5.2.6.RELEASE.jar

  • 创建数据库和建表
    数据库user_db、表t_account
    插入数据后表结果:

    id      username   money  
    ------  --------  --------
    1       Lucy          1000
    2       Mary          1000
    
  • spring的配置文件注入连接池和jdbcTemplate

  • 创建 service和dao并完成对象的创建和注入关系

  • 在dao中创建多钱和少钱的方法,在service中创建转账的方法

示例代码

代码结构:

└─src
    │  bean.xml
    │
    └─com
        └─spring5
            │  Test.java
            │
            ├─dao
            │      UserDao.java
            │      UserDaoImpl.java
            │
            └─service
                    UserService.java

bean.xml:

<!--组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
     destroy-method="close">
   <property name="url" value="jdbc:mysql:///user_db?useUnicode=true&amp;characterEncoding=utf8" />
   <property name="username" value="root" />
   <property name="password" value="root" />
   <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
   <!--set方式注入dataSource-->
   <property name="dataSource" ref="dataSource"></property>
</bean>

UserDao接口:

public interface UserDao {

    void addMoney();
    void reduceMoney();
}

UserDaoImpl类:

@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addMoney() {
        String sql =  "update t_account set money=money+? where username=?";
        jdbcTemplate.update(sql,100, "Mary");
    }

    @Override
    public void reduceMoney() {
        String sql =  "update t_account set money=money-? where username=?";
        jdbcTemplate.update(sql,100, "Lucy");
    }
}

UserService类:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void accountMoney(){
        // 两个方法是为了更好的看清楚事务案例
        userDao.reduceMoney();
        userDao.addMoney();
    }
}

Test类:

public class Test {

    @org.junit.Test
    public void testAccount(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.accountMoney();
    }
}

运行后程序没有异常,数据库效果:

id      username   money  
------  --------  --------
1       Lucy           900
2       Mary          1100

场景引入

上面的例子若运行有异常,如下所示:

    public void accountMoney(){
        // 两个方法是为了更好的看清楚事务案例
        userDao.reduceMoney();
        int a = 100/0;
        userDao.addMoney();
    }

数据库效果:

id      username   money  
------  --------  --------
1       Lucy           800
2       Mary          1100

Lucy少钱,而Mary没多钱。这时候就需要用到事务。

Sping事务管理介绍

  • 一般把事务添加到service层
  • 事务管理方式
    编程式事务管理 :1、开启事务 2、执行业务逻辑 3、若业务逻辑没有异常提交事务,若有异常事务回滚。
    声明式事务管理:1、基于注解实现 2、基于XML实现
  • Spring的声明式事务底层用的是AOP原理

声明式事务管理

注解实现

配置步骤
  • 在spring配置文件中配置事务管理器

  • 在spring配置文件中开启事务注解
    需要引入tx空间

    xmlns:tx="http://www.springframework.org/schema/tx"
    http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
    
  • 使用@Transactional注解开启事务。
    如果把这个注解添加类上面,这个类里面所有的方法都添加事务,如果把这个注解添加方法上面,为这个方法添加事务。

Spring事务的传播行为
事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

前两种比较常用,需要掌握。
在这里插入图片描述

参考链接1
参考链接2

spring事务的隔离级别
问题

事务有隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题,比如脏读、不可重复读和幻读。

  • 脏读:一个未提交事务读取到另一个未提交事务的数据。
    比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
  • 不可重复读:一个未提交事务读取到另一提交事务修改数据。一个事务对同一行数据重复读取两次,但是却得到了不同的结果。
    比如银行取钱,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
  • 幻读:一个未提交事务读取到另一提交事务添加或删除数据。
    比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
    在这里插入图片描述
解决方法
隔离级别/是否解决问题脏读不可重复读幻读
READ_UNCOMMITTED(读未提交)
READ_COMMITED(读已提交)
REPEATABLE_READ(可重复读)
SERLALIZABLE(串行化)

Spring隔离级别DEFAULT将使用底层数据库的默认事务隔离级别。MySQL默认隔离级别是REPEATABLE_READ,代码如下:

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {
}
Spring事务的其他参数
  • timeout :超时时间
    事务需要在一定时间内进行提交,如果不提交进行回滚;默认值是 -1表示不失效,设置时间以秒单位进行计算
  • readOnly :是否只读
    readOnly 默认值 false,可以进行增删查改操作;设置为true后只能进行查询操作
  • rollbackFor :回滚
    设置出现哪些异常进行事务回滚
  • noRollbackFor :不回滚
    设置出现哪些异常不进行事务回滚

示例代码如下:

@Transactional(timeout = 100,readOnly = false,rollbackFor = {ArrayIndexOutOfBoundsException.class,RuntimeException.class})

XML实现

步骤
  • 配置事务管理器
  • 配置通知
  • 配置切入点和切面
相关代码

java代码中去掉@Transactional注解,其他不变。spring配置文件代码如下:

<!--组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
     destroy-method="close">
   <property name="url" value="jdbc:mysql:///user_db?useUnicode=true&amp;characterEncoding=utf8" />
   <property name="username" value="root" />
   <property name="password" value="root" />
   <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
   <!--set方式注入dataSource-->
   <property name="dataSource" ref="dataSource"></property>
</bean>

<!--1 创建事务管理器-->
<bean  id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <!--注入数据源-->
   <property  name= "dataSource"  ref= "dataSource"></property>
</bean>

<!--2 配置通知-->
<tx:advice  id= "txadvice">
   <!--配置事务参数-->
   <tx:attributes>
       <!--指定哪种规则的方法上面添加事务 *表示匹配所有-->
       <!--<tx:method name="account*"/>-->
       <tx:method  name= "accountMoney"  propagation= "REQUIRED"/>
   </tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
   <!--配置切入点-->
   <aop:pointcut  id= "pt"  expression= "execution(* com.spring5.service.UserService.*(..))"/>
   <!--配置切面-->
   <aop:advisor  advice-ref= "txadvice"  pointcut-ref= "pt"/>
</aop:config>

点击获取Spring相关文章的MarkDown源文件(一共6个)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值