【大白话说Java面试题 第140题】【05_Mybatis篇】第10题:MyBatis 的事务管理是如何实现的?

📌 PDF:大白话说Java面试题 — 05_Mybatis篇

第10题:MyBatis 的事务管理是如何实现的?

📚 回答:

  • 核心考点: MyBatis 事务管理是面试中看似简单实则容易踩坑的考点。面试官不会满足于"手动 commit/rollback 或用 Spring @Transactional"这种表面回答,而是深入考察 MyBatis 原生两种事务工厂(JDBC vs MANAGED)的源码级差异SqlSession 创建时 Transaction 对象的装配链路Spring 整合后 SqlSessionTemplate 如何通过 ThreadLocal 和动态代理实现线程安全与事务同步、以及 声明式事务的 AOP 拦截链路与 Connection 绑定机制。面试官真正想判断的是:你是否理解从 MyBatis 原生到 Spring 整合的完整事务链路,以及能否在生产环境中正确排查事务失效问题。
1. MyBatis 原生事务管理——两种 TransactionFactory 深度对比

MyBatis 通过 TransactionFactory 接口抽象事务管理,内置两种实现:[citation:5]

事务类型工厂类事务实现类commit() 行为rollback() 行为close() 行为适用场景
JDBCJdbcTransactionFactoryJdbcTransaction调用 Connection.commit()调用 Connection.rollback()关闭 Connection独立使用 MyBatis
MANAGEDManagedTransactionFactoryManagedTransaction空实现空实现仅关闭 Connection(不提交/回滚)容器管理事务(如 Spring)
  • JDBC 事务:MyBatis 自己管理事务,通过 Connectioncommit()/rollback() 控制。这是独立使用 MyBatis 时的默认方式。
  • MANAGED 事务:MyBatis 不管理事务,将事务控制权交给外部容器(如 Spring 的 DataSourceTransactionManager)。commit()rollback() 是空实现,仅负责关闭连接。[citation:0]

配置方式

<!-- mybatis-config.xml -->
<environments default="development">
  <environment id="development">
    <!-- JDBC: MyBatis 自己管理事务 -->
    <transactionManager type="JDBC"/>
    <!-- MANAGED: 交给容器管理 -->
    <!-- <transactionManager type="MANAGED"/> -->
    <dataSource type="POOLED">...</dataSource>
  </environment>
</environments>
2. JDBC 事务管理——源码级执行链路
  • 2.1 SqlSession 创建时的 Transaction 装配 当调用 sqlSessionFactory.openSession() 时,MyBatis 内部执行以下链路:[citation:4]

    // DefaultSqlSessionFactory.openSession()
    public SqlSession openSession() {
        return openSessionFromDataSource(
            configuration.getDefaultExecutorType(),  // SIMPLE
            null,                                     // 隔离级别
            false                                     // autoCommit=false
        );
    }
    
    private SqlSession openSessionFromDataSource(ExecutorType execType,
        TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            final Environment environment = configuration.getEnvironment();
            // 1. 获取 TransactionFactory(默认 JdbcTransactionFactory)
            final TransactionFactory transactionFactory =
                getTransactionFactoryFromEnvironment(environment);
            // 2. 创建 Transaction 实例
            tx = transactionFactory.newTransaction(
                environment.getDataSource(), level, autoCommit);
            // 3. 创建 Executor(事务对象注入 Executor)
            final Executor executor = configuration.newExecutor(tx, execType);
            // 4. 创建 DefaultSqlSession
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx);
            throw ExceptionFactory.wrapException(...);
        }
    }
    

    关键认知Transaction 对象被注入 Executor,而 SqlSessioncommit()/rollback() 实际上是委托给 Executor,最终调用 Transaction.commit()/rollback()。[citation:17]

  • 2.2 手动事务管理代码示例

    SqlSession sqlSession = sqlSessionFactory.openSession();  // autoCommit=false
    try {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.insertUser(user);
        mapper.updateUserStatus(user.getId(), "ACTIVE");
        sqlSession.commit();  // 调用 JdbcTransaction.commit() → Connection.commit()
    } catch (Exception e) {
        sqlSession.rollback();  // 调用 JdbcTransaction.rollback() → Connection.rollback()
        throw e;
    } finally {
        sqlSession.close();  // 关闭 Connection,归还连接池
    }
    
  • 2.3 openSession() 的重载与事务隔离级别

    方法签名autoCommit隔离级别用途
    openSession()false默认手动事务管理(默认)
    openSession(boolean autoCommit)指定默认简单查询可设 true
    openSession(TransactionIsolationLevel level)false指定需要特定隔离级别
    openSession(ExecutorType execType)false默认指定执行器类型
    // 开启可重复读隔离级别的事务
    SqlSession sqlSession = sqlSessionFactory.openSession(
        TransactionIsolationLevel.REPEATABLE_READ);
    
3. Spring 整合事务管理——声明式事务的完整链路
  • 3.1 核心组件与职责 Spring 整合 MyBatis 后,事务管理由 Spring 全权接管,核心组件如下:[citation:0][citation:6]

    组件职责
    DataSourceTransactionManagerSpring 事务管理器,控制事务的开启、提交、回滚
    SqlSessionFactoryBean创建 SqlSessionFactory,配置数据源和 Mapper
    SqlSessionTemplate线程安全的 SqlSession 代理,通过 ThreadLocal 管理
    SpringManagedTransactionMyBatis-Spring 提供的事务实现,委托给 Spring 管理
    TransactionSynchronizationManagerSpring 事务同步管理器,ThreadLocal 绑定 Connection
  • 3.2 SqlSessionTemplate 的线程安全与事务同步 SqlSessionTemplate 是 MyBatis-Spring 的核心类,它通过 JDK 动态代理 解决 SqlSession 的线程安全问题:[citation:15]

    public class SqlSessionTemplate implements SqlSession {
        private final SqlSessionFactory sqlSessionFactory;
        private final SqlSession sqlSessionProxy;  // JDK 动态代理
    
        public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
            this.sqlSessionFactory = sqlSessionFactory;
            // 创建代理对象,拦截所有 SqlSession 方法
            this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[]{SqlSession.class},
                new SqlSessionInterceptor()
            );
        }
    }
    

    SqlSessionInterceptor.invoke() 的核心逻辑:[citation:15]

    public Object invoke(Object proxy, Method method, Object[] args) {
        // 1. 获取 SqlSession(优先从 Spring 事务上下文中获取)
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(
            sqlSessionFactory, executorType, exceptionTranslator);
        try {
            // 2. 执行目标方法
            Object result = method.invoke(sqlSession, args);
            // 3. 如果不是 Spring 管理的事务,自动提交
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {
                sqlSession.commit(true);  // force commit
            }
            return result;
        } catch (Throwable t) {
            // 4. 异常时回滚(非事务场景)
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {
                sqlSession.rollback();
            }
            throw ExceptionUtil.unwrapThrowable(t);
        } finally {
            // 5. 关闭 SqlSession(非事务场景)
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {
                sqlSession.close();
            }
        }
    }
    

    关键设计:如果当前线程已绑定 Spring 事务,SqlSessionUtils.getSqlSession() 会返回事务中的同一个 SqlSession,且不会自动提交/关闭;如果无事务,则创建新的 SqlSession 并自动管理生命周期。[citation:15]

  • 3.3 Spring 声明式事务的 AOP 拦截链路 当方法标注 @Transactional 时,Spring 通过 AOP 代理拦截方法调用,完整链路如下:[citation:6][citation:7]

    @Transactional 方法调用
        ↓
    Spring AOP 代理(CglibAopProxy / JdkDynamicAopProxy)
        ↓
    TransactionInterceptor.invoke()  // 事务拦截器
        ↓
    PlatformTransactionManager.getTransaction()  // 开启事务
        ↓
    DataSourceTransactionManager.doBegin()
        ↓
    DataSource.getConnection() + connection.setAutoCommit(false)  // 获取连接,关闭自动提交
        ↓
    TransactionSynchronizationManager.bindResource(dataSource, connectionHolder)  // ThreadLocal 绑定
        ↓
    执行业务方法(MyBatis Mapper 操作)
        ↓
    SqlSessionTemplate → SqlSessionUtils.getSqlSession()
        ↓
    从 TransactionSynchronizationManager 获取已绑定的 Connection
        ↓
    使用同一个 Connection 执行 SQL
        ↓
    方法正常结束 / 抛出异常
        ↓
    TransactionInterceptor 提交 / 回滚
        ↓
    TransactionSynchronizationManager.unbindResource(dataSource)  // 解绑 ThreadLocal
        ↓
    Connection.close()  // 归还连接池
    

    核心机制:Spring 通过 TransactionSynchronizationManagerDataSourceConnectionHolder 绑定到当前线程的 ThreadLocal 中。MyBatis 的 SqlSessionTemplate 在执行 SQL 时,通过 DataSourceUtils.getConnection(dataSource)ThreadLocal 中获取同一个 Connection,确保同一事务内所有操作使用同一连接。[citation:0]

  • 3.4 Spring 事务配置示例

    @Configuration
    @EnableTransactionManagement  // 开启声明式事务
    public class TransactionConfig {
    
        @Bean
        public DataSourceTransactionManager transactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
        @Bean
        public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource);
            factoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver()
                    .getResources("classpath:mappers/**/*.xml"));
            return factoryBean.getObject();
        }
    
        @Bean
        public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
    
    @Service
    public class UserService {
        @Autowired
        private UserMapper userMapper;
        @Autowired
        private OrderMapper orderMapper;
    
        @Transactional(rollbackFor = Exception.class, timeout = 30)
        public void createOrder(User user, Order order) {
            userMapper.insert(user);
            orderMapper.insert(order);
            // 异常时自动回滚,无需手动处理
        }
    }
    
4. 声明式事务 vs 编程式事务深度对比
对比维度声明式事务(@Transactional)编程式事务(TransactionTemplate)
实现方式AOP 代理 + 注解编码控制事务边界
代码侵入性低(只需注解)高(需编写事务代码)
灵活性低(固定切面)高(可动态控制)
适用场景大多数业务方法复杂事务逻辑、跨服务事务
异常回滚默认 RuntimeException / Error需手动指定
事务传播通过注解属性配置通过代码逻辑控制
可读性高(声明式意图清晰)中(业务代码被事务代码包裹)

编程式事务示例(适用于需要精细控制的场景):

@Autowired
private TransactionTemplate transactionTemplate;

public void complexBusiness() {
    Boolean result = transactionTemplate.execute(status -> {
        try {
            userMapper.insert(user);
            orderMapper.insert(order);
            if (someCondition) {
                status.setRollbackOnly();  // 手动标记回滚
                return false;
            }
            return true;
        } catch (Exception e) {
            status.setRollbackOnly();
            throw e;
        }
    });
}
5. Spring Boot 自动配置下的特殊行为
  • 5.1 无 @Transactional 时的"自动提交" 在 Spring Boot 中,即使不标注 @Transactional,MyBatis 的每次 Mapper 操作也会自动提交。这不是 MyBatis 的 autoCommit=true,而是 Spring 为每个数据库操作开启并立即提交一个独立事务:[citation:1]

    场景是否自动提交控制者说明
    原生 MyBatis(无 Spring)❌ 默认不提交MyBatis需手动 sqlSession.commit()
    Spring Boot + MyBatis(无 @Transactional)✅ 自动提交Spring每条 SQL 独立事务
    Spring Boot + MyBatis(有 @Transactional)❌ 方法结束前不提交Spring成功提交,异常回滚

    原因SqlSessionTemplate 的代理逻辑中,如果检测到当前无 Spring 事务,会在方法执行后自动调用 sqlSession.commit(true)。[citation:1]

  • 5.2 事务管理器与 SqlSessionFactory 必须共用同一个 DataSource 如果 DataSourceTransactionManagerSqlSessionFactoryBean 配置不同的 DataSource,Spring 事务管理器无法感知 MyBatis 的数据库连接,导致事务失效:[citation:15]

    // ❌ 错误:两个不同的 DataSource
    @Bean
    public DataSourceTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource1());  // dataSource1
    }
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource2());  // dataSource2!事务失效!
        return factory.getObject();
    }
    
6. 生产环境避坑指南
  • 6.1 @Transactional 失效的 6 大场景

    失效原因示例解决方案
    方法非 publicprivate @Transactional void method()改为 public
    同类内部调用this.transactionalMethod()注入自身代理对象,或拆分到另一个 Service
    异常被捕获未抛出try { ... } catch (Exception e) { log.error(e); }捕获后重新抛出,或手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
    异常类型不匹配抛出 Checked Exception(默认不回滚)@Transactional(rollbackFor = Exception.class)
    数据库引擎不支持MySQL 使用 MyISAM 引擎改用 InnoDB 引擎
    多数据源未指定事务管理器@Transactional 未指定 transactionManager@Transactional(transactionManager = "db1TxManager")
  • 6.2 事务传播行为误用 Spring 提供 7 种传播行为,最常见的误用是 REQUIRED(默认)和 REQUIRES_NEW 的混淆:

    传播行为行为描述典型误用
    REQUIRED(默认)加入当前事务,无则新建
    REQUIRES_NEW挂起当前事务,新建独立事务在循环中调用,导致大量事务创建
    NESTED在当前事务中创建 savepointREQUIRES_NEW 混淆
  • 6.3 长事务风险 @Transactional 方法中执行远程调用、大量计算或循环操作,会导致数据库连接长时间不释放:

    // ❌ 错误:长事务
    @Transactional
    public void badPractice() {
        for (int i = 0; i < 100000; i++) {
            userMapper.insert(user);  // 10万次插入在一个事务中
        }
        // 远程调用阻塞 30 秒
        rpcService.call();
    }
    

    优化:批量插入、远程调用移出事务、分页处理。

  • 6.4 只读事务优化 查询方法应标注 @Transactional(readOnly = true),Spring 会设置 connection.setReadOnly(true),数据库可进行优化(如 MySQL 不加锁读、Oracle 不生成 Undo)。

  • 6.5 事务隔离级别配置 根据业务场景选择合适的隔离级别:

    @Transactional(isolation = Isolation.READ_COMMITTED)  // 读已提交
    public List<User> queryUsers() { ... }
    
7. 面试官追问与高分回答模板
  • 追问 1:“MyBatis 的事务管理是如何实现的?”

    低分回答:“可以用 JDBC 手动管理,也可以用 Spring 的 @Transactional。”(没有触及源码链路)

    高分回答

    "MyBatis 的事务管理分两种模式:

    1. 原生 JDBC 模式:通过 TransactionFactory 创建 Transaction 实例(默认 JdbcTransactionFactory 创建 JdbcTransaction)。SqlSessionFactory.openSession() 时,Transaction 被注入 Executor,SqlSession 的 commit()/rollback() 委托给 Executor,最终调用 JdbcTransactionConnection.commit()/rollback()。需要手动管理事务边界。
    2. Spring 整合模式:Spring 通过 DataSourceTransactionManager 管理事务,SqlSessionTemplate 通过 JDK 动态代理拦截所有 SqlSession 方法。如果当前线程有 Spring 事务,从 TransactionSynchronizationManager 的 ThreadLocal 中获取绑定的 Connection,复用同一个 SqlSession;如果无事务,则自动创建、提交、关闭 SqlSession。
    3. MANAGED 模式ManagedTransactionFactory 创建的 ManagedTransactioncommit()/rollback() 是空实现,事务完全交给外部容器(如 Spring)管理。"
  • 追问 2:“Spring 整合 MyBatis 后,事务是怎么保证的?SqlSession 不是线程不安全的吗?”

    低分回答:“Spring 用 @Transactional 管理事务,SqlSessionTemplate 是线程安全的。”(没有讲清机制)

    高分回答

    "Spring 整合后通过两层机制保证线程安全和事务一致性:

    1. 线程安全SqlSessionTemplate 通过 JDK 动态代理实现线程安全。每次调用 Mapper 方法时,代理对象的 invoke() 方法会调用 SqlSessionUtils.getSqlSession(),该方法优先从 TransactionSynchronizationManager(ThreadLocal)获取当前线程绑定的 SqlSession。如果无事务,则创建新的 SqlSession,方法结束后自动关闭。
    2. 事务一致性@Transactional 方法被 Spring AOP 拦截后,DataSourceTransactionManager 从数据源获取 Connection,设置 autoCommit=false,并将 Connection 绑定到当前线程的 ThreadLocal。MyBatis 执行 SQL 时,SpringManagedTransaction 通过 DataSourceUtils.getConnection(dataSource) 从 ThreadLocal 获取同一个 Connection,确保同一事务内所有操作使用同一连接。
    3. 生命周期管理:有事务时,SqlSession 的提交/回滚/关闭由 Spring 控制;无事务时,SqlSessionTemplate 代理自动在方法结束后提交并关闭。"
  • 追问 3:“@Transactional 注解在什么情况下会失效?”

    高分回答

    "@Transactional 失效的常见场景有 6 种:

    1. 方法非 public:Spring AOP 代理只能拦截 public 方法;
    2. 同类内部调用this.method() 绕过代理对象,AOP 拦截器不生效;
    3. 异常被吞掉:try-catch 捕获异常未重新抛出,Spring 感知不到异常;
    4. 异常类型不匹配:默认只回滚 RuntimeException 和 Error,Checked Exception 需配置 rollbackFor
    5. 数据库引擎不支持:如 MyISAM 不支持事务;
    6. 多数据源未指定事务管理器@Transactional 默认使用主事务管理器,其他数据源需显式指定。
      排查时可通过 TransactionSynchronizationManager.isActualTransactionActive() 判断当前是否处于事务中。"
  • 追问 4:“MyBatis 的 MANAGED 事务和 JDBC 事务有什么区别?什么时候用 MANAGED?”

    高分回答

    "两者的核心区别在于 事务控制权

    • JDBC 事务JdbcTransactioncommit() 调用 Connection.commit()rollback() 调用 Connection.rollback(),MyBatis 自己控制事务生命周期。适用于独立使用 MyBatis 的场景。
    • MANAGED 事务ManagedTransactioncommit()rollback()空实现,仅负责关闭 Connection。事务完全交给外部容器(如 Spring 的 DataSourceTransactionManager、Java EE 容器)管理。
      当 MyBatis 整合 Spring 时,必须使用 MANAGED 模式(或 SpringManagedTransaction),因为 Spring 需要通过 DataSourceTransactionManager 统一控制事务。如果误用 JDBC 模式,Spring 和 MyBatis 会各自管理事务,导致冲突。"
  • 追问 5:“Spring Boot 中,没有 @Transactional 的 Mapper 方法会自动提交吗?为什么?”

    高分回答

    "是的,会自动提交。在 Spring Boot 中,即使没有 @Transactional,MyBatis 的每次 Mapper 操作也会自动提交。这不是 MyBatis 原生 autoCommit=true 的行为,而是 SqlSessionTemplate 的代理逻辑:

    1. SqlSessionInterceptor.invoke() 执行完 SQL 后,调用 SqlSessionUtils.isSqlSessionTransactional() 检查当前是否有 Spring 事务;
    2. 如果返回 false(无事务),代理会自动调用 sqlSession.commit(true) 强制提交;
    3. 然后关闭 SqlSession,归还连接。
      所以无 @Transactional 时,每条 SQL 都是一个独立事务,立即提交。这也是 Spring Boot 中’自动提交’的真正含义——不是 JDBC 层面的 autoCommit,而是 Spring 为每个操作开启并立即提交的独立事务。"
  • 追问 6:“如果让你设计一个跨服务的事务方案(如订单服务调用库存服务),MyBatis 层面要注意什么?”

    高分回答

    "跨服务事务不能依赖单数据库的 @Transactional,需要考虑分布式事务。MyBatis 层面的注意事项:

    1. 本地事务保证原子性:每个服务内部的操作仍用 @Transactional 保证本地原子性;
    2. 分布式事务选型
      • Seata AT 模式:对业务侵入最小,MyBatis 无需改动,Seata 通过代理 DataSource 拦截 SQL 生成 Undo Log;
      • TCC 模式:业务侵入大,需实现 Try/Confirm/Cancel 接口;
      • Saga 模式:长事务场景,通过状态机或事件驱动补偿;
      • 最终一致性:MQ 异步通知 + 本地消息表,适合对实时性要求不高的场景。
    3. MyBatis 配置:如果使用 Seata,需确保 SqlSessionFactoryBean 的 DataSource 被 Seata 代理(DataSourceProxy),否则 Seata 无法拦截 SQL;
    4. 避免长事务:分布式事务中,本地事务应尽量短,减少全局锁持有时间;
    5. 幂等性设计:所有接口必须支持幂等调用,防止网络超时导致的重复执行。"
8. 方案选型速查表
业务场景推荐方案核心理由
独立使用 MyBatis(无 Spring)JDBC 手动事务openSession() + commit()/rollback()
Spring 单体应用@Transactional 声明式事务开发效率高,AOP 自动管理
复杂事务逻辑(条件回滚)TransactionTemplate 编程式事务精细控制事务边界
跨服务分布式事务Seata AT + @Transactional对业务侵入小,MyBatis 无感知
高并发短事务@Transactional(readOnly = true)只读优化,减少锁竞争
批量数据导入编程式事务 + 分页批量提交避免长事务导致连接池耗尽

💡 面试官想要的满分总结

MyBatis 的事务管理不是简单的 commit()/rollback(),而是涉及 TransactionFactory 抽象、Executor 委托、Spring AOP 代理、ThreadLocal 连接绑定 的多层协作体系。

原生模式下,JdbcTransactionFactory 创建 JdbcTransaction,通过 Connection 控制事务,需手动管理。Spring 整合后,SqlSessionTemplate 通过 JDK 动态代理 实现线程安全,SpringManagedTransaction 将事务委托给 DataSourceTransactionManager,通过 TransactionSynchronizationManager 的 ThreadLocal 确保同一事务复用同一 Connection。

理解 Spring Boot 中"自动提交"的本质很重要:无 @Transactional 时,SqlSessionTemplate 代理会在每个 Mapper 操作后自动提交,这不是 JDBC 的 autoCommit,而是 Spring 为每个操作开启的独立事务。生产环境中要警惕 @Transactional 失效的 6 大场景(非 public、内部调用、异常被吞、异常类型不匹配、引擎不支持、多数据源未指定),以及 长事务风险(远程调用、大批量操作不应在事务内执行)。

最后记住:事务管理器的 DataSource 必须与 SqlSessionFactory 的 DataSource 是同一个,否则事务管理器无法感知 MyBatis 的连接,导致事务完全失效——这是生产环境中最隐蔽也最常见的配置错误。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AI人工智能+电脑小能手

若对您有所帮助,请点点关注哟~

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

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

打赏作者

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

抵扣说明:

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

余额充值