ssm合集(spring)---006

5 Spring 事务
5.1Spring 的事务管理
         事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,
Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
Spring 中通常可以通过以下两种方式来实现对事务的管理:
1 )使用 Spring 的事务注解管理事务
2 )使用 AspectJ AOP 配置管理事务
5.2Spring 事务管理 API
Spring 的事务管理,主要用到两个事务相关的接口。
1 ) 事务管理器接口 ( 重点 )
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回
滚,及获取事务的状态信息。

A 、常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类:
DataSourceTransactionManager :使用 JDBC MyBatis 进行数据库操作时使用。
HibernateTransactionManager :使用 Hibernate 进行持久化数据时使用。
B Spring 的回滚方式 ( 理解 )
Spring 事务的默认回滚方式是: 发生运行时异常和 error 时回滚,发生受查 ( 编译 ) 异常时
提交 。不过,对于受查异常,程序员也可以手工设置其回滚方式。
C 、 回顾错误与异常 ( 理解 )

Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类 ( 或其子类之一 )
的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。
Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError
ThreadDeath NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出)
的, JVM 一般会终止线程。
程序在编译和运行时出现的另一类错误称之为异常,它是 JVM 通知程序员的一种方式。
通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。
异常分为运行时异常与受查异常。
运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如,
NullPointerException ArrayIndexOutOfBoundsException IllegalArgumentException 等均属于
运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代
码编写足够仔细,程序足够健壮,运行时异常是可以避免的。
受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,
则无法通过编译。如 SQLException ClassNotFoundException IOException 等都属于受查异常。
RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception
的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的
RuntimeException 的子类,那么定义的就是受查异常。
2 ) 事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、
事务传播行为、事务默认超时时限,及对它们的操作。

A 、定义了五个事务隔离级别常量 ( 掌握 )
这些常量均是以 ISOLATION_ 开头。即形如 ISOLATION_XXX
DEFAULT 采用 DB 默认的事务隔离级别。 MySql 的默认为 REPEATABLE_READ Oracle
默认为 READ_COMMITTED
READ_UNCOMMITTED :读未提交。未解决任何并发问题。
READ_COMMITTED :读已提交。解决脏读,存在不可重复读与幻读。
REPEATABLE_READ :可重复读。解决脏读、不可重复读,存在幻读
SERIALIZABLE :串行化。不存在并发问题。
B 、 定义了七个事务传播行为常量 ( 掌握 )
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情
况。如, A 事务中的方法 doSome() 调用 B 事务中的方法 doOther() ,在调用执行期间事务的
维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
a PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事
务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
如该传播行为加在 doOther() 方法上。若 doSome() 方法在调用 doOther() 方法时就是在事
务内运行的,则 doOther() 方法的执行也加入到该事务内执行。若 doSome() 方法在调用
doOther() 方法时没有在事务内执行,则 doOther() 方法会创建一个事务,并在其中执行。

b PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

c PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

C 、 定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限, sql 语句的执行时长。
注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该
值一般就使用默认值即可。
5.3 程序举例环境搭建
举例:购买商品 trans_sale 项目
本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。
实现步骤:
Step0 :创建数据库表
创建两个数据库表 sale , goods
sale 销售表

Step1: maven 依赖 pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.bjpowernode</groupId>
  <artifactId>ch10-spring-trans-aspectj</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--做spring事务用到的-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
    <!--阿里公司的数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
    <!--aspectj的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>

  <build>
    <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
    <!--指定jdk的版本-->
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
Step2 :创建实体类
创建实体类 Sale Goods
public class Sale {
    private Integer id;
    private Integer gid;
    private Integer nums;
}
public class Goods {
    private String name;
    private Integer id;
    private Integer amount;
    private Float price;
}
Step3 :定义 dao 接口
定义两个 dao 的接口 SaleDao , GoodsDao
package com.zsz.dao;

import com.zsz.domain.Sale;

public interface SaleDao {
    //增加销售记录
    int insertSale (Sale sale);

}
Step4 :定义 dao 接口对应的 sql 映射文件
SaleDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zsz.dao.SaleDao">


    <insert id="insertSale">
        insert into sale(gid,nums) values (#{gid},#{nums})
    </insert>


</mapper>

GoodsDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zsz.dao.GoodsDao">

   <update id="updateGoods">
        update  goods set amount=amount-#{amount} where id=#{id}
   </update>
    <select id="selectGoods" resultType="com.zsz.domain.Goods">
        select id,name,amount,price from goods where id=#{gid}
    </select>

</mapper>

Step5 :定义异常类
定义 service 层可能会抛出的异常类 NotEnoughException
package com.zsz.excep;

//自定义的运行时异常
public class NotEnoughException extends RuntimeException {
    public NotEnoughException() {
        super();
    }

    public NotEnoughException(String message) {
        super(message);
    }
}
Step6 :定义 Service 接口
定义 Service 接口 BuyGoodsService
package com.zsz.service;

public interface BuyGoodsService {
    //购买商品的方法,其中goodsId:光谱卖商品的编号,nums:购买商品的数量。
    void buy(Integer goodsId,Integer nums);
}

Step7 :定义 service 的实现类
定义 service 层接口的实现类 BuyGoodsServiceImpl
1)
类定义
public class BuyGoodsServiceImpl implements BuyGoodsService {
2)
Dao 属性
    private SaleDao saleDao;
    private GoodsDao goodsDao;

      public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
3)
Buy 方法
 @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("===buy()方法的开始===");

       //记录销售信息,向sale表添加记录
        Sale sale=new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);



        //更新库存
        Goods goods=goodsDao.selectGoods(goodsId);
        if (goods==null){
            //商品不存在
            throw new NullPointerException("编号是:"+goodsId+"的商品不存在");

        }else if (goods.getAmount()<nums){
            throw new NotEnoughException("编号是:\"+goodsId+\"的商品库存不足");

        }

        //修改库存了
        Goods buyGoods=new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);

        System.out.println("===buy()方法的完成===");


    }

Step8 :修改 Spring 配置文件内容
声明 Mybatis 对象
<context:property-placeholder location="classpath:jdbc.properties" />

    <!--声明数据源DataSource, 作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库信息 -->
        <!--    使用属性配置文件中的数据,语法 ${key} -->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
        SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。

    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.zsz.dao"/>
    </bean>
声明业务层对象
<!--声明service-->
    <bean id="buyService" class="com.zsz.service.impl.BuyGoodsServiceImpl">
         <property name="goodsDao" ref="goodsDao"></property>
        <property name="saleDao" ref="saleDao"></property>
    </bean>
Step9 :定义测试类
定义测试类 MyTest 。现在就可以在无事务代理的情况下运行了。
package com.zsz;

import com.zsz.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class myTest {
    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ac=new ClassPathXmlApplicationContext(config);
        BuyGoodsService buyService= (BuyGoodsService) ac.getBean("buyService");

        buyService.buy(1001,10);
    }
}

5.4 使用 Spring 的事务注解管理事务 ( 掌握 )
通过 @Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示:
propagation 用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为
Propagation.REQUIRED
isolation 用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为
Isolation.DEFAULT
readOnly 用于设置该方法对数据库的操作是否是只读的。该属性为 boolean ,默认值
false
timeout 用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int ,默认值为
-1 ,即没有时限。
rollbackFor 指定需要回滚的异常类。类型为 Class[] ,默认值为空数组。当然,若只有
一个异常类时,可以不使用数组。
rollbackForClassName 指定需要回滚的异常类类名。类型为 String[] ,默认值为空数组。
当然,若只有一个异常类时,可以不使用数组。
noRollbackFor 指定不需要回滚的异常类。类型为 Class[] ,默认值为空数组。当然,若
只有一个异常类时,可以不使用数组。
noRollbackForClassName 指定不需要回滚的异常类类名。类型为 String[] ,默认值为空
数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是, @Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public
方法,如果加上了注解 @Transactional ,虽然 Spring 不会报错,但不会将指定事务织入到该
方法中。因为 Spring 会忽略掉所有非 public 方法上的 @Transaction 注解。
@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤:
复制 trans_sale 项目,新项目 trans_sale_annotation
1.
声明事务管理器
 <!--使用spring的事务处理-->
    <!--1.声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
2.
开启注解驱动
<!--2.开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象

        transaction-manager:事务管理其对象的id

    -->

    <tx:annotation-driven transaction-manager="transactionManager"/>
transaction-manager :事务管理器 bean id
3. 业务层 public 方法加入事务属性
package com.zsz.service.impl;

import com.zsz.dao.GoodsDao;
import com.zsz.dao.SaleDao;
import com.zsz.domain.Goods;
import com.zsz.domain.Sale;
import com.zsz.excep.NotEnoughException;
import com.zsz.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class BuyGoodsServiceImpl implements BuyGoodsService {
    private SaleDao saleDao;
    private GoodsDao goodsDao;


    /**
     *isolation:表示事物的隔离级别
     *rollbackFor:表示发生指定的异常一定回滚。
     *
     *            1)处理逻辑是:spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中,
     *               如果异常在rollbackFor的列表中,不管是神魔类型的异常,一定回滚。
     *            2)如果你的跑出的异常不在rollbackFor列表中,spring会判断异常是不是RunTimeException,
     *               如果是那么一定回滚。
     */
//    @Transactional(
//            propagation = Propagation.REQUIRED,
//            isolation = Isolation.DEFAULT,
//            readOnly = false,
//            rollbackFor = {
//                    NotEnoughException.class,
//                    NullPointerException.class
//
//            }
//    )

    //使用的是失误控制的默认值,默认的传播行为是REQUIRED,默认你的隔离级别DEFAULT
    //默认的抛出运行时异常,回滚事务。
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("===buy()方法的开始===");

       //记录销售信息,向sale表添加记录
        Sale sale=new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);



        //更新库存
        Goods goods=goodsDao.selectGoods(goodsId);
        if (goods==null){
            //商品不存在
            throw new NullPointerException("编号是:"+goodsId+"的商品不存在");

        }else if (goods.getAmount()<nums){
            throw new NotEnoughException("编号是:\"+goodsId+\"的商品库存不足");

        }

        //修改库存了
        Goods buyGoods=new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);

        System.out.println("===buy()方法的完成===");


    }

    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
}
5.5 使用 AspectJ AOP 配置管理事务 ( 掌握 )
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类
较多,配置文件会变得非常臃肿。
使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法
很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。
Step1 :复制项目
复制 trans_sale 项目,并重命名为 trans_sal_aspectj 。在此基础上修改。
Step2 maven 依赖 pom.xml
新加入 aspectj 的依赖坐标
 <!--aspectj的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
Step3 :在容器中添加事务管理器
 <!--声明式事务处理:和源代码完全分离的-->
    <!--1.声明事务管理器对象-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="myDataSource" />
    </bean>
Step4 :配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。
<!--2.声明业务方法它的事务属性(隔离级别,传播行为,超时时间)
          id:自定义名称,表示 <tx:advice> 和 </tx:advice>之间的配置内容的
          transaction-manager:事务管理器对象的id
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!--tx:attributes:配置事务属性-->
        <tx:attributes>
            <!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
                name:方法名称,1)完整的方法名称,不带有包和类。
                              2)方法可以使用通配符,* 表示任意字符
                propagation:传播行为,枚举值
                isolation:隔离级别
                rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚
            -->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/>

            <!--使用通配符,指定很多的方法-->
            <tx:method name="add*" propagation="REQUIRES_NEW" />
            <!--指定修改方法-->
            <tx:method name="modify*" />
            <!--删除方法-->
            <tx:method name="remove*" />
            <!--查询方法,query,search,find-->
            <tx:method name="*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
Step5 :配置增强器
指定将配置好的事务通知,织入给谁。
<!--配置aop-->
    <aop:config>
        <!--配置切入点表达式:指定哪些包中类,要使用事务
            id:切入点表达式的名称,唯一值
            expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象

            com.bjpowernode.service
            com.crm.service
            com.service
        -->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

        <!--配置增强器:关联adivce和pointcut
           advice-ref:通知,上面tx:advice哪里的配置
           pointcut-ref:切入点表达式的id
        -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
    </aop:config>
Step6 :修改测试类
测试类中要从容器中获取的是目标对象。
package com.zsz;

import com.zsz.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class myTest {
    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ac=new ClassPathXmlApplicationContext(config);
        BuyGoodsService buyService= (BuyGoodsService) ac.getBean("buyService");
        System.out.println("buyService是代理:"+buyService.getClass().getName());

        buyService.buy(1001,10);
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值