1. 业务是什么?为什么需求业务?
业务是一种用于办理数据库操作的机制。它将一组操作封装成一个单元,确保数据库操作要么悉数成功提交,要么悉数回滚,以保持数据的一起性和完好性。
为什么要运用业务呢?请看下面的事例。
假设有两个用户的银行账户,账户A和账户B,它们别离存储着必定的金额。现在,用户A想要向用户B转账100元。这个转账操作需求以下两个过程:
- 从用户A的账户中扣除100元。
- 将扣除的100元增加到用户B的账户中。
在这个过程中,咱们需求确保两个过程要么一起成功提交,要么一起回滚。假如第一步履行成功了,第二步却履行失利了,那么B没有收到这100块钱,A的钱就不翼而飞了。所以假如其间一个过程呈现问题,咱们有必要回滚整个业务,以保持数据的一起性。
2. Spring 中业务的完成
2.1 编程式业务
Spring Boot
中内置了两个目标,即:DataSourceTransactionManager
与 TransactionDefinition
,用这两个目标就能够来操作业务了。
这儿现已配置了相应的数据库环境
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//DataSourceTransactionManager: 数据源业务办理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//TransactionDefinition:业务界说
@Autowired
private TransactionDefinition transactionDefinition;
//根据 id 删去数据
@RequestMapping("/delete")
public Integer delete(Integer id){
//敞开业务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
//对数据库操作:删去
Integer result = userService.delete(1);
//提交业务 or 回滚业务
//回滚业务
dataSourceTransactionManager.rollback(transactionStatus);
//提交业务
//dataSourceTransactionManager.commit(transactionStatus);
return result;
}
- 运用
DataSourceTransactionManager
的getTransaction
办法开端一个业务。 - 在
getTransaction
办法中传递一个TransactionDefinition
目标来界说业务的特点。 -
getTransaction
办法回来一个TransactionStatus
目标,表明当时业务的状况。 - 在业务履行过程中,能够经过
TransactionStatus
目标来查看业务的状况。 - 终究,经过调用
dataSourceTransactionManager
的commit
或rollback
办法提交或回滚业务。
下面是更为完好、规范的代码:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/delete")
public Integer delete(Integer id){
if(id == null || id <= 0){
return 0;
}
TransactionStatus transactionStatus = null;
int result = 0;
try{
//敞开业务
transactionStatus = transactionManager.getTransaction(transactionDefinition);
//业务操作,删去业务
result = userService.delete(id);
System.out.println("删去:" + result);
//提交业务
transactionManager.commit(transactionStatus);
}catch (Exception e){
//回滚业务
if(transactionStatus != null){
transactionManager.rollback(transactionStatus);
}
}
return result;
}
}
可是这种办法太繁琐了,还有更为简略的办法。
2.2 声明式业务(注解)
运用 @Transactional
注解:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/delete2")
@Transactional
public Integer delete2(Integer id){
if(id == null || id <= 0){
return 0;
}
int result = userService.delete(id);
System.out.println("删去:" + result);
return result;
}
}
无需手动敞开业务和提交业务,进入办法时主动敞开业务,办法履行完会主动提交业务,假如半途产生了没有处理的反常会主动回滚业务。
待删去数据的表,这儿删去“张三”:
进行拜访后:
阐明业务提交成功。
2.2.1 产生反常的时候
(1)没有处理的反常会主动回滚业务
下面的代码会抛出反常,这时候再看看业务是否会回滚。
@RequestMapping("/delete2")
@Transactional
public Integer delete2(Integer id){
if(id == null || id <= 0){
return 0;
}
int result = userService.delete(id);
int x = 8 / 0; //会抛出 ArithmeticException 反常
System.out.println("删去:" + result);
return result;
}
半途产生了没有处理的反常会主动回滚业务。
(2)处理后的反常不会主动回滚业务
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/delete2")
@Transactional
public Integer delete2(Integer id){
if(id == null || id <= 0){
return 0;
}
int result = 0;
try {
//删去数据
result = userService.delete(id);
System.out.println("删去:" + result);
int x = 8 / 0;
}catch (Exception e){
System.out.println(e.getMessage());
}
return result;
}
}
拜访前的表:
删去 id=4
:
拜访后的表:
能够看到处理了反常后,业务没有回滚,这样的操作十分的危险,可是也有处理的办法,那便是手动回滚业务:
@RequestMapping("/delete2")
@Transactional
public Integer delete2(Integer id){
if(id == null || id <= 0){
return 0;
}
int result = 0;
try {
result = userService.delete(id);
System.out.println("删去:" + result);
int x = 8 / 0;
}catch (Exception e){
System.out.println(e.getMessage());
//手动回滚业务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
这样它就会回滚业务了。
2.2.2 @Transactional 效果规模
@Transactional
能够加在办法上以及类上,可是:
- 当运用
@Transactional
注解润饰办法时,它只对public
的办法生效。 - 当运用
@Transactional
注解润饰类时,表明对该类中一切的public
办法生效。
这是因为基于署理的业务办理机制在运行时创立署理目标,而且署理目标只能拜访public
办法。当@Transactional
注解应用于非public
办法(如protected
、private
或默许包可见性的办法)时,署理目标无法拜访这些办法,导致业务办理无法生效。
2.2.3 @Transactional 参数阐明
下面是补充的 @Transactional 注解的参数及其效果的汇总表格:
参数 | 描述 |
---|---|
value |
指定业务办理器的称号。 |
propagation |
指定业务的传达行为。 |
isolation |
指定业务的阻隔等级。 |
readOnly |
指定业务是否为只读。 |
timeout |
指定业务的超时时刻(以秒为单位)。 |
rollbackFor |
指定哪些反常触发业务回滚。 |
rollbackForClassName |
指定哪些反常类名触发业务回滚。 |
noRollbackFor |
指定哪些反常不触发业务回滚。 |
noRollbackForClassName |
指定哪些反常类名不触发业务回滚。 |
- propagation:指定业务的传达行为(后文详细介绍)。
- isolation:指定业务的阻隔等级,界说了业务之间的可见性和并发操控(后文详细介绍)。
- readOnly:指定业务是否为只读,假如设置为 true,则表明该业务只读取数据,不修正数据。
- timeout:指定业务的超时时刻,单位为秒。假如业务履行时刻超越指定的超时时刻,则业务会被强制回滚。
- rollbackFor:指定哪些反常触发业务回滚。能够指定一个或多个反常类型的数组。
- rollbackForClassName:指定哪些反常类名触发业务回滚。能够指定一个或多个反常类名的字符串数组。
- noRollbackFor:指定哪些反常不触发业务回滚。能够指定一个或多个反常类型的数组。
- noRollbackForClassName:指定哪些反常类名不触发业务回滚。能够指定一个或多个反常类名的字符串数组。
2.2.3 @Transactional 作业原理
Spring 经过署理模式完成 @Transactional
的作业原理。当一个带有 @Transactional
注解的办法被调用时,Spring
将创立一个署理目标来办理业务。Spring
运用 AOP(面向切面编程)将业务办理逻辑织入到带有 @Transactional
注解的办法周围。这样,在办法履行前后,会刺进业务办理相关的代码。
下面是办法调用的详细过程:
- 调用者经过署理目标调用被署理的办法。
- 署理目标接收到办法调用恳求。
- 署理目标在办法调用前履行预界说的逻辑,例如业务办理的开端。
- 署理目标将实践的办法调用委托给原目标。这意味着署理目标将真实的办法调用传递给原目标,使原目标履行实践的业务逻辑。
- 原目标履行办法的实践逻辑。
- 原目标回来办法的成果给署理目标。
- 署理目标在办法调用后履行额外的逻辑,例如业务办理的提交或回滚。
- 署理目标将办法的成果回来给调用者。
3. 业务的阻隔等级
3.1 业务特性
业务具有以下四个重要的特性,一般被称为 ACID 特性:
- 原子性(Atomicity):原子性要求业务被视为不可分割的最小作业单元,要么悉数履行成功,要么悉数失利回滚。业务在履行过程中产生错误或中止,系统有必要能够将其康复到业务开端前的状况,确保数据的一起性。
- 一起性(Consistency):一起性确保业务在履行前后数据库的状况是一起的。业务在履行过程中对数据库进行的修正有必要满意预界说的规则和约束,以确保数据的完好性。
- 阻隔性(Isolation):阻隔性指多个业务并发履行时,每个业务的操作都应当与其他业务相互阻隔,使它们感觉不到其他业务的存在。阻隔性能够避免并发履行的业务之间产生干扰和数据抵触,确保数据的正确性。
- 持久性(Durability):持久性要求业务一旦提交,其对数据库的修正便是永久性的,即便在系统产生故障或重启的情况下,修正的数据也能够被康复。持久性经过将业务的成果写入非易失性存储介质(如磁盘)来完成。
3.2 业务的阻隔等级
对于阻隔性,一般有以下四个规范的阻隔等级:
-
Read Uncommitted(读取未提交数据)
:最低的阻隔等级。在该等级下,一个业务能够读取到另一个业务未提交的数据,或许导致脏读,即读取到了未经验证的数据。这个等级会导致数据的不一起性,而且不供给任何并发操控。 -
Read Committed(读取已提交数据)
:在该等级下,一个业务只能读取到现已提交的数据。它避免了脏读,但或许呈现不可重复读(Non-repeatable Read)的问题。不可重复读是指同一个业务中屡次读取同一数据,在业务履行过程中,该数据被其他业务修正,导致每次读取到的值不一起。 -
Repeatable Read(可重复读)
:在该等级下,一个业务在履行期间屡次读取同一数据时,确保能够读取到一起的成果。即便其他业务对该数据进行修正,也不会影响当时业务的读取操作。这个等级经过确定读取的数据,避免了不可重复读,但或许呈现幻读(Phantom Read)的问题。幻读是指同一个业务中屡次查询同一个规模的数据时,因为其他业务刺进了新的数据,导致每次查询成果集不一起。 -
Serializable(可串行化)
:最高的阻隔等级,它要求业务串行履行,彻底避免了并发问题。在该等级下,业务之间相互看不到对方的操作,能够避免脏读、不可重复读和幻读等问题。然而,因为串行化履行,会牺牲必定的并发性能。
3.3 Spring 中设置阻隔等级
在Spring
中,能够运用@Transactional
注解设置业务的阻隔等级。Spring
供给了与数据库业务阻隔等级对应的五个常量:
-
DEFAULT
:运用数据库的默许阻隔等级。 -
READ_UNCOMMITTED
:对应数据库的读取未提交数据(Read Uncommitted)阻隔等级。 -
READ_COMMITTED
:对应数据库的读取已提交数据(Read Committed)阻隔等级。 -
REPEATABLE_READ
:对应数据库的可重复读(Repeatable Read)阻隔等级。 -
SERIALIZABLE
:对应数据库的可串行化(Serializable)阻隔等级。
运用@Transactional
注解时,能够经过isolation
特点指定业务的阻隔等级。例如:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void myMethod() {
// 业务处理逻辑
}
4. Spring 业务传达机制
4.1 什么是业务的传达机制?
业务传达机制是指定业务在办法调用之间如何传达和影响的机制,经过界说业务的传达行为,操控业务在不同办法之间的创立、挂起、康复和回滚操作。
下面是常见的业务传达行为:
- REQUIRED(默许):假如当时存在业务,则参加到当时业务中,假如没有业务,则创立一个新的业务。
- SUPPORTS:假如当时存在业务,则参加到当时业务中,假如没有业务,则以非业务的办法履行。
- MANDATORY:有必要在一个已存在的业务中履行,不然抛出反常。
- REQUIRES_NEW:每次都会创立一个新的业务,假如当时存在业务,则将当时业务挂起。
- NOT_SUPPORTED:以非业务的办法履行操作,假如当时存在业务,则将当时业务挂起。
- NEVER:有必要以非业务办法履行,假如当时存在业务,则抛出反常。
- NESTED:假如当时存在业务,则在嵌套业务内履行,假如没有业务,则创立一个新的业务。
”当时存在业务”指的是在办法调用期间现已敞开的业务。在Spring中,业务是基于线程的,每个线程都有一个业务上下文。假如在办法调用期间现已存在一个业务上下文(即现已敞开了一个业务),则能够说”当时存在业务”。
当一个办法被调用时,Spring会查看当时线程是否现已有一个业务上下文存在。假如有,那么这个办法就能够在这个已存在的业务上下文中履行,即在当时业务中履行。办法能够拜访和操作当时业务中的数据,并共享该业务的一起性和阻隔等级(取决于办法的业务传达行为设置)。
假如当时线程没有业务上下文存在,那么办法能够选择创立一个新的业务,或许以非业务办法履行。这取决于办法的业务传达行为设置。新的业务上下文会在办法开端时创立,并在办法履行结束后进行提交或回滚。
例如,一个办法A内部调用了另一个办法B,假如办法B具有REQUIRED(默许)的业务传达行为,而办法A现已在一个业务中履行,那么办法B将参加到办法A的业务中,一起参与业务的操作。
4.2 业务传达机制的演示
本篇只演示一部分。
4.2.1 准备作业
在演示之前,这儿先创立两张表,以便利咱们看出它们的效果。
刺进的数据:
log 表为空:
界说3个类:
@RestController
@RequestMapping("/user3")
public class UserController3 {
@Autowired
private UserService userService;
// REQUIRED 类型
@RequestMapping("/add")
@Transactional(propagation = Propagation.REQUIRED)
public int add(String username,String password){
if(username == null || password == null || username.equals("") || password.equals("")){
return 0;
}
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password);
int result = userService.add(userInfo);
return result;
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private LogService logService;
public Integer delete(int id){
return userMapper.delete(id);
}
// REQUIRED 类型
//增加用户
@Transactional(propagation = Propagation.REQUIRED)
public Integer add(UserInfo userInfo){
//给用户表增加用户信息
int addUserResult = userMapper.add(userInfo);
System.out.println("增加用户成果:" + addUserResult);
Log log = new Log();
log.setMessage("增加日志信息");
logService.add(log);
return 0;
}
}
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
//增加日志信息
// REQUIRED 类型
@Transactional(propagation = Propagation.REQUIRED)
public Integer add(Log log){
int result = logMapper.add(log);
System.out.println("增加日志的成果:" + result);
//回滚业务,仿照产生反常,这儿为什么不写一个反常呢?因为反常会传递到外面的办法。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return result;
}
}
4.2.2 REQUIRED 演示
调用联系:
传入以下的值:
能够看到,两个表都是增加成功的,可是LogService
中的add
办法回滚了,重点看其它办法回滚了没有:
这两个表没有变化,阐明一切的办法都是回滚了的。这就体现了REQUIRED
这个传达行为,一个办法回滚了,其它一切办法都回滚。
更详细的:LogService
中的add
办法自身有业务,UserService
中的add
办法也是REQUIRED
。这时候,LogService
中的add
就参加了UserService
中的业务,相当于一个全体。
4.2.3 REQUIRES_NEW 演示
将办法都改为REQUIRES_NEW
,办法调用跟上面相同。
@RequestMapping("/add")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(String username,String password){
if(username == null || password == null || username.equals("") || password.equals("")){
return 0;
}
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password);
int result = userService.add(userInfo);
return result;
}
//增加用户
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer add(UserInfo userInfo){
//给用户表增加用户信息
int addUserResult = userMapper.add(userInfo);
System.out.println("增加用户成果:" + addUserResult);
Log log = new Log();
log.setMessage("增加日志信息");
logService.add(log);
return 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer add(Log log){
int result = logMapper.add(log);
System.out.println("增加日志的成果:" + result);
//回滚业务,仿照产生反常,这儿为什么不写一个反常呢?因为反常会传递到外面的办法。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return result;
}
发送恳求:
成果:
UserService
中add
办法的业务没有回滚,LogService
中的业务回滚了,回忆REQUIRES_NEW
:每次都会创立一个新的业务,假如当时存在业务,则将当时业务挂起。在这儿,LogService
中的业务先履行,履行完后再履行UserService
中的业务。
4.2.4 NESTED(嵌套业务)演示
同样的,把@Transactional
改为NESTED
。
恳求:
数据库中的表:
LogService
中的业务现已回滚,可是嵌套业务不会回滚嵌套之前的业务,也便是说嵌套业务能够完成部分业务回滚,可是这与上面的REQUIRES_NEW
是相同的效果呀,它们有什么差异呢?
4.2.5 NESTED(嵌套业务)与 REQUIRES_NEW
的差异
-
NESTED
(嵌套业务):在嵌套业务中,内部业务实践上是由外部业务敞开和提交/回滚的。 当外部业务回滚时,会导致内部业务也被回滚,即便内部业务现已履行了一些提交操作。 这是因为嵌套业务的模仿经过保存和康复业务状况来完成,当外部业务回滚时,它会回滚到敞开内部业务的那个点,包括内部业务履行的任何修正或提交。这样能够确保业务的一起性。
-
REQUIRES_NEW
:
REQUIRES_NEW
表明创立一个独立的业务。当一个业务(外部业务)调用另一个带有REQUIRES_NEW
传达行为的业务时,内部业务将在一个新的业务中履行,独立于外部业务。内部业务的提交或回滚不会影响外部业务。不管外部业务是否回滚,内部业务都能够独立提交或回滚。
4.3 嵌套业务和参加业务的差异
- 嵌套业务(Nested Transactions): 嵌套业务是指在一个业务内部敞开了另一个独立的业务。 嵌套业务能够在父业务的规模内履行,而且具有独立的业务日志和回滚机制。 嵌套业务允许在父业务中进行更细粒度的操作和操控,例如,在一个长业务中的某个过程中敞开了一个子业务,子业务能够独立提交或回滚,而不会影响父业务的其他过程。嵌套业务一般用于复杂的业务逻辑,能够供给更灵活的业务处理。
- 参加业务(Join Transactions): 参加业务是指将一个独立的业务合并到当时业务中,使它们成为一个全体。 参加业务能够将多个业务合并为一个更大的业务,确保它们作为一个原子操作进行提交或回滚。参加业务一般用于多个独立业务之间存在逻辑上的依靠联系,需求以一起的办法进行处理。经过将多个业务参加到一个业务中,能够确保它们的一起性,而且要么悉数提交成功,要么悉数回滚。