SSM,Mybatis深入了解

Mybatis

1.简介

1.数据持久化

  • 持久化就是将程序的数据在持久化状态和瞬时状态转化的过程

数据库JDBC,IO文件持久化,比如生活中的食物冷藏

2.持久层

Dao,Service,Controller

  • 完成持久化工作的代码块

  • 层界限明显

3.为什么需要Mybatis

  • 帮助数据存入数据库

  • 将JDBC代码简化,框架,自动化

优点:

  • sql和代码的分离,提高了可维护性
  • 提供映射标签,支持对象和数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql

2.第一个Mybaits程序

1.搭建环境

  • 创建JDBC

  • 创建Maven或gradle

  • 删除src(将它变成父项目)

  • 导入依赖 mysql、mybatis、junit

  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>x.x.x</version>
  </dependency>

2.创建一个模块(子项目)

  • 编写mybatis的核心配置文件

    ​ mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
      <!-- JDBC驱动 -->
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/sys001?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=false&verifyServerCertificate=false&autoReconnect=true&autoReconnectForPools=true&allowMultiQueries=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
<!-- 每一个Mapper.xml都需要在Mybatis核心配置文件中注册! 是路径用/ -->
  <mappers>
    <mapper resource="mybatis.xml"/>
  </mappers>
</configuration>

编写工具类Utils

在这里插入图片描述

3.编写代码

实体类

 @Data //提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
 @Builder //为你的类生成相对略微复杂的构建器API。
 @NoArgsConstructor //为类提供一个无参的构造方法
 @AllArgsConstructor //为类提供一个全参的构造方法
 
 public class SysJob implements Serializable {
     @ApiModelProperty("岗位ID") //表示对model属性的说明或者数据操作更改
     private String jobId;
     
     @ApiModelProperty("岗位名称")
     private String jobName;
 
     @ApiModelProperty("岗位类型")
     private String jobType;
 
     @ApiModelProperty("岗位状态")
     private String jobStatus;
 
     @ApiModelProperty("创建时间")
     private Date createTime;
 
     @ApiModelProperty("编辑时间")
     private Date editTime;
 
     @ApiModelProperty("编辑用户")
     private String editUser;
 
     private static final long serialVersionUID = 1L;
 
 }

Dao接口(Mapper映射器)

  public interface BaseMapper<T> {
      int deleteByPrimaryKey(Object var1);
  
      int insert(T var1);
  
      int insertSelective(T var1);
  
      int updateByPrimaryKeySelective(T var1);
  
      int updateByPrimaryKey(T var1);
  
      T selectByPrimaryKey(Object var1);
  
      int selectCount(Map<String, Object> var1);
  
      List<Map<String, Object>> selectList(Map<String, Object> var1);
  
      int selectCount(PageDTO var1);
  
      List<Object> selectList(PageDTO var1);
  
      List<T> selectAllList(T var1);
  
      List<Map<String, Object>> selectByMap(Map<String, Object> var1);
  }
  public interface SysJobMapper extends BaseMapper<SysJob>{
  
  }

接口实现类xml文件(比如UserMapper)

<?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">
  
<!-- namespace = 绑定一个Dap/Mapper接口 -->
<mapper namespace="cn.bywin.social.dao.SysJobMapper">
    
<!-- sql语句 id=方法名 -->
<!-- 结果集写权限命名 resultType(单个)  resultMap(集合) -->
<!-- 对象中的属性可以直接取出 -->
    
    <select id="selectAllList" resultMap="BaseResultMap" parameterType="cn.bywin.social.model.SysJob" >
        select 
        <include refid="Base_Column_List" />
        from sys_job t 
        <include refid="Condition" />
    </select>
</mapper>

4.增删改查

1.编写mapper接口

2.编写对应的mapper中的测试语句

  • namespace

    —包名要和Dao/Mapper接口的包名一致

  • 语句参数

    —id: 对应的namespace中的方法名

    —resultType: sql语句的返回值

    —parameterTyoe: 参数类型

3.万能的Map

————————————————(多参数用)

  • 不需要写全每个字段,只要字段的属性不是Not Null都可以不传入
  • 是对象类(User)的话需要与对象中的属性一一对应
int selectCount(Map<String, Object> var1);

List<Map<String, Object>> selectList(Map<String, Object> var1);
<sql id="Condition">
    <where>
        <if test="jobName != null">
            and job_name like '%${jobName}%'
        </if>
        <if test="jobType != null">
            and job_type like '%${jobType}%'
        </if>
        <if test="editUser != null">
            and edit_user like '%${editUser}%'
        </if>
    </where>
</sql>


<select id="selectList" resultType="cn.bywin.common.model.DataMap" parameterType="Map" >
    select t.* from sys_job t 
    
    <!-- include是将代码块打包 -->
    <include refid="Condition" />
    <include refid="Pagination" />
</select>

4.模糊查询

  • java代码执行和,传递通配符 $ {value} 用美元符$
  • 其余用 #{value} 来传递参数

5.核心配置文件

1.环境配置(environments)

  • 可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境

  • 默认使用的环境 ID(比如:default=“development”)。

  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。

  • 事务管理器的配置(比如:type=“JDBC/MANAGED” 默认是JDBC)。

  • 数据源的配置(比如:type=“POOLED” 连接池)。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

2.数据源(dataSource)

driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/sys001?${spring.datasource.driverParams}
username: root
password: 123456
  • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
  • url – 这是数据库的 JDBC URL 地址。
  • username – 登录数据库的用户名。
  • password – 登录数据库的密码。

3.属性(properties)

这些属性可以在外部进行配置(引入外部配置文件),并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

<!-- 注意:每个标签必须按顺序写 properties在最上面-->
<properties resource="org/mybatis/example/config.properties">  
	<property name="username" value="dev_user"/>  
	<property name="password" value="F2Fa3!33TYyg"/>
</properties>

4.类型别名(typeAliases)

1.类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>  
	<typeAlias alias="sysjob" type="cn.bywin.social.model.SysJob"/>
</typeAliases>
<!-- parameterType="cn.bywin.social.model.SysJob" -> parameterType="SysJob" -->
<select id="selectAllList" resultMap="BaseResultMap" parameterType="SysJob" >    
	select    
	<include refid="Base_Column_List" />    
	from sys_job t     
	<include refid="Condition" />
</select>

2.也可以指定一个包名,MyBatis 会扫描包中的的 Java Bean,使用默认别名(首字母小写) 比如:

<typeAliases>  
	<package name="cn.bywin.social.model"/>
</typeAliases>
<!-- parameterType="cn.bywin.social.model.SysJob" -> parameterType="sysJob" -->
<select id="selectAllList" resultMap="BaseResultMap" parameterType="sysjob" >    
	select     
	<include refid="Base_Column_List" />    
	from sys_job t     
	<include refid="Condition" />
</select>

5.设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

在这里插入图片描述

6.映射器(Mappers)

定义SQL映射语句

方法一【推荐】:使用相对路径 resource=

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

方法二:使用class文件绑定注册 class=

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="cn.bywin.dao.UserMapper"/>
</mappers>
  • 接口和它的Mapper配置文件必须同名
  • 接口和它的Mapper配置文件必须在同一个包

方法三:使用扫描包进行注入绑定

<mappers>
  <package name="cn.bywin"/>
</mappers>
  • 接口和它的Mapper配置文件必须同名
  • 接口和它的Mapper配置文件必须在同一个包

7.生命周期和作用域

作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题

在这里插入图片描述

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory,就不需要它了
  • 局部变量

SqlSessionFactory:

  • 可以想象成:数据库连接池
  • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域
  • 使用单例模式或者静态单例模式

SqlSession

  • 连接到连接池的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完之后需要关闭请求,否则资源被占用

6.ResultMap结果映射

解决实体类中属性名和数据库中字段名不一致的问题

  • 1.定义结果集映射
<!-- 定义一种结果集 -->    
<resultMap id="BaseResultMap" type="SysJob" >
<!-- column=字段名 property=属性名  -->    
	<id column="job_id" property="jobId" jdbcType="VARCHAR" />    
	<result column="job_name" property="jobName" jdbcType="VARCHAR" />    
	<result column="job_type" property="jobType" jdbcType="VARCHAR" />    
	<result column="job_status" property="jobStatus" jdbcType="VARCHAR" />    
	<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />    
	<result column="edit_time" property="editTime" jdbcType="TIMESTAMP" />    
	<result column="edit_user" property="editUser" jdbcType="VARCHAR" />
</resultMap>
  • 2.resultMap=“BaseResultMap” 对应定义的id
<select id="selectAllList" resultMap="BaseResultMap" parameterType="SysJob" >    
	select     
	<include refid="Base_Column_List" />    
	from sys_job t     
	<include refid="Condition" />
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素。

  • resultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

  • 只需要写字段名和属性名不一样的就可

7.日志工厂

如果一个数据库操作出现了异常,我们需要用日志排错

以前:sout、debug

在这里插入图片描述

  • SLF4J
  • LOG4J 【掌握】(需要导包)
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】(标准日志不需要导包)
  • NO_LOGGING

在Mybatis中具体使用哪个日志实现,在设置中设定!

<settings><!-- 标准日志实现 -->    
	<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

8.分页

  • 减少数据的处理量

使用Limit分页

select * form user limit startIndex,pageSize

9.注解开发

9.1、面向接口编程

  • 根本原因:解耦
  • 接口:定义与实现的分离

9.2、注解(简单语句)

package org.mybatis.example;
public interface BlogMapper {  
	@Select("SELECT * FROM User WHERE id = #{id}")  
	Blog selectBlog(int id);
}

10.Lombok

使用步骤

  • 在IDEA中安装lombok插件(Setting->Plugins->Lombok)
  • 在项目中导入Lombok包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>    
	<groupId>org.projectlombok</groupId>    
	<artifactId>lombok</artifactId>    
	<version>1.18.20</version>    
	<scope>provided</scope>
</dependency>
  • 在实体类上加注解即可!
@Getter
@Setter
@ToString
@EqualsAndHashCode
@AllArgsConstructor  //为类提供一个全参的构造方法    
@NoArgsConstructor  //为类提供一个无参的构造方法    
@Data  //提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法    
@Builder  //为你的类生成相对略微复杂的构建器API。

11.关系查询

1.多对一

嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用

对象:association

  • 一个复杂类型的关联;许多结果将包装成这种类型

集合:collection

  • 一个复杂类型的集合

在这里插入图片描述

  • 复杂嵌套查询(子查询)

在这里插入图片描述

  • 连表查询——按照结果嵌套查询

在这里插入图片描述

2.一对多

  • 复杂嵌套查询(子查询)

在这里插入图片描述

  • 连表查询——按照结果嵌套查询

在这里插入图片描述

3、多对多

需要使用中间表,中间表中包含各自的主键,在中间表中是外键。

4.小结

  • 关联 - association 【多对一】
  • 集合 - collection 【一对多】
  • javaType & ofType
    • javaType 用来指定实体类中属性的类型
    • ofType 用来指定映射到List或集合中的pojo类型(泛型中的约束类型)

12.动态SQL

动态SQL就是根据不同的条件生成不同的SQL语句

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

UUID生成随机的ID

在这里插入图片描述

1、IF

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分

<select id="findActiveBlogLike"     resultType="Blog">  
	SELECT * FROM BLOG  
	<where>    
		<if test="state != null">         
			state = #{state}    	
		</if>    
		<if test="title != null">        
			AND title like #{title}    
		</if>    
		<if test="author != null and author.name != null">       
			AND author_name like #{author.name}    
		</if>  
	</where>
</select>

2、choose(when、otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<select id="findActiveBlogLike"     resultType="Blog">  
	SELECT * FROM BLOG     
	<where>      
		<choose>    	
			<when test="title != null">   		   		
				AND title like #{title}  		
			</when>   		 
			<when test="author != null and author.name != null">   		 	 	
				AND author_name like #{author.name} 		 
			</when> 		 
			<otherwise>  			    
				AND featured = 1 		 
			</otherwise>	  
		</choose>    
	</where>
</select>

3、trim(where、set)

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

<!-- prefix前缀  suffix后缀 prefixOverrides定义前缀  suffixOverrides定义后缀-->
insert into sys_job
        <trim prefix="(" suffix=")" suffixOverrides="," >
            <if test="jobId != null" >
                job_id,
            </if>
            <if test="jobName != null" >
                job_name,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides="," >
            <if test="jobId != null" >
                #{jobId,jdbcType=VARCHAR},
            </if>
            <if test="jobName != null" >
                #{jobName,jdbcType=VARCHAR},
            </if>
        </trim>

可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素

4、Foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。

5、SQL片段打包

1、使用SQL标签抽得公共部分

<sql id="Base_Column_List" >
    job_id, job_name, job_type, job_status, create_time, edit_time, edit_user
</sql>
<!-- 在需要用到的地方使用 <include refid="" /> -->

2、在需要的地方使用include标签引用

<select id="selectAllList" resultMap="BaseResultMap" parameterType="cn.bywin.social.model.SysJob" >
    select 
    <include refid="Base_Column_List" />
    from sys_job t 
    <include refid="Condition" />
</select>

13、缓存

1、简介

查询 :连接数据库,要耗资源!
	将一次查询的结果,给他暂存在一个可以直接得到的地方!-->内存 :缓存

当我们再次查询相同数据的时候,直接走缓存,而不用连接数据库

1、什么是缓存

  • 存在内存中的临时数据
  • 将用户经常查询的数据放再缓存中,再次查询走缓存,从而提高查询效率,解决了高并发系统的性能问题

2、为什么使用缓存

  • 减少和数据库的交互次数,减少系统开销,提高系统效率

3、什么要的数据能使用缓存

  • 经常查询并不经常改变的数据

2、Mybatis缓存

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。

  • MyBatis系统中默认定义了:一级缓存和二级缓存

    • 默认一级缓存开启(SqlSession级别的缓存)

    • 二级缓存需要手动开启,是基于namespace级别的缓存

    • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存

3、一级缓存

  • 一级缓存也叫本地缓存
    • 与数据库同一次会话期间查询到的数据会放再本地缓存中

4、二级缓存

  • 二级缓存也叫全局缓存

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>
  • 工作机制

    • 一个会话查询一条数据,这个数据放入一级缓存
    • 当会话关闭,一级缓存没了后,将被保存入二级缓存
    • 新的会话查询可以从二级缓存获得内容
    • 不同的mapper查出的数据会放再自己对应的缓存(map)中

步骤

1.开启全局缓存

<setting name="cacheEnable" value="true"/>

2、在要使用二级缓存的Mapper中开启

<!-- 在当前Mapper.xml中使用二级缓存 -->
<cache/>

也可以自定义参数

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

5、缓存原理

在这里插入图片描述

6、自定义缓存(ehcache)

1、导包

<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.9.4</version>
</dependency>

除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

2、ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shiroCache">

    <diskStore path="java.io.tmpdir"/>
    <!--
       name:缓存名称。
       maxElementsInMemory:缓存最大数目
       maxElementsOnDisk:硬盘最大缓存个数。
       eternal:对象是否永久有效,一但设置了,timeout将不起作用。
       overflowToDisk:是否保存到磁盘,当系统当机时
       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
       diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
        clearOnFlush:内存数量最大时是否清除。
         memoryStoreEvictionPolicy:
            Ehcache的三种清空策略;
            FIFO,first in first out,这个是大家最熟的,先进先出。
            LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
            LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="0"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />

    <!-- 登录记录缓存锁定5分钟 -->
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>
</ehcache>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值