MyBatis-01 MyBatis入门篇

Mybatis概述

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

官方Github地址为 :https://github.com/mybatis ,在官方Github中可以看到Mybatis的多个子项目,后续摸索。

Mybatis是一款优秀的支持自定义SQL查询、存过过程和高级映射的持久层框架,消除了几乎所有的JDBC代码和参数的手动设置以及结果的检索。

Mybatis可以使用XML或者注解进行配置和映射,Mybatis通过将参数映射到配置的SQL形成最终执行的SQL,最后将SQL的结果映射成Java对象。

和其他ORM框架不同,Mybatis并没有将Java对象和数据库表关联起来,而是将Java方法与SQL语句关联

Mybatis支持声明式数据缓存。Mybatis提供了默认情况下基于Java HashMap的缓存实现,以及用于与OSCache、Ehcache、Hazelcast和Memcached连接的默认连接器,同时还提供了API供其他缓存实现使用。


演示数据

MySql数据库

-- ----------------------------
-- Create database named artisan
-- ----------------------------
CREATE  DATABASE  artisan DEFAULT  CHARACTER  SET  utf8  COLLATE  utf8_general_ci;  

-- ----------------------------
-- use artisan
-- ----------------------------

use artisan ;


-- ----------------------------
-- Table structure for country
-- ----------------------------
DROP TABLE IF EXISTS `country`;
CREATE TABLE `country` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `countryname` varchar(255) DEFAULT NULL,
  `countrycode` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of country
-- ----------------------------
INSERT INTO `country` VALUES ('1', '中国', 'CN');
INSERT INTO `country` VALUES ('2', '美国', 'US');
INSERT INTO `country` VALUES ('3', '俄罗斯', 'RU');
INSERT INTO `country` VALUES ('4', '英国', 'GB');
INSERT INTO `country` VALUES ('5', '法国', 'FR');
commit;

原生JDBC问题总结

我们先总结先原生JDBC操作数据库的基本步骤

1. 加载数据库驱动
2. 创建并获取数据库链接
3. 创建jdbc statement对象
4. 设置sql语句
5. 设置sql语句中的参数(使用preparedStatement)
6. 通过statement执行sql并获取结果
7. 解析处理sql执行结果
8. 释放资源(resultSet、preparedstatement、connection)

粗略代码如下所示

package com.artisan.mybatis.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.log4j.Logger;

import com.artisan.mybatis.simple.model.Country;

/**
 * 
 * 
 * @ClassName: CountryInfoByJdbc
 * 
 * @Description: JDBC连接访问数据库,方便与Mybatis对比
 * 
 * @author: Mr.Yang
 * 
 * @date: 2018年4月10日 上午1:12:11
 */
public class CountryInfoByJdbc {
    private static final Logger logger = Logger.getLogger(CountryInfoByJdbc.class);
    private static final String QUERY_COUNTRY_BY_CODE = "select id ,countryname ,countrycode from country where countrycode = ? ";

    public static void main(String[] args) {
        // 数据库连接
        Connection connection = null;
        // 预编译的PreparedStatement,提高数据库效率
        PreparedStatement preparedStatement = null;
        // 结果集
        ResultSet resultSet = null;
        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 通过数据库驱动管理类获取数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/artisan", "root", "root");
            // 获取preparedStatement
            preparedStatement = connection.prepareStatement(QUERY_COUNTRY_BY_CODE);
            // 设置参数
            preparedStatement.setString(1, "CN");
            // 通过preparedStatement执行SQL
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果
            while (resultSet.next()) {
                Country country = new Country();
                country.setId(Long.valueOf(resultSet.getString("id")));
                country.setCountryname(resultSet.getString("countryname"));
                country.setCountrycode(resultSet.getString("countrycode"));
                logger.info("通过jdbc获取的结果:" + country);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭连接,释放资源
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


    }

}

日志输出如下:

通过jdbc获取的结果:Country [id=1, countryname=中国, countrycode=CN]

符合结果

mysql> use artisan;
Database changed
mysql> select id ,countryname ,countrycode from country where countrycode = 'CN';
+----+-------------+-------------+
| id | countryname | countrycode |
+----+-------------+-------------+
|  1 | 中国        | CN          |
+----+-------------+-------------+
1 row in set

mysql> 

原生JDBC存在的问题

1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

2. Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

3. 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。

4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。


Mybatis架构

这里写图片描述

1、mybatis全局配置文件,SqlMapConfig.xml(名称可任意),配置mybatis的运行环境等信息。mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。


入门示例

创建Maven项目

项目结构如下:

这里写图片描述

完整pom.xml 如下

<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.artisan.mybatis</groupId>
    <artifactId>MyBatisMaster</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>MyBatisMaster</name>
    <url>http://maven.apache.org</url>

    <properties>
        <java.version>1.6</java.version>
        <junit.version>4.12</junit.version>
        <mybatis.version>3.3.0</mybatis.version>
        <mysql.version>5.1.38</mysql.version>
        <slf4j.version>1.7.12</slf4j.version>
        <log4j.version>1.2.17</log4j.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
    </dependencies>


    <build>
        <finalName>SpringMaster</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
                <configuration>
                    <parallel>methods</parallel>
                    <threadCount>10</threadCount>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

主要步骤:

1. 设置源代码的编码方式为UTF-8
2. 设置编译源码的JDK版本,为扩大兼容性,使用了1.6
3. 添加mybatis的依赖
4. 添加Log4J、Junit、MySql驱动的依赖

配置完成后, 工程右键-Maven-Update Project 更新外部依赖的jar包。


数据库及数据

同上,这里我使用了navicat

这里写图片描述


配置mybatis

这里我们采用最基础最常用的XML的方式进行配置

首先在src/main/resources下面创建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>
    <!-- 通过logImpl属性指定使用LOG4J输出日志,mybatis默认使用log4j作为输出日志信息。 -->
    <settings>
        <setting name="logImpl" value="LOG4J" />
    </settings>


    <!-- typeAliases元素下配置了一个包的别名,通常确定一个类的时候需要使用全限定名,
        比如 com.artisan.mybatis.simple.mapper.model.Country
    -->
    <typeAliases>
        <package name="com.artisan.mybatis.simple.model" />
    </typeAliases>

    <!-- 和spring整合后 environments配置将废除-->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理-->
            <transactionManager type="JDBC"/>
            <!-- 数据库连接池-->
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/artisan?characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/artisan/mybatis/simple/mapper/CountryMapper.xml" />
    </mappers>
</configuration>
  • settings节点通过logImpl属性指定使用LOG4J输出日志

  • typeAliases元素下面配置了一个包名,通常确定一个类的时候要使用类的全限定名称,比如com.artisan.mybatis.simple.model.Country. 为了方便使用,这里配置了包名,后续在使用类的时候就不需要写包名的部分,只写Country即可

  • environment节点主要配置了数据库连接部分、事务等

  • mappers节点配置了一个包含完整类路径的CountryMapper.xml,这是一个Mybatis的SQL语句和映射配置文件


创建实体类和Mappe.xml文件

Mybatis是一个结果映射框架,这里创建的实体类实际上是一个数据值对象(Data Value Object). 在实际应用中,一般一个表会对应一个实体,用于增删改查操作

package com.artisan.mybatis.simple.model;

/**
 * 
 * 
 * @ClassName: Country
 * 
 * @Description: Country 实体类
 * 
 * @author: Mr.Yang
 * 
 * @date: 2018年4月9日 下午5:54:29
 */
public class Country {

    /**
     * 由于java中的基本类型会有默认值,某些情况下动态sql无法实现null的情况,因此实体类中不建议使用基本类型
     */
    private Long id;
    private String countryname;
    private String countrycode;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCountryname() {
        return countryname;
    }

    public void setCountryname(String countryname) {
        this.countryname = countryname;
    }

    public String getCountrycode() {
        return countrycode;
    }

    public void setCountrycode(String countrycode) {
        this.countrycode = countrycode;
    }

    @Override
    public String toString() {
        return "Country [id=" + id + ", countryname=" + countryname + ", countrycode=" + countrycode + "]";
    }

}

在src/main/resources 创建 com/artisan/mybatis/simple/mapper目录,创建CountryMapper.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.artisan.mybatis.simple.mapper.CountryMapper">

    <!-- select节点 -->
    <select id="selectAll" resultType="Country">
        select id,countryname,countrycode from country
    </select>
</mapper>
  • namespace :命名空间,用于隔离sql语句

  • resultType:定义结果映射类型


配置Log4j以便查看Mybatis的操作过程

#全局配置
log4j.rootLogger=DEBUG, stdout

#MyBatis 日志配置
log4j.logger.com.artisan.mybatis.simple.mapper=TRACE

#控制台输出配置
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n

编写单元测试运行

package com.artisan.mybatis.simple.mapper;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.artisan.mybatis.simple.model.Country;

public class CountryMapperTest {
    private Logger logger = Logger.getLogger(CountryMapperTest.class);
    private static SqlSessionFactory sessionFactory;
    private SqlSession sqlSession;

    @Before
    public void init() {
        try {
            // 根据mybatis的配置文件创建sqlSessionFactory
            String config = "mybatis-config.xml";
            InputStream ins = Resources.getResourceAsStream(config);
            sessionFactory = new SqlSessionFactoryBuilder().build(ins);
            ins.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test() {
        // 使用SqlSessionFactory打开sqlSession
        sqlSession = sessionFactory.openSession();
        // 使用sqlSession 操作数据库
        List<Country> countryList = sqlSession.selectList("com.artisan.mybatis.simple.mapper.CountryMapper.selectAll");
        for (Country country : countryList) {
            logger.info(country);
        }

    }

    @After
    public void destory() {
        sqlSession.close();
    }
}
  • 通过Resources工具类将配置文件读入流

  • 再通过SqlSessionFactoryBuilder通过输入流来创建SqlSessionFactory工厂对象,读取配置文件中的mappers配置后会读取全部的Mapper.xml进行具体的方法解析。 在这些解析完成以后,SqlSessionFactory就包含了所有的属性和执行SQL的信息

  • 使用时通过SqlSessionFactory工厂打开一个SqlSession

  • 通过SqlSessionFactory的selectList方法查找到CountryMapper.xml中id=”selectAll”的方法,执行SQL

  • Mybatis底层使用JDBC执行SQL,获取查询结果集ResultSet后,根据resultType的配置将结果映射为Country类型的结合,返回查询结果

  • 这样就得到了查询结果countryList,遍历输出

  • 最后一定要记得关闭SqlSession。

输出结果

2018-04-13 00:46:04,582 DEBUG [main] (LogFactory.java:135) - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2018-04-13 00:46:04,819 DEBUG [main] (VFS.java:109) - Class not found: org.jboss.vfs.VFS
2018-04-13 00:46:04,819 DEBUG [main] (JBoss6VFS.java:142) - JBoss 6 VFS API is not available in this environment.
2018-04-13 00:46:04,822 DEBUG [main] (VFS.java:109) - Class not found: org.jboss.vfs.VirtualFile
2018-04-13 00:46:04,823 DEBUG [main] (VFS.java:70) - VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment.
2018-04-13 00:46:04,824 DEBUG [main] (VFS.java:84) - Using VFS adapter org.apache.ibatis.io.DefaultVFS
2018-04-13 00:46:04,825 DEBUG [main] (DefaultVFS.java:222) - Find JAR URL: file:/D:/workspace/workspace-sts/MyBatisMaster/target/classes/com/artisan/mybatis/simple/model
2018-04-13 00:46:04,826 DEBUG [main] (DefaultVFS.java:248) - Not a JAR: file:/D:/workspace/workspace-sts/MyBatisMaster/target/classes/com/artisan/mybatis/simple/model
2018-04-13 00:46:05,023 DEBUG [main] (DefaultVFS.java:102) - Reader entry: Country.class
2018-04-13 00:46:05,024 DEBUG [main] (DefaultVFS.java:113) - Listing file:/D:/workspace/workspace-sts/MyBatisMaster/target/classes/com/artisan/mybatis/simple/model
2018-04-13 00:46:05,025 DEBUG [main] (DefaultVFS.java:222) - Find JAR URL: file:/D:/workspace/workspace-sts/MyBatisMaster/target/classes/com/artisan/mybatis/simple/model/Country.class
2018-04-13 00:46:05,025 DEBUG [main] (DefaultVFS.java:248) - Not a JAR: file:/D:/workspace/workspace-sts/MyBatisMaster/target/classes/com/artisan/mybatis/simple/model/Country.class
2018-04-13 00:46:05,032 DEBUG [main] (DefaultVFS.java:102) - Reader entry: ����2;(com/artisan/mybatis/simple/model/Countryjava/lang/ObjectidLjava/lang/Long;countrynameLjava/lang/String;countrycode<init>()VCode
2018-04-13 00:46:05,033 DEBUG [main] (ResolverUtil.java:256) - Checking to see if class com.artisan.mybatis.simple.model.Country matches criteria [is assignable to Object]
2018-04-13 00:46:05,078 DEBUG [main] (LogFactory.java:135) - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
2018-04-13 00:46:05,349 DEBUG [main] (JdbcTransaction.java:138) - Opening JDBC Connection
Fri Apr 13 00:46:05 BOT 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
2018-04-13 00:46:05,864 DEBUG [main] (JdbcTransaction.java:102) - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2b2fe2f0]
2018-04-13 00:46:05,880 DEBUG [main] (BaseJdbcLogger.java:142) - ==>  Preparing: select id,countryname,countrycode from country 
2018-04-13 00:46:05,975 DEBUG [main] (BaseJdbcLogger.java:142) - ==> Parameters: 
2018-04-13 00:46:06,010 TRACE [main] (BaseJdbcLogger.java:148) - <==    Columns: id, countryname, countrycode
2018-04-13 00:46:06,011 TRACE [main] (BaseJdbcLogger.java:148) - <==        Row: 1, 中国, CN
2018-04-13 00:46:06,015 TRACE [main] (BaseJdbcLogger.java:148) - <==        Row: 2, 美国, US
2018-04-13 00:46:06,016 TRACE [main] (BaseJdbcLogger.java:148) - <==        Row: 3, 俄罗斯, RU
2018-04-13 00:46:06,016 TRACE [main] (BaseJdbcLogger.java:148) - <==        Row: 4, 英国, GB
2018-04-13 00:46:06,018 TRACE [main] (BaseJdbcLogger.java:148) - <==        Row: 5, 法国, FR
2018-04-13 00:46:06,019 DEBUG [main] (BaseJdbcLogger.java:142) - <==      Total: 5
2018-04-13 00:46:06,020  INFO [main] (CountryMapperTest.java:44) - Country [id=1, countryname=中国, countrycode=CN]
2018-04-13 00:46:06,020  INFO [main] (CountryMapperTest.java:44) - Country [id=2, countryname=美国, countrycode=US]
2018-04-13 00:46:06,020  INFO [main] (CountryMapperTest.java:44) - Country [id=3, countryname=俄罗斯, countrycode=RU]
2018-04-13 00:46:06,020  INFO [main] (CountryMapperTest.java:44) - Country [id=4, countryname=英国, countrycode=GB]
2018-04-13 00:46:06,021  INFO [main] (CountryMapperTest.java:44) - Country [id=5, countryname=法国, countrycode=FR]
2018-04-13 00:46:06,021 DEBUG [main] (JdbcTransaction.java:124) - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2b2fe2f0]
2018-04-13 00:46:06,022 DEBUG [main] (JdbcTransaction.java:92) - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2b2fe2f0]

Mybatis解决jdbc编程的问题

1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决方案:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。

2、 Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决方案:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3、 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决方案:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

4、 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决方案:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。


总结

本篇博文过后,我们对Mybatis有了一个简单的认识,通过创建一个maven项目,结合mybatis的简单配置以及使用方法,跑起来了一个简单的demo,后续继续学习mybatis的各种配置以及常用的、复杂的用法。

©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页
实付 9.90元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值