事务简单来说,就是将多个操作成为一个工作单元,其中任何一个操作执行失败,都会回到工作单元之前的状态
事务的特性也称为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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://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 {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public 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 {
@Autowired
private UserMapper userMapper;
@Override
public int transMoney(int from, int to, int money) {
userMapper.updateMoney(from,-money);
userMapper.updateMoney(to,money);
return 0;
}
}
测试方法:
@org.junit.Test
public 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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://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 {
@Autowired
private 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 : 当方法发生哪些异常时,不会回滚
网友评论