每日学习Java之一万个为什么

TransactionTemplate使用

Spring提供的编程式事务类,支持自定义事务范围,以及更灵活更细粒度的事务操作。

以下是您要求的完整示例,包含使用 Lombok@RequiredArgsConstructorfinal 构造函数注入 TransactionTemplate,并通过事务操作、异常处理和全局异常处理器的实现。最后附上 @TransactionalTransactionTemplate 的对比表格。


1. 定义异常枚举类

// BizException.java
public enum BizExceptionEnum {
    DATABASE_ERROR(5001, "数据库操作失败"),
    ILLEGAL_ARGUMENT(4001, "非法参数");

    private final int code;
    private final String message;

    BizExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

// 自定义异常类
public class BizException extends RuntimeException {
    private final int code;

    public BizException(BizExceptionEnum enumType) {
        super(enumType.getMessage());
        this.code = enumType.getCode();
    }

    public int getCode() {
        return code;
    }
}

2. 配置 TransactionTemplate

// AppConfig.java
@Configuration
public class AppConfig {

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.setPropagationBehavior(Propagation.REQUIRED.value()); // 默认传播行为
        return template;
    }
}

3. Service 层实现

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

@Service
@RequiredArgsConstructor // Lombok 生成构造函数,参数为所有 final 字段
public class UserService {

    private final TransactionTemplate transactionTemplate;
    private final UserRepository userRepository; // 假设这是 DAO 层

    public void updateUserInTransaction(Long userId, String newName) {
        transactionTemplate.execute(status -> {
            try {
                // 执行数据库操作
                User user = userRepository.findById(userId)
                        .orElseThrow(() -> new BizException(BizExceptionEnum.ILLEGAL_ARGUMENT));
                user.setName(newName);
                userRepository.save(user);

                // 模拟可能的异常
                if (newName.equals("error")) {
                    throw new RuntimeException("Simulated database error");
                }

                return null;
            } catch (Exception e) {
                // 异常时手动回滚事务
                status.setRollbackOnly();
                throw new BizException(BizExceptionEnum.DATABASE_ERROR);
            }
        });
    }
}

4. DAO 层实现 / 替换为MySQL

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Optional<User> findById(Long userId) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.query(sql, (rs, rowNum) -> 
            new User(rs.getLong("id"), rs.getString("name"))
        ).stream().findFirst();
    }

    public void save(User user) {
        String sql = "UPDATE users SET name = ? WHERE id = ?";
        jdbcTemplate.update(sql, user.getName(), user.getId());
    }
}

5. 全局异常处理器

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BizException.class)
    public ResponseEntity<ErrorInfo> handleBizException(BizException ex) {
        ErrorInfo errorInfo = new ErrorInfo(ex.getCode(), ex.getMessage());
        return new ResponseEntity<>(errorInfo, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    private static class ErrorInfo {
        private final int code;
        private final String message;

        public ErrorInfo(int code, String message) {
            this.code = code;
            this.message = message;
        }

        // getters
    }
}

6. 事务操作边界说明

  • 事务开始:调用 transactionTemplate.execute(...) 时,事务开始。
  • 事务提交:若回调方法正常执行完毕,事务自动提交。
  • 事务回滚
    • 默认情况下,未捕获的 RuntimeException 会触发回滚。
    • catch 块中调用 status.setRollbackOnly() 显式回滚。

7. 对比表格:@Transactional vs TransactionTemplate

特性@Transactional(声明式事务)TransactionTemplate(编程式事务)
定义方式通过注解定义(如 @Transactional通过代码显式调用 TransactionTemplate.execute()
配置复杂度简单(只需配置事务管理器)需要手动配置 TransactionTemplate 和事务管理器
灵活性固定的事务边界(方法级别)可动态控制事务边界(如嵌套事务、条件性事务)
异常处理自动回滚 RuntimeException,需配置 rollbackFor手动控制回滚(通过 status.setRollbackOnly()
适用场景简单事务(如方法级事务)复杂事务逻辑(如动态事务、嵌套事务)
代码侵入性低(只需注解)高(需显式编写事务逻辑)
事务传播行为通过注解参数(如 propagation)设置通过 TransactionTemplate 的配置设置

场景描述

假设有一个订单处理流程,包含以下步骤:

  1. 保存订单:将订单信息保存到数据库。
  2. 扣减库存:调用库存服务扣减商品库存。
  3. 调用支付服务:调用第三方支付接口完成支付。
  4. 更新订单状态:根据支付结果更新订单状态。

要求:

  • 如果任何步骤失败,需回滚前面的操作。
  • 如果扣减库存失败,仅回滚库存操作(模拟嵌套事务)。
  • 如果支付失败,回滚整个订单操作。

1. 定义异常和枚举

// 自定义异常枚举
public enum OrderExceptionEnum {
    ORDER_SAVE_FAILED(5001, "保存订单失败"),
    INVENTORY_DEDUCT_FAILED(5002, "库存扣减失败"),
    PAYMENT_FAILED(5003, "支付失败");

    private final int code;
    private final String message;

    OrderExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    // 省略 getter 方法
}

// 自定义异常类
public class OrderException extends RuntimeException {
    private final int code;

    public OrderException(OrderExceptionEnum enumType, String message) {
        super(message);
        this.code = enumType.getCode();
    }

    // 省略 getter 方法
}

2. 配置 TransactionTemplate

@Configuration
public class AppConfig {

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}

3. Service 层实现(动态事务控制)

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final TransactionTemplate transactionTemplate;
    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final PaymentService paymentService;

    public void processOrder(Order order) {
        transactionTemplate.execute(status -> {
            try {
                // 步骤1:保存订单
                orderRepository.save(order);

                // 步骤2:扣减库存(模拟嵌套事务)
                executeInventoryDeduction(order);

                // 步骤3:调用支付服务
                PaymentResult paymentResult = paymentService.pay(order);
                if (!paymentResult.isSuccess()) {
                    throw new OrderException(OrderExceptionEnum.PAYMENT_FAILED, "支付失败");
                }

                // 步骤4:更新订单状态
                order.setStatus(OrderStatus.PAID);
                orderRepository.update(order);

                return "Success";
            } catch (OrderException e) {
                // 根据异常类型决定是否回滚
                status.setRollbackOnly();
                throw e;
            } catch (Exception e) {
                // 其他异常一律回滚
                status.setRollbackOnly();
                throw new OrderException(OrderExceptionEnum.ORDER_SAVE_FAILED, "未知错误", e);
            }
        });
    }

    // 执行扣减库存的嵌套事务
    private void executeInventoryDeduction(Order order) {
        TransactionTemplate nestedTemplate = new TransactionTemplate(transactionTemplate);
        nestedTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
        nestedTemplate.execute(nestedStatus -> {
            try {
                inventoryService.deduct(order.getProductId(), order.getQuantity());
            } catch (InventoryException e) {
                nestedStatus.setRollbackOnly();
                throw new OrderException(OrderExceptionEnum.INVENTORY_DEDUCT_FAILED, e.getMessage());
            }
            return null;
        });
    }
}

4. DAO 和 Service 层实现

// 订单仓储
@Repository
public class OrderRepository {
    public void save(Order order) {
        // 保存订单到数据库
    }

    public void update(Order order) {
        // 更新订单状态
    }
}

// 库存服务
@Service
public class InventoryService {
    public void deduct(String productId, int quantity) {
        // 扣减库存,可能抛出 InventoryException
    }
}

// 支付服务
@Service
public class PaymentService {
    public PaymentResult pay(Order order) {
        // 调用支付接口,返回 PaymentResult
        return new PaymentResult(true); // 模拟成功
    }
}

5. 动态事务控制的关键点

  1. 动态回滚控制

    • catch 块中,根据异常类型(如 OrderException)决定是否调用 status.setRollbackOnly()
    • 如果支付失败,直接回滚整个事务;如果库存扣减失败,仅回滚子事务(通过嵌套事务)。
  2. 嵌套事务的模拟

    • 使用 TransactionDefinition.PROPAGATION_NESTED 设置传播行为,创建子事务。
    • 如果子事务(扣减库存)失败,仅回滚到保存点,主事务继续执行后续步骤(但实际中需依赖数据库支持保存点)。

6. 异常捕获与全局异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(OrderException.class)
    public ResponseEntity<ErrorInfo> handleOrderException(OrderException ex) {
        ErrorInfo errorInfo = new ErrorInfo(ex.getCode(), ex.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorInfo);
    }

    private static class ErrorInfo {
        private final int code;
        private final String message;

        public ErrorInfo(int code, String message) {
            this.code = code;
            this.message = message;
        }
    }
}

7. 事务边界说明

步骤事务边界回滚条件
保存订单主事务开始任何异常触发回滚
扣减库存嵌套事务(保存点)库存不足时仅回滚子事务
调用支付主事务中执行支付失败时回滚整个主事务
更新订单状态主事务中执行异常时回滚整个主事务

8. 对比:声明式事务 vs 编程式事务

场景声明式事务(@Transactional)编程式事务(TransactionTemplate)
动态回滚控制无法根据异常类型动态决定回滚可通过 status.setRollbackOnly() 精确控制回滚
嵌套事务支持仅支持传播行为(如 PROPAGATION_NESTED可通过 TransactionDefinition.PROPAGATION_NESTED 模拟
复杂逻辑处理无法在事务中嵌套条件判断或分阶段提交可在 execute 回调中实现复杂的条件逻辑和分阶段操作
代码侵入性低(仅需注解)高(需编写事务逻辑代码)

总结

  • 动态事务控制:通过 TransactionTemplateexecute 回调,可以灵活处理不同异常类型,并决定是否回滚事务。
  • 嵌套事务:通过 PROPAGATION_NESTED 实现局部回滚,但需依赖数据库支持保存点(如 MySQL 的 SAVEPOINT)。
  • 适用场景:当需要根据业务逻辑动态控制事务边界、处理复杂条件或与第三方服务交互时,TransactionTemplate 是更灵活的选择。

Sa-Token

对比维度Sa-TokenSpring Security
核心定位轻量级权限认证框架,专注于登录认证、权限控制、单点登录(SSO)、OAuth2 等核心功能。企业级安全框架,提供全面的安全解决方案,包括认证、授权、加密、OAuth2、CSRF 防护等。
上手难度简单易用,API 设计简洁,开箱即用,适合快速集成。复杂,配置繁琐,学习曲线陡峭,需要熟悉 Spring 生态系统和安全概念。
功能特性- 登录认证
- 权限验证(RBAC 模型)
- 分布式 Session
- SSO(三种模式)
- OAuth2 支持(四种授权模式)
- 自动续签、踢人下线、封禁账号等。
- 完整的认证与授权体系
- 支持 OAuth2、OpenID、JWT
- CSRF 防护
- 细粒度权限控制
- 与 Spring 生态无缝集成。
社区与生态社区活跃,文档完善,提供大量示例和插件(如 SSO、OAuth2)。社区庞大,资源丰富,Spring 家族成员,与 Spring Boot/Spring MVC 等深度整合。
适用场景- 小型到中型项目
- 需要快速实现基础权限控制和 SSO
- 对轻量级和易用性要求高。
- 复杂企业级应用
- 需要细粒度权限控制和高级安全功能
- 依赖 Spring 生态系统的项目。
扩展性通过插件和自定义接口扩展,但灵活性有限。极强,支持自定义认证提供者、权限表达式(如 SpEL)、过滤器链等,适合复杂需求。
性能与资源占用轻量级,资源占用低,适合对性能敏感的场景。功能全面但配置复杂,可能增加资源开销,需合理优化。
与 Spring 的集成提供 Spring Boot Starter,开箱即用,但非 Spring 生态核心组件。原生 Spring 家族成员,与 Spring Boot、Spring MVC 等无缝集成,依赖关系紧密。
安全特性内置防 Ticket 劫持、参数加密等,适合通用场景。支持更全面的安全机制(如加密算法、安全响应处理、攻击防护),适合高安全需求场景。
分布式支持支持分布式 Session 和 SSO(通过 Redis 或 HTTP 接口),提供多种模式适配不同架构。需借助第三方组件(如 Redis、JWT)实现分布式安全,配置复杂度较高。

如果需要 SSO 或 OAuth2 的 开箱即用支持,Sa-Token 的实现更简单;而 Spring Security 需要更多自定义配置。
对于 微服务架构,两者均支持,但 Sa-Token 通过插件化设计更易快速集成,而 Spring Security 需借助 Zuul/Gateway 等组件。

string.intern()

将某个字符串引用指向其常量池,避免额外空间浪费。但是这个值如果用来加锁会有释放不及时的问题。

Spring官网为什么推荐构造器注入

Spring 官方推荐构造器注入(Constructor Injection)而非字段注入(Field Injection)或Setter注入,主要基于以下核心原因,结合知识库中的信息总结如下:


**1. ** 显式声明依赖关系

  • 显式性:通过构造函数的参数,可以一目了然地看到一个类的所有依赖项。例如:
    public class OrderService {
        private final PaymentService paymentService;
        private final InventoryService inventoryService;
    
        @Autowired
        public OrderService(PaymentService paymentService, InventoryService inventoryService) {
            this.paymentService = paymentService;
            this.inventoryService = inventoryService;
        }
    
    • 优点:依赖关系清晰,开发者无需查看类内部属性即可了解类的依赖项。
    • 对比字段注入:字段注入(如 @Autowired private PaymentService paymentService;)会隐藏依赖关系,导致类的可读性下降。

**2. ** 强制依赖完整性

  • 依赖必须存在:构造器注入要求在对象实例化时所有依赖项必须被提供,否则无法创建对象实例。例如:
    // 如果 PaymentService 或 InventoryService 未被正确注入,构造器将无法实例化 OrderService
    
    • 优点:避免了依赖项为 null 的风险,确保对象在创建时就处于有效状态。
    • 对比Setter注入:Setter注入允许依赖项在对象创建后延迟注入,可能导致依赖未被正确初始化时调用方法,引发 NullPointerException

**3. ** 支持不可变性(Immutability)

  • 依赖不可变:通过将依赖项声明为 final,可以确保依赖在对象初始化后无法被修改:
    private final PaymentService paymentService;
    
    • 优点:提高代码的线程安全性和稳定性,避免多线程环境下依赖被意外修改。
    • 对比Setter注入:Setter注入允许依赖在运行时被修改,可能引入不可预测的行为。

**4. ** 提高可测试性

  • 易于单元测试:构造器注入使得在测试时可以方便地传入模拟对象(Mock):
    @Test
    public void testOrderService() {
        PaymentService mockPayment = mock(PaymentService.class);
        InventoryService mockInventory = mock(InventoryService.class);
        OrderService service = new OrderService(mockPayment, mockInventory);
        // 测试逻辑...
    }
    
    • 优点:无需依赖Spring容器即可手动实例化对象,测试更灵活、可控。
    • 对比字段注入:字段注入需要通过反射或复杂的配置来模拟依赖,测试代码更复杂。

**5. ** 避免与框架的强耦合

  • POJO(普通Java对象)原则:构造器注入使类保持为简单的POJO,不依赖于Spring的注解或框架特性。例如:
    // 即使不使用Spring,也可以通过构造器手动实例化对象
    OrderService service = new OrderService(new PaymentService(), new InventoryService());
    
    • 优点:类的设计更独立,可移植性更强,不绑定到特定框架。
    • 对比字段注入:字段注入直接依赖于 @Autowired 注解,增加了与Spring框架的耦合。

**6. ** 防止代码异味(Code Smell)

  • 减少过度依赖:构造函数参数过多可能提示类承担了过多职责(违反单一职责原则)。例如:
    // 过多的参数可能提示需要重构
    public OrderService(Dependency1 d1, Dependency2 d2, ..., DependencyN dN) { ... }
    
    • 优点:通过构造函数参数的臃肿问题,促使开发者对类进行合理拆分,提升代码质量。
    • 对比Setter注入:Setter注入可能使类的依赖项无限制增加,隐藏设计问题。

**7. ** Spring框架的优化支持

  • 自动装配的灵活性
    • Spring 4.3+ 支持通过无注解构造函数进行自动装配,只要类只有一个构造函数:
      public class OrderService {
          private final PaymentService paymentService;
          private final InventoryService inventoryService;
      
          // 无需 @Autowired 注解,Spring 自动识别
          public OrderService(PaymentService paymentService, InventoryService inventoryService) {
              // ...
          }
      }
      
    • 优点:减少注解的冗余,代码更简洁。

对比其他注入方式的缺点

字段注入(Field Injection)
  • 问题
    1. 隐藏依赖:依赖关系不显式,难以通过类构造函数快速了解依赖。
    2. 延迟初始化风险:字段可能未被及时注入,导致运行时 NullPointerException
    3. 破坏封装性:直接暴露字段,可能被外部修改。
    4. 测试困难:需通过反射或框架支持注入模拟对象。
Setter注入(Setter Injection)
  • 适用场景
    • 非必需的依赖(可选配置)。
    • 需要在对象创建后动态修改依赖的情况。
  • 问题
    • 依赖可能未被及时注入,需添加 @Required 注解确保必填性。
    • 依赖可变性可能引入不可预测的行为。

总结:Spring推荐构造器注入的核心原因

原因具体表现
显式性依赖关系清晰可见,无需深入类内部即可了解依赖项。
强制完整性确保依赖在对象创建时必须存在,避免 null 引发的异常。
不可变性通过 final 修饰符保证依赖不可变,提升线程安全性和代码稳定性。
可测试性方便单元测试中传入模拟对象,无需依赖框架。
减少框架耦合类保持为POJO,可独立于Spring实例化。
代码质量保障通过参数臃肿问题提示设计缺陷,避免类承担过多职责。

最佳实践建议

  1. 强制依赖:使用构造器注入(如服务、数据库连接等必需依赖)。
  2. 可选/可变依赖:使用Setter注入(如配置参数、非核心组件)。
  3. 避免字段注入:除非有特殊需求,否则不推荐直接通过字段注解注入。

通过构造器注入,可以编写出更健壮、可维护且符合设计原则的代码,这也是Spring官方推荐的核心原因。

多账号认证

场景:当我们一个系统有不同的权限体系。例如模块与模块之间的功能并不相同。这个时候怎么区分开不同的权限认证体系。

HttpBasic / Digest认证

OAuth2

单点登录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~Yogi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值