事务简单来说,就是将多个操作成为一个工作单元,其中任何一个操作执行失败,都会回到工作单元之前的状态
事务的特性也称为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事务管理
配置文件中新增事务配置:
<?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;}
}
这边模拟一个异常
结果:
数据库中数据没有变化:
四、@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