当前位置: 代码迷 >> 综合 >> Spring--事务管理
  详细解决方案

Spring--事务管理

热度:19   发布时间:2023-09-11 01:48:52.0

事务简单来说,就是将多个操作成为一个工作单元,其中任何一个操作执行失败,都会回到工作单元之前的状态

事务的特性也称为ACID,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),真想吐槽谁发明的这些概念

事务的并发问题

  • 脏读 : 一个事务A访问数据,另一个事务B进行了修改,A重新访问获得了修改后的数据。我们希望A获得原来的数据
  • 不可重复读 : 一个事务A访问数据,另一个事务B进行了修改并提交,A重新访问获得了修改后的数据。我们希望A获得原来的数据
  • 幻读 : 一个事务A访问数据,另一个事务B进行了插入数据并提交,A重新访问数据得到了增加后的数据。我们希望A获得原来的数据

事务的隔离级别可以解决并发问题
从低到高依次为READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ以及SERIALIZABLE,隔离级别越低,越能支持高并发的数据库操作。

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 不解决 不解决 不解决
READ COMMITTED 解决 不解决 不解决
REPEATABLE READ 解决 解决 不解决
SERIALIZABLE 解决 解决 解决

一般情况下,我们选择REPEATABLE READ,幻读不以解决

一、Spring项目配置

事务需要操作数据库,导入以下依赖:

<!--spring核心容器包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.5</version></dependency><!--spring切面包--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.5</version></dependency><!--aop联盟包--><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!--德鲁伊连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.22</version></dependency><!--springJDBC包--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.5</version></dependency><!--spring事务控制包--><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.3.5</version></dependency><!--spring orm 映射依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.5</version></dependency><!--Apache Commons日志包--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency>

准备数据库配置文件:

jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=root

Spring配置文件:

<?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:c="http://www.springframework.org/schema/c"xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
"><!--读取jdbc配置--><context:property-placeholder location="classpath:jdbc.properties"/><!--配置连接池--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc_username}"></property><property name="password" value="${jdbc_password}"></property><property name="url" value="${jdbc_url}"></property><property name="driverClassName" value="${jdbc_driver}"></property></bean><!--配置JDBCTemplate对象,并向里面注入DataSource--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!--通过set方法注入连接池--><property name="dataSource" ref="dataSource"></property></bean><!--多个包可以使用,分割--><context:component-scan base-package="com.aruba"/>
</beans>

准备数据库表:

create table user(
id int primary key auto_increment,
name varchar(10) not null,
money int not null default 0
);insert into user values(default,'zhangsan',2000);
insert into user values(default,'lisi',2000);

二、代码准备

创建Mapper接口和实现类:

public interface UserMapper {int updateMoney(int id, int money);
}
@Repository
public class UserMapperImpl implements UserMapper {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic int updateMoney(int id, int money) {String sql = "update user set money =money + ? where id =?";Object[] args = {money, id};return jdbcTemplate.update(sql, args);}
}

创建Service接口和实现类:

public interface UserService {int transMoney(int from, int to, int money);
}
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic int transMoney(int from, int to, int money) {userMapper.updateMoney(from,-money);userMapper.updateMoney(to,money);return 0;}
}

测试方法:

@org.junit.Testpublic void test() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");UserService userService = applicationContext.getBean(UserService.class);userService.transMoney(5, 6, 200);}

重新查询下数据:

Spring--事务管理

三、Spring事务管理

配置文件中新增事务配置:

<?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:c="http://www.springframework.org/schema/c"xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd
">
...<!--配置一个事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--将数据源注入事务管理器--><property name="dataSource"  ref="dataSource"></property></bean><!--开启事务注解--><tx:annotation-driven transaction-manager="transactionManager"/></beans>

Service层使用@Transactiona注解开启事务:

@Service
//@Transactional 所有方法开启事务
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Override@Transactional //该方法开启事务public int transMoney(int from, int to, int money) {userMapper.updateMoney(from, -money);int a = 1 / 0;//模拟一个异常userMapper.updateMoney(to, money);return 0;}
}

这边模拟一个异常

结果:

Spring--事务管理

数据库中数据没有变化:

Spring--事务管理

四、@Transactiona注解参数

propagation参数:
事务传播行为类型 描述
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。默认值
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
isolation参数:
事务隔离级别 描述
DEFAULT 使用数据库默认的事务隔离级别,MySQL默认REPEATABLE_READ,Oracle默认READ_COMMITTED
READ_UNCOMMITTED 产生脏读,不可重复读和幻像读
READ_COMMITTED 可以避免脏读出现,但是可能会出现不可重复读和幻读
REPEATABLE_READ 可以防止脏读、不可重复读,但是可能出现幻读
SERIALIZABLE 防止脏读、不可重复读外,还避免了幻像读
其他参数:
  • timeout : 指定事务在多长时间之内提交,如果不提交就会回滚
  • readOnly : 事务是否只能读取数据库的数据
  • rollbackFor : 当方法发生哪些异常时才会回滚
  • noRollbackFor : 当方法发生哪些异常时,不会回滚

项目地址:

https://gitee.com/aruba/spring-study.git

  相关解决方案