概述
Spring
针对Java Transaction API (JTA)
、JDBC
、Hibernate
和Java Persistence API(JPA)
等业务 API,完结了共同的编程模型,而Spring
的声明式业务功用更是提供了极端便利的业务装备办法,配合Spring Boot
的主动装备,大多数Spring Boot
项目只需要在办法上标记@Transactional
注解,即可一键敞开办法的业务性装备。可是,业务假如没有被正确出,很有可能会导致业务的失效,带来意想不到的数据不共同问题,随后便是大量的人工接入检查和修复数据,该篇首要共享Spring
业务在技术上的正确运用办法,避免由于业务处理不当导致业务逻辑发生大量偶发性BUG
。
在分析业务失效的常见场景之前,咱们先来了解一下:业务的传达类型 和 @Transactionnal 注解的不同特点的意义。
业务的传达类型
//假如有业务, 那么参加业务, 没有的话新建一个(默许)
@Transactional(propagation=Propagation.REQUIRED)
//容器不为这个办法敞开业务
@Transactional(propagation=Propagation.NOT_SUPPORTED)
//不管是否存在业务, 都创立一个新的业务, 本来的挂起, 新的履行完毕, 继续履行老的业务
@Transactional(propagation=Propagation.REQUIRES_NEW)
//有必要在一个已有的业务中履行, 不然抛出反常
@Transactional(propagation=Propagation.MANDATORY)
//有必要在一个没有的业务中履行, 不然抛出反常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.NEVER)
//假如其他bean调用这个办法, 在其他bean中声明业务, 那就用业务, 假如其他bean没有声明业务, 那就不用业务
@Transactional(propagation=Propagation.SUPPORTS)
isolation
该特点用于设置底层数据库的业务阻隔等级,业务的阻隔等级介绍:
// 读取未提交数据(会呈现脏读, 不可重复读) 根本不运用
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// 读取已提交数据(会呈现不可重复读和幻读) Oracle默许
@Transactional(isolation = Isolation.READ_COMMITTED)
// 可重复读(会呈现幻读) MySQL默许
@Transactional(isolation = Isolation.REPEATABLE_READ)
// 串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
@Transactionnal注解特点
@Transactional
注解能够作用于接口、接口办法、类以及类办法上,它能够经过不同的参数来选择什么类型Exception
反常下履行回滚或许不回滚操作。
参数 | 阐明 |
---|---|
rollbackFor | 用于指定有必要履行业务回滚的反常类型,能够是一个也能够是多个,当经过rollbackFor 指定对应反常之后,办法履行过程中只有抛出该类型反常,才会触发业务的回滚,比方:@Transactional(rollbackFor = BusinessException.class)
|
rollbackForClassName | 与rollbackFor 功用相同,能够为彻底限制类名的字符串类型,比方:@Transactional(rollbackForClass = {"BusinessException.class", "RuntimeException.class"})
|
noRollbackFor | 用于指定不需要履行业务回滚的反常类型,能够是一个也能够是多个,当经过noRollbackFor 指定对应反常之后,办法履行过程中抛出该类型反常,不触发业务的回滚 |
noRollbackForClassName | 与noRollbackFor 功用相同,能够为彻底限制类名的字符串类型 |
propagation | 用户设置业务的传达行为,例如:@Transactional(propagation=Propagation.REQUIRED ) |
Spring业务失效的场景
1. 业务办法未被Spring办理
假如业务办法地点的类没有注册到Spring IOC
容器中,也便是说,业务办法地点类并没有被Spring
办理,则Spring
业务会失效,举个比方:
/**
* 产品业务完结层
*
* @author: austin
* @since: 2023/2/10 14:19
*/
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
@Autowired
private ProductMapper productMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateProductStockById(Integer stockCount, Long productId) {
productMapper.updateProductStockById(stockCount, productId);
}
}
ProductServiceImpl
完结类上没有增加@Service
注解,Product
的实例也就没有被加载到Spring IOC
容器,此刻updateProductStockById()
办法的业务就会在Spring
中失效。
2. 办法运用final类型润饰
有时候,某个办法不想被子类重新,这时能够将该办法界说成final
的。一般办法这样界说是没问题的,但假如将业务办法界说成final
,例如:
@Service
public class OrderServiceImpl {
@Transactional
public final void cancel(OrderDTO orderDTO) {
// 撤销订单
cancelOrder(orderDTO);
}
}
OrderServiceImpl
的cancel
撤销订单办法被final
润饰符润饰,Spring
业务底层运用了AOP
,也便是经过JDK
动态署理或许cglib
,帮咱们生成了署理类,在署理类中完结的业务功用。但假如某个办法用final
润饰了,那么在它的署理类中,就无法重写该办法,从而无法增加业务功用。这种情况业务就会在Spring
中失效。
Tips: 假如某个办法是
static
的,相同无法经过动态署理将办法声明为业务办法。
3. 非public润饰的办法
假如业务办法不是public
润饰,此刻Spring
业务会失效,举个比方:
/**
* 产品业务完结层
*
* @author: austin
* @since: 2023/2/10 14:19
*/
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
@Autowired
private ProductMapper productMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void updateProductStockById(Integer stockCount, String productId) {
productMapper.updateProductStockById(stockCount, productId);
}
}
尽管ProductServiceImpl
增加了@Service
注解,一起updateProductStockById()
办法上增加了@Transactional(propagation = Propagation.REQUIRES_NEW)
注解,可是由于业务办法updateProductStockById()
被 private
界说为办法内私有,相同Spring
业务会失效。
4. 同一个类中的办法相互调用
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
public ResponseEntity submitOrder(Order order) {
// 保存生成订单信息
long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_" + orderNo);
orderMapper.insert(order);
// 扣减库存
this.updateProductStockById(order.getProductId(), 1L);
return new ResponseEntity(HttpStatus.OK);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateProductStockById(Integer num, Long productId) {
productMapper.updateProductStockById(num, productId);
}
}
submitOrder()
办法和updateProductStockById()
办法都在OrderService
类中,然而submitOrder()
办法没有增加业务注解,updateProductStockById()
办法尽管增加了业务注解,这种情况updateProductStockById()
会在Spring
业务中失效。
5. 办法的业务传达类型不支撑业务
假如内部办法的业务传达类型为不支撑业务的传达类型,则内部办法的业务相同会在Spring
中失效,举个比方:
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ResponseEntity submitOrder(Order order) {
long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_" + orderNo);
orderMapper.insert(order);
// 扣减库存
this.updateProductStockById(order.getProductId(), 1L);
return new ResponseEntity(HttpStatus.OK);
}
/**
* 扣减库存办法业务类型声明为NOT_SUPPORTED不支撑业务的传达
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateProductStockById(Integer num, Long productId) {
productMapper.updateProductStockById(num, productId);
}
}
6. 反常被内部catch,程序生吞反常
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ResponseEntity submitOrder(Order order) {
long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_" + orderNo);
orderMapper.insert(order);
// 扣减库存
this.updateProductStockById(order.getProductId(), 1L);
return new ResponseEntity(HttpStatus.OK);
}
/**
* 扣减库存办法业务类型声明为NOT_SUPPORTED不支撑业务的传达
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateProductStockById(Integer num, Long productId) {
try {
productMapper.updateProductStockById(num, productId);
} catch (Exception e) {
// 这里仅仅是捕获反常之后的打印(相当于程序吞掉了反常)
log.error("Error updating product Stock: {}", e);
}
}
}
7. 数据库不支撑业务
Spring
业务收效的前提是衔接的数据库支撑业务,假如底层的数据库都不支撑业务,则Spring
业务肯定会失效的,例如:运用MySQL
数据库,选用MyISAM
存储引擎,由于MyISAM
存储引擎自身不支撑业务,因而业务毫无疑问会失效。
8. 未装备敞开业务
假如项目中没有装备Spring
的业务办理器,即使运用了Spring
的业务办理功用,Spring
的业务也不会收效,例如,假如你是Spring Boot
项目,没有在SpringBoot
项目中装备如下代码:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
假如是以往的Spring MVC
项目,假如没有装备下面的代码,Spring
业务也不会收效,正常需要在applicationContext.xml
文件中,手动装备业务相关参数,比方:
<!-- 装备业务办理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切点把业务切进去 -->
<aop:config>
<aop:pointcut expression="execution(* com.universal.ubdk.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
9. 错误的传达特性
其实,咱们在运用@Transactional
注解时,是能够指定propagation
参数的。
该参数的作用是指定业务的传达特性,目前Spring
支撑7种传达特性:
-
REQUIRED
假如当时上下文中存在业务,那么参加该业务,假如不存在业务,创立一个业务,这是默许的传达特点值。 -
SUPPORTS
假如当时上下文存在业务,则支撑业务参加业务,假如不存在业务,则运用非业务的办法履行。 -
MANDATORY
假如当时上下文中存在业务,不然抛出反常。 -
REQUIRES_NEW
每次都会新建一个业务,并且一起将上下文中的业务挂起,履行当时新建业务完结以后,上下文业务恢复再履行。 -
NOT_SUPPORTED
假如当时上下文中存在业务,则挂起当时业务,然后新的办法在没有业务的环境中履行。 -
NEVER
假如当时上下文中存在业务,则抛出反常,不然在无业务环境上履行代码。 -
NESTED
假如当时上下文中存在业务,则嵌套业务履行,假如不存在业务,则新建业务。
假如咱们在手动设置propagation
参数的时候,把传达特性设置错了,比方:
@Service
public class OrderServiceImpl {
@Transactional(propagation = Propagation.NEVER)
public void cancelOrder(UserModel userModel) {
// 撤销订单
cancelOrder(orderDTO);
// 复原库存
restoreProductStock(orderDTO.getProductId(), orderDTO.getProductCount());
}
}
咱们能够看到cancelOrder()
办法的业务传达特性界说成了Propagation.NEVER
,这种类型的传达特性不支撑业务,假如有业务则会抛反常。
10. 多线程调用
在实践项目开发中,多线程的运用场景还是挺多的。假如Spring
业务用在多线程场景中运用不当,也会导致业务无法收效。
@Slf4j
@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Autowired
private MessageService messageService;
@Transactional
public void orderCommit(orderModel orderModel) throws Exception {
orderMapper.insertOrder(orderModel);
new Thread(() -> {
messageService.sendSms();
}).start();
}
}
@Service
public class MessageService {
@Transactional
public void sendSms() {
// 发送短信
}
}
经过示例,咱们能够看到订单提交的业务办法orderCommit()
中,调用了发送短信的业务办法sendSms()
,可是发送短信的业务办法sendSms()
是另起了一个线程调用的。
这样会导致两个办法不在同一个线程中,从而是两个不同的业务。假如是sendSms()
办法中抛了反常,orderCommit()
办法也回滚是不可能的。
实践上,Spring
的业务是经过ThreadLocal
来确保线程安全的,业务和当时线程绑定,多个线程自然会让业务失效。
总结
本篇文章首要是介绍
Spring
业务传达特性,阐明了@Transactional
注解特点的运用办法,经过不同的代码示例演示了Spring
业务失效的常见场景,假如文章对你有所帮助,欢迎点赞+谈论+收藏❤,我是:austin流川枫,咱们下期见~