1.事务简介
2.数据库中使用事务
需求:账号A向账号B转账,账户A更新余额后账户B也同时更新余额
初始数据:
操作语句:
update account set balance=1000 where account_number='A';
update account set balance=3000 where account_number='B';
操作结果:
问题:当在执行B失败时,数据库数据异常,如下:
问题:A更新数据,B数据却未更新。
解决方案:事务引入,将这两个操作作为一个事务,两个事务必须同时成功才能写入数据库。
#开启事物
start transaction ;
update account set balance=1000 where account_number='A';
update ;#抛出异常
update account set balance=3000 where account_number='B';
#提交事物,如果执行成功则同时写入数据库
commit ;
#回滚事物,如果执行失败则返回到数据库执行开始状态
ROLLBACK ;
执行结果:
3.事物并发问题
4.springboot项目中使用声明式事务
在Spring框架中,声明式事务管理是一种更简洁和声明性的方法来管理事务,通常是通过注解来实现的。声明式事务将事务管理的逻辑与业务逻辑分开,使开发者无需关心事务的底层细节。在Spring Boot中,声明式事务管理主要通过@Transactional注解来实现。
下面是如何在Spring Boot中使用声明式事务的步骤:
1.启用事务管理
确保在启动类或配置类上添加了@EnableTransactionManagement注解,以启用Spring的事务管理功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2.使用@Transactional注解
在需要事务管理的方法或类上使用@Transactional注解。这个注解告诉Spring该方法需要在事务的上下文中执行。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
// ... 依赖注入和数据访问层代码
@Transactional
public void performTransactionalTask() {
// 在这里执行你的业务逻辑
// 如果抛出运行时异常,事务会自动回滚
}
}
@Transactional注解可以应用于方法或类级别。当应用于类级别时,它会影响类中的所有公共方法。
3.配置事务属性
@Transactional注解支持多种属性,用于定义事务的传播行为、隔离级别、超时和只读属性。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 30, readOnly = false)
public void performTransactionalTask() {
// ...
}
- propagation:定义事务的传播行为,例如Propagation.REQUIRED表示当前方法必须在一个事务中运行,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- isolation:定义事务的隔离级别,如Isolation.DEFAULT使用数据库默认的事务隔离级别。
- timeout:定义事务的超时时间,单位是秒。
- readOnly:指定事务是否只读。如果设置为true,则事务只读取数据而不修改数据。
4.异常处理
默认情况下,如果在事务方法内部抛出了运行时异常(RuntimeException),Spring会触发事务回滚。对于已检查的异常(checked exceptions),Spring不会触发回滚,除非明确指定了@Transactional注解的rollbackFor属性。
@Transactional(rollbackFor = Exception.class)
public void performTransactionalTask() throws Exception {
// 如果抛出Exception或其子类,事务会回滚
}
5.事务的传播行为(Propagation Behavior)
定义了当事务方法被另一个事务方法调用时,应如何使用事务。这主要涉及到如何处理嵌套事务的情况。Spring框架提供了多种传播行为选项,这些选项可以通过@Transactional注解的propagation属性来设置。以下是Spring框架中定义的事务传播行为:
- PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的选择,通常作为默认的事务传播行为。
- PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。这意味着方法可以在事务中运行,也可以不在事务中运行,取决于调用它的代码是否处于事务中。
- PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这要求调用方法必须在一个事务中运行。
- PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果当前存在事务,则将其挂起。这意味着无论调用方法是否处于事务中,被调用方法都将在一个新的事务中运行。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。这意味着方法将不会在一个事务中运行,即使调用它的代码处于事务中。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。这要求调用方法不能在一个事务中运行。
- PROPAGATION_NESTED:如果当前存在事务,则嵌套事务作为当前事务的一个子事务运行;如果当前没有事务,则执行PROPAGATION_REQUIRED行为。这意味着如果在一个事务中调用该方法,那么该方法将在嵌套事务中运行,嵌套事务可以独立回滚而不影响外部事务。
6.事务的回滚
7.实例:
描述: 将用户预约id为1的车位预约信息存入数据库并更新该车位状态,在预约信息存入数据库后抛出错误,观察事务是否成功。
数据库初始状态:
关键代码
/**
* 预约处理
* @param model
* @param request
* @return
*/
@PostMapping("/car/orderDetail")
@Transactional(propagation = Propagation.REQUIRED)
public String orderDetail(Model model,HttpServletRequest request){
String username = request.getParameter("username");
String tel = request.getParameter("tel");
String parkId = request.getParameter("id");//车位编号
String location = request.getParameter("location");
String date = request.getParameter("date");
String time = request.getParameter("time");
String totalprice = request.getParameter("totalprice");
String type = request.getParameter("type");
String duration = request.getParameter("duration");
OrderParking orderParking=new OrderParking();
// 拼接日期和时间字符串
String datetime_str = date + " " + time;
// 定义日期时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
// 将拼接后的字符串转换为LocalDateTime对象
LocalDateTime datetime_obj = LocalDateTime.parse(datetime_str, formatter);
orderParking.setDate(datetime_obj);
orderParking.setLocation(location);
orderParking.setTel(tel);
orderParking.setParkId(Integer.valueOf(parkId));
orderParking.setTotalprice(Double.valueOf(totalprice));
orderParking.setType(type);
orderParking.setUsername(username);
// 计算截止时间
LocalDateTime endTime = datetime_obj.plusSeconds(Long.parseLong(duration)*3600L);
orderParking.setDeadtime(endTime);
orderParking.setDuration(Double.valueOf(duration));
orderParking.setType(type);
//判断是否可以预约
int status1 = orderService.findStatus(Integer.parseInt(parkId));
if(status1==1){
model.addAttribute("msg", "该车位已预约");
model.addAttribute("target", "http://"+address+":"+port+"/order/list");
return "/operate-result";
}
//将预约表单存入数据库
int i = orderService.addOrderParking(orderParking);
//这里模拟异常
String s=null;
System.out.println(s.toString());
//更新车位表状态
int status = orderService.updateStatus(Integer.parseInt(parkId));
if(status==1&&i==1){
model.addAttribute("msg", "预约成功");
model.addAttribute("target", "http://"+address+":"+port+"/order/list");
return "/operate-result";
}
model.addAttribute("msg", "预约失败");
model.addAttribute("target", "http://"+address+":"+port+"/order/list");
return "/operate-result";
}
执行结果
预约表中记录并为增添且车位状态并未更新(第二列为预约车位编号)
事务执行成功
5.编程式事务
以下是使用编程式事务管理的基本步骤:
1.配置事务管理器
首先,你需要在Spring配置中定义一个PlatformTransactionManager的bean。这通常是基于你使用的数据源类型(如JDBC、Hibernate、JPA等)来决定的。
例如,对于JDBC,你可以这样配置一个DataSourceTransactionManager:
2.使用TransactionTemplate
TransactionTemplate是Spring提供的一个帮助类,它简化了编程式事务的使用。你可以创建一个TransactionTemplate的bean,并注入你的事务管理器。然后,你可以在你的服务代码中使用这个TransactionTemplate来执行需要事务管理的操作。
这里是转账的逻辑
@Autowired
OrderMapper orderMapper;
public void update(String A,int aBalance,String B,int bBalance){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 在这里执行你的业务逻辑
orderMapper.updateAccount(A,aBalance);
String s=null;
System.out.println(s.toString());
orderMapper.updateAccount(B, bBalance);
// 如果抛出运行时异常,事务会自动回滚
} catch (Exception e) {
// 可以选择手动回滚
status.setRollbackOnly();
throw e;
}
}
});
}
3.手动管理事务
除了使用TransactionTemplate,你还可以直接使用PlatformTransactionManager来手动开始、提交和回滚事务。这通常是通过TransactionStatus对象来完成的。
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.PlatformTransactionManager;
@Service
public class MyService {
private final PlatformTransactionManager transactionManager;
public MyService(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void performManualTransactionalTask() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 在这里执行你的业务逻辑
// 如果抛出运行时异常,事务会自动回滚
transactionManager.commit(status);
} catch (Exception e) {
// 发生异常,手动回滚事务
transactionManager.rollback(status);
throw e;
}
}
}