目录
事务操作的四大特征 ACID:
原子性 atommicity :表示多个操作要么同时成功,要么同时失败。因为时多个操作,所以失败的时候可能已经成功了默写操作,但是这些操作最终不能执行成功,也就是需要回滚。
一致性 consistency :相同的操作返回的相同的结果。在单个数据库中一般不考虑数据的一致性,因为数据库是由一个,相同的 sql 语句查询的结果肯定相同。但分布式存储系统中,数据时存储的不同的服务器中,那再这种情况下如何保证数据的一致性呢?
隔离性 isolation :这是四个特性中较好理解的一个,在一个数据库中,要是有多个用户并发方法相同的数据,那数据库会为每个用户开启一个事务,这个时候,每个事物之间不能相互影响,也就是隔离性。
持久性 durability :表示事务一旦被提交,就不会改变,即使是服务器或数据库系统遇到故障也不会丢失数据。实际中的数据库中是很多数据的,像阿里巴巴,腾讯这些公司,数据库的数据更是多得数不清,这些数据当然不会轻易丢失,所以持久性还是比较容易理解的。
案例:模拟银行银行的转账功能,这个功能分为减钱和加钱两个部分,现在转账账户上执行减钱操作,再从接收账户上执行加钱账户。这明显是个原子操作,减钱之后没加,那钱去哪里了呢?
在 pom.xml 中添加依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
因为我使用的是 maven 工程,所以使用依赖,如果大家使用的是 Java EE 工程,可以把依赖下载好,然后添加到依赖中。
编写实体类 Account
/**
* @Author XY
* @Date 2022/6/11 10:56
*/
public class Account {
private Integer id;
private String username;
private Integer balance;
}
这个类在本程序中并没有用到,大家可以参考这个类来创建数据表。
编写操作类 AccountDao
import org.apache.ibatis.annotations.Param;
/**
* @Author XY
* @Date 2022/6/11 10:57
*/
public interface AccountMapper {
/**
* 减钱操作
* @param from 减钱的账户,@Param("username")表示在编写 sql 语句的时候变量名是 username
* @param money 转账金额
* @return 该操作导致数据表的受影响行数
*/
Integer updateMinusBalance(@Param("username") String from, @Param("money") Integer money);
/**
* 加钱操作
* @param to 加钱操作
* @param money 转账金额
* @return 该操作导致数据表的受影响行数
*/
Integer updateAddBalance(@Param("username") String to, @Param("money") Integer money);
}
编写服务类 AccountService
import com.qfedu.ssm.dao.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
/**
* @Author XY
* @Date 2022/6/11 14:06
* <p>
* 下面这两个注解就可以使转账异常使回滚
* @EnableTransactionManagement,添加这个注解的时候可能会出现 No qualifying bean of type 'org.springframework.transaction.TransactionManager' available 这个异常,也就是找不到合适TransactionManager,这个时候向 spring 容器注册一个就行
* @Transactional
*/
@Service
@EnableTransactionManagement
public class AccountService {
@Autowired
AccountMapper accountMapper;
@Transactional
public void transfer(String from, String to, Integer money) {
Integer r1 = accountMapper.updateMinusBalance(from, money);
System.out.println("r1>>>>" + r1);
//手动制造异常,模拟出错时的转账失败
// Integer i = 1 / 0;
Integer r2 = accountMapper.updateAddBalance(to, money);
System.out.println("r2>>>>>" + r2);
}
}
编写配置文件 spring-context.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.qfedu.ssm"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test01?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations">
<value>classpath*:mapper/*.xml</value>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qfedu.ssm.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
</bean>
</beans>
编写测试类 AccountServiceTest
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author XY
* @Date 2022/6/11 14:08
*/
public class AccountServiceTest {
@Test
public void test01() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
AccountService accountService = ctx.getBean(AccountService.class);
accountService.transfer("zhangsan", "lisi", 50);
}
}
总结:当没有异常时,转账操作正常运行,当出现异常时,已经成功的操作会执行回滚,也就是相当于撤销。
这里在告诉大家一个快捷键,ctrl + shift + t 可以快速创建一个测试类,默认的类名就是当前类名加上 Test ,我这里是 maven 创建的测试类在测试包下,如果你们的是 Java EE 工程,创建的测试类就和被测试类在同一包下。
Testing library :这里先择JUnit4
Class name : 这里是测试类的名称
Destination package :这个测试类的包名(不建议修改)
setUp 和 tearDown :这两个分别是测试类注解 @Before 和 @After 对应的方法
Generate test methods for :这里是测试的方法,勾上的话就会自动创建好
编写好测试类后,在被测试类中可以直接使用 ctrl + shift + t 快捷键调转到测试类中,也可以再创建一个测试类。