MybatisPlus学习

MyBatisPlus(MP)是一个MyBatis的增强工具,提供无侵入的CRUD操作、Lambda形式调用、主键自动生成、ActiveRecord模式等功能。本文详细介绍了MP的配置、通用操作、条件构造器EntityWrapper以及代码生成器的使用,帮助开发者提高开发效率。
摘要由CSDN通过智能技术生成

简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

入门 HelloWorld

配置

  1. 创建表

    -- 创建库
    CREATE DATABASE mp;
    -- 使用库
    USE mp;
    -- 创建表
    CREATE TABLE tbl_employee(
       id INT(11) PRIMARY KEY AUTO_INCREMENT,
       last_name VARCHAR(50),
       email VARCHAR(50),
       gender CHAR(1),
       age INT
    );
    INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Tom','tom@atguigu.com',1,22);
    INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Jerry','jerry@atguigu.com',0,25);
    INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Black','black@atguigu.com',1,30);
    INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('White','white@atguigu.com',0,35);
    
    SELECT  * FROM tbl_employee;
    
    EXPLAIN DELETE FROM tbl_employee WHERE id = 100
    
    CREATE TABLE  tbl_user(
      id INT(11) PRIMARY KEY  AUTO_INCREMENT,
      NAME VARCHAR(50),
      logic_flag INT(11)
    )
    
    
  2. 导入maven坐标

    <dependencies>
            <!-- mybatis-plus依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus</artifactId>
                <version>2.3</version>
            </dependency>
            <!-- junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <!-- log4j -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
            <!-- c3p0 -->
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.2</version>
            </dependency>
            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.32</version>
            </dependency>
            <!-- spring -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
        </dependencies>
    
  3. 配置Spring核心配置文件和Mybatis核心配置文件

    <!-- 数据源 -->
        <context:property-placeholder location="classpath:jdbcConfig.properties"/>
        <bean id="dataSource"
              class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="jdbcUrl" value="${jdbc.url}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <!-- 事务管理器 -->
        <bean id="dataSourceTransactionManager"
              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 基于注解的事务管理 -->
        <tx:annotation-driven
                transaction-manager="dataSourceTransactionManager"/>
        <!-- 配置 SqlSessionFactoryBean
            Mybatis提供的:org.mybatis.spring.SqlSessionFactoryBean
            Mybatis-Plus提供的:com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean
        -->
        <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
            <!-- 数据源 -->
            <property name="dataSource" ref="dataSource"></property>
            <property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
            <!-- 别名处理 -->
            <property name="typeAliasesPackage"
                      value="com.it.domain"></property>     
        </bean>
    
        
    
        <!--
        配置 mybatis 扫描 mapper 接口的路径
        -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage"
                      value="com.it.dao"></property>
        </bean>
    
    <configuration>
    </configuration>
    
  4. 编写实体类

    public class Employee {
    	private Integer id;
    	private String lastName;
        private String email;
        private Integer gender;
        private Integer age;
    }
    
  5. 编写Mapper类(继承BaseMapper接口,泛型是实体类)

    /**
     * 基于MP:让XXXDao接口继承BaseMapper接口即可
     */
    public interface EmployeeDao extends BaseMapper<Employee> {
    }
    

通用CRUD

插入操作

insert方法
		//初始化一个Employee对象
        Employee employee = new Employee();
        //employee.setLastName("奇奇");
        employee.setEmail("qiqi@it.com");
        employee.setGender(0);
        employee.setAge(20);
        //插入到数据库
        Integer result = employeeDao.insert(employee);

此时会报一个错误

org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.it.domain.Employee' with value '1296012965098635266' Cause: java.lang.IllegalArgumentException: argument type mismatch

原因:是因为MybatisPlus主键策略 (默认 ID_WORKER)要使用(ID_AUTO)的主键策略
解决方法:

  1. 在映射表中主键的属性上加上@TableId注解

    /**
         * @TableId:
         *     value:指定表中的主键列的列名,如果实体类中属性名和列名一致,可以省略不写
         *     type:指定主键策略
         */
        @TableId(type = IdType.AUTO)
        private Integer id;
    
  2. 在Spring核心配置文件中定义MybatisPlus的全局策略配置,并且MybatisSqlSessionFactoryBean中配置

    <!-- 定义MybatisPlus的全局策略配置 -->
        <bean id="globalConfiguration" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
    
            <!-- 全局的主键策略 -->
            <property name="idType" value="0"></property>
    
        </bean>
    
    <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
            <!-- 数据源 -->
            <property name="dataSource" ref="dataSource"></property>
            <property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
            <!-- 别名处理 -->
            <property name="typeAliasesPackage"
                      value="com.it.domain"></property>
    
            <!-- 注入全局MP配置 -->
            <property name="globalConfig" ref="globalConfiguration"></property>
        </bean>
    

再次执行插入操作时又会报新的错误:

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mp.employee' doesn't exist

不存在employee这张表
原因:是因为直接将实体类名当作表名
解决:

  1. 在实体类上添加@TableName注解 并指定表名

    @TableName(value = "tbl_employee")
    public class Employee {
    
  2. 在定义MybatisPlus的全局策略配置加入表前缀策略配置

    <!-- 定义MybatisPlus的全局策略配置 -->
        <bean id="globalConfiguration" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
            <!-- 在2.3版本后,dbColumnUnderline默认值就是true -->
            <property name="dbColumnUnderline" value="true"></property>
    
            <!-- 全局的主键策略 -->
            <property name="idType" value="0"></property>
    
            <!-- 全局的表前缀策略配置 -->
            <property name="tablePrefix" value="tbl_"></property>
        </bean>
    

对于MybatisPlus的全局策略配置中dbColumnUnderline是设置是否驼峰命名,在2.3版本后,dbColumnUnderline默认值就是true

insertAllColumn方法
//初始化一个Employee对象
        Employee employee = new Employee();
        //employee.setLastName("奇奇");
        employee.setEmail("qiqi@it.com");
        employee.setGender(0);
        employee.setAge(20);
        //插入到数据库
        Integer result = employeeDao.insertAllColumn(employee);

该方法会将实体类所有的属性进行插入,若实体类中存在于数据库表中不对应的属性 会报错
解决:在实体类属性上添加@TableField注解

/**
     * @TableField:
     *     value:指定表中的列名
     *     exist:判断表中是否存在列名与其一致,不存在设置为false则不添加
     */
    @TableField(value = "last_name",exist = false)
    private String lastName;

insert与insertAllColumn比较

insert方法:在插入时,会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现在sql语句中
insertAllColumn方法:在插入时,不管属性是否非空,属性对应的字段都会出现在sql语句中

注意:MybatisPlus支持主键自增的数据库插入数据获取主键值

		//获取数据在数据库的主键值
        Integer id = employee.getId();
        System.out.println("id:"+id);

更新操作

updateById方法
		//初始化一个Employee对象
        Employee employee = new Employee();
        employee.setId(5);
        //employee.setLastName("怪怪");
        employee.setEmail("guaiguai@it.com");
        employee.setGender(1);
        employee.setAge(27);
        //修改数据
        Integer result = employeeDao.updateById(employee);
updateAllColumnById方法
		//初始化一个Employee对象
        Employee employee = new Employee();
        employee.setId(5);
        //employee.setLastName("怪怪");
        employee.setEmail("guaiguai@it.com");
        employee.setGender(1);
        employee.setAge(27);
        //修改数据
        Integer result = employeeDao.updateAllColumnById(employee);

查询操作

selectById方法
		//1.通过id查询
        Employee result = employeeDao.selectById(5);
selectOne方法
		//2.通过多个列进行查询
        Employee employee = new Employee();
        employee.setAge(20);
        employee.setId(1);
        Employee result = employeeDao.selectOne(employee);
selectBatchIds方法
		//3.通过多个id进行查询
        List<Integer> list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        List<Employee> employees = employeeDao.selectBatchIds(list);
selectByMap方法
		//4.通过Map封装条件查询 map集合中key值要为数据库的列名
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("last_name","Tom");
        map.put("gender",1);
        List<Employee> employees = employeeDao.selectByMap(map);
selectPage方法

该方法的第二个参数下面会说,此时可以设为null

		//5.分页查询
        List<Employee> employees = employeeDao.selectPage(new Page(2, 2), null);
        System.out.println(employees);

删除操作

deleteById方法
		//1.根据id进行删除
        Integer result = employeeDao.deleteById(5);
        System.out.println(result);

deleteByMap方法
		//2.根据条件进行删除
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("last_name","奇奇");
        map.put("email","qiqi@it.com");
        Integer result = employeeDao.deleteByMap(map);
        System.out.println(result);
deleteBatchIds方法
		//3.批量删除
        List<Integer> list = new ArrayList<Integer>();
        list.add(15);
        list.add(16);
        Integer result = employeeDao.deleteBatchIds(list);
        System.out.println(result);

MP 启动注入 SQL 原理分析

xxxMapper 继承了 BaseMapper<T>, BaseMapper 中提供了通用的 CRUD 方法,
方法来源于 BaseMapper, 有方法就必须有 SQL, 
因为 MyBatis 最终还是需要通过 SQL 语句操作数据.

通过现象看到本质

  1. employeeMapper 的本质 org.apache.ibatis.binding.MapperProxy
  2. MapperProxy 中 sqlSession –>SqlSessionFactory

在这里插入图片描述

  1. SqlSessionFacotry 中 → Configuration→ MappedStatements
    每一个 mappedStatement 都表示 Mapper 接口中的一个方法与 Mapper 映射文件中的一个 SQL。
    MP 在启动就会挨个分析 xxxMapper 中的方法,并且将对应的 SQL 语句处理好,保存到 configuration 对象中的 mappedStatements 中
  2. 本质:在这里插入图片描述
    • Configuration: MyBatis 或者 MP 全局配置对象
    1. MappedStatement:一个 MappedStatement 对象对应 Mapper 配置文件中的一个
      select/update/insert/delete 节点,主要描述的是一条 SQL 语句
    2. SqlMethod : 枚举对象 ,MP 支持的 SQL 方法
    3. TableInfo:数据库表反射信息 ,可以获取到数据库表相关的信息
    4. SqlSource: SQL 语句处理对象
    5. MapperBuilderAssistant: 用于缓存、SQL 参数、查询方剂结果集处理等.通过 MapperBuilderAssistant 将每一个 mappedStatement添加到 configuration 中的 mappedstatements 中

条件构造器 EntityWrapper

简介

  1. Mybatis-Plus 通过 EntityWrapper(简称 EW,MP 封装的一个查询条件构造器)或者
    Condition(与 EW 类似) 来让用户自由的构建查询条件,简单便捷,没有额外的负担,
    能够有效提高开发效率
  2. 实体包装器,主要用于处理 sql 拼接,排序,实体参数查询等
  3. 注意: 使用的是数据库字段,不是 Java 属性!
  4. 参数在这里插入图片描述

带条件的查询

/**
     * 条件构造器 分页查询
     */
    @Test
    public void testEntityWrapperSelect(){
        //在tbl_employee表中分页查询gender为1且age在20到30之间的数据
        List<Employee> employees = employeeDao.selectPage(new Page<Employee>(1,1),
                new EntityWrapper<Employee>()
                        .eq("gender", 1)
                        .between("age", 20, 30)
        );

        System.out.println(employees);
    }
 /**
     * 条件构造器 查询操作
     */
    @Test
    public void testSelectList(){
        //查询tbl_employee表中,性别为女或邮箱包含"b"
        List<Employee> employees = employeeDao.selectList(
                new EntityWrapper<Employee>()
                        .eq("gender", 0)
                        .or()
                        .like("email", "b")
        );

        System.out.println(employees);
    }

带条件的修改

/**
     * 条件构造器 修改操作
     */
    @Test
    public void testEntityWrapperUpdate(){
        //将性别为女且年龄小于30的姓名修改为Tom
        Employee employee = new Employee();
        employee.setLastName("Tom");
        Integer result = employeeDao.update(
                employee,
                new EntityWrapper<Employee>()
                        .lt("age", 30)
                        .eq("gender", 0)
        );
        System.out.println(result);
    }

带条件的删除

/**
     * 条件构造器 删除操作
     */
    @Test
    public void testEntityWrapperDelete(){
        //删除姓名为Tom且性别为女的数据
        Integer result = employeeDao.delete(
                new EntityWrapper<Employee>()
                        .eq("last_name", "Tom")
                        .eq("gender", 0)
        );

        System.out.println(result);
    }

使用 Condition 的方式打开如上需求

List<Employee> userListCondition = employeeMapper.selectPage(
new Page<Employee>(2,3),
Condition.create().
eq("gender", 1).
eq("last_name", "MyBatisPlus").
between("age", 18, 50));

ActiveRecord(活动记录)

简介

Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型数据库中的
一个表,而模型类的一个实例对应表中的一行记录。
ActiveRecord 一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,
对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索

如何使用 AR 模式

仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅

@TableName("tbl_employee")
public class Employee extends Model<Employee>{
// .. fields
// .. getter and setter
@Override
protected Serializable pkVal() {
return this.id;
}

插入操作

/**
	 * AR  插入操作
	 */
	@Test
	public void  testARInsert() {
		Employee employee = new Employee();
		employee.setLastName("宋老师");
		employee.setEmail("sls@atguigu.com");
		employee.setGender(1);
		employee.setAge(35);
		
		boolean result = employee.insert();
		System.out.println("result:" +result );
	}

修改操作

/**
	 * AR 修改操作
	 */
	@Test
	public void testARUpdate() {
		Employee employee = new Employee();
		employee.setId(20);
		employee.setLastName("宋老湿");
		employee.setEmail("sls@atguigu.com");
		employee.setGender(1);
		employee.setAge(36);
		
		
		boolean result = employee.updateById();
		System.out.println("result:" +result );
		
	}

查询操作

/**
	 * AR 查询操作
	 */
	@Test
	public void testARSelect() {
		Employee employee = new Employee();
		//第一种用法
		Employee result = employee.selectById(14);
		//第二种用法
		employee.setId(14);
		Employee result = employee.selectById();
		System.out.println(result );
			
}
List<Employee> emps = employee.selectAll();
System.out.println(emps);
List<Employee > emps= employee.selectList(new EntityWrapper<Employee>().like("last_name", "老师"));
System.out.println(emps);
Integer result = employee.selectCount(new EntityWrapper<Employee>().eq("gender", 0));
		System.out.println("result: " +result );

删除操作

/**
	 * AR 删除操作
	 * 
	 * 注意: 删除不存在的数据 逻辑上也是属于成功的. 
	 */
	@Test
	public void testARDelete() {
		
		Employee employee = new Employee();
		
		boolean result = employee.delete(new EntityWrapper<Employee>().like("last_name", "小"));
		System.out.println(result );
	}
boolean result = employee.deleteById(2);
employee.setId(2);
boolean result = employee.deleteById();
System.out.println("result:" +result );
		

分页复杂操作

/**
	 * AR  分页复杂操作
	 */
	@Test
	public void  testARPage() {
		
		Employee employee = new Employee();
		
		Page<Employee> page = employee.selectPage(new Page<>(1, 1), 
				new EntityWrapper<Employee>().like("last_name", "老"));
		List<Employee> emps = page.getRecords();
		System.out.println(emps);
	}

AR 小结

  1. AR 模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 Mybatis 对应的方法,类似于语法糖
    语法糖是指计算机语言中添加的某种语法,这种语法对原本语言的功能并没有影响.
    可以更方便开发者使用,可以避免出错的机会,让程序可读性更好.
  2. 到此,我们简单领略了 Mybatis-Plus 的魅力与高效率,值得注意的一点是:
    我们提供了强大的代码生成器,可以快速生成各类代码,真正的做到了即开即用

代码生成器

  1. MP 提供了大量的自定义设置,生成的代码完全能够满足各类型的需求
  2. MP 的代码生成器 和 Mybatis MBG 代码生成器:
    MP 的代码生成器都是基于 java 代码来生成。MBG 基于 xml 文件进行代码生成
    MyBatis 的代码生成器可生成: 实体类、Mapper 接口、Mapper 映射文件
    MP 的代码生成器可生成: 实体类(可以选择是否支持 AR)、Mapper 接口、Mapper 映射文件、 Service 层、Controller 层.
  3. 表及字段命名策略选择
    在 MP 中,我们建议数据库表名 和 表字段名采用驼峰命名方式, 如果采用下划线命名方式 请开启全局下划线开关,如果表名字段名命名方式不一致请注解指定,我们建议最好保持一致。
    这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。
    当然如果项目里不用考虑这点性能损耗,那么你采用下滑线也是没问题的,只需要在生成代码时配置 dbColumnUnderline 属性就可以

代码生成器依赖

1 MP 的代码生成器默认使用的是 Apache 的 Velocity 模板
2 加入 slf4j ,查看日志输出信息
3 生成controller时需要的依赖

<dependency>
	 <groupId>org.apache.velocity</groupId>
	 <artifactId>velocity-engine-core</artifactId>
	 <version>2.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.7</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.7</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>5.0.2.RELEASE</version>
</dependency>

MP 代码生成器示例代码

		//1.全局配置
        GlobalConfig config = new GlobalConfig();
        config.setActiveRecord(true) //是否支持AR模式
              .setAuthor("wyc") //作者
              .setOutputDir("C:\\Users\\11\\IdeaProjects\\study_mybatisplus\\src\\main\\java") //生成路径
              .setFileOverride(true) //文件是否覆盖
              .setServiceName("%sService") //设置生成的Service接口的首字母是否为I
              .setBaseResultMap(true)
              .setBaseColumnList(true);
        //2.数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL) //设置数据库类型
                        .setDriverName("com.mysql.jdbc.Driver")
                        .setUrl("jdbc:mysql:///mp2")
                        .setUsername("root")
                        .setPassword("123456");
        //3.策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setCapitalMode(true) //全局大写
                      .setDbColumnUnderline(true) //指定表名字段名是否用下划线
                      .setNaming(NamingStrategy.underline_to_camel) //数据库表映射到实体的命名策略
                      .setTablePrefix("tbl_")
                      .setInclude("tbl_dept"); //生成的表
        //4.包名策略配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.it")
                     .setMapper("dao")
                     .setService("service")
                     .setController("controller")
                     .setEntity("domain")
                     .setXml("dao");

        //5.整合配置
        AutoGenerator autoGenerator = new AutoGenerator();
        autoGenerator.setGlobalConfig(config)
                     .setDataSource(dataSourceConfig)
                     .setStrategy(strategyConfig)
                     .setPackageInfo(packageConfig);
        //6.执行
        autoGenerator.execute();

ServiceImpl 说明

EmployeeServiceImpl 继承了 ServiceImpl 类,mybatis-plus 通过这种方式为我们注入了 EmployeeMapper,这样可以使用 service 层默认为我们提供的很多方法,也可以调用我们自己在 dao 层编写的操作数据库的方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值