MyBatis

 

·MyBatis是一款优秀的持久层框架

·它支持定制化SQL、存储过程和高级映射

·MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集

·可以使用简单的XML或注解来配置和映射原生类型、接口和JAVA的POJO(Plain Old Java Objecrs,普通老式Java对象)为数据库中的记录

·MyBatis本来是apache的开源项目iBatis,2010年迁移到goole code并改名,2013年迁移到Github

*1.1如何获取MyBatis?*

·maven仓库(3.4.6版本)

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.4.6</version>

</dependency>

·Github

·中文网:MyBatis中文网

*1.2什么是持久层*

*数据持久化*

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

·内存:*断电即失*

·数据库(JDBC),io文件 这两种方式能让数据持久化。

·持久化概念:举生活的例子,如冷藏,使食物持久化

*为什么需要持久化?*

·因为内存会断电即失,有一些对象不能让他丢失

·内存太贵了

*1.2.1持久层*

Dao层、Service层(业务操作)、Controller层(接受用户请求并且把用户的请求转化给业务去做)....

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

·层的界限十分明显。

*1.3为什么需要MyBatis?*

·帮助程序员将数据存入到数据库中

·方便

·传统的JDBC代码复杂

·是一个简化的,自动化的框架

·学习框架更容易上手

Spring SpringMVC SpringBoot

2. *第一个MyBatis程序*

思路:搭建环境——>导入MyBatis*——>*编写代码——>测试

*2.1搭建环境*

(1) 搭建数据库

(2) 新建项目

1. 新建一个普通的maven项目

2. 删除src目录

3. 导入maven依赖

 <!--导入依赖-->
<dependencies>  
    <!--mysql驱动-->  
    <dependency>  
        <groupId>mysql</groupId>  
        <artifactId>mysql-connector-java</artifactId>        <version>8.0.21</version>
    </dependency>  
    <!--mybatis-->  
    <dependency>    
        <groupId>org.mybatis</groupId>   
        <artifactId>mybatis</artifactId>     
        <version>3.4.6</version>  
    </dependency>  
    <!--junit-->  
    <dependency>   
        <groupId>junit</groupId>  
        <artifactId>junit</artifactId> 
        <version>4.12</version>      
        <scope>test</scope> 
    </dependency>
</dependencies>

(3)创建一个模块

·编写mybatis的核心配置文件

 <!--configuration核心配置文件-->
<configuration>  
    <environments default="development">    
        <environment id="development">    
            <transactionManager type="JDBC"/>    
            <dataSource type="POOLED">    
                <property name="driver" value="com.mysql.jdbc.Driver"/>                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&uxdunicode=true&characterEncoding=UTF-8"/>   
                <property name="username" value="root"/>   
                <property name="password" value="753123"/>      
            </dataSource>    
        </environment>
    </environments>

·编写mybatis工具类

//sqlSessionFactory工厂模式 构建session
public class MybatisUtils {
    //提升作用域
  private static SqlSessionFactory **sqlSessionFactory**;
   
  static{
    try {
      //使用Mybatis第一步:获取sqlSessionFactory对象
      String resource = "mybatis-config.xml";
      InputStream inputStream = Resources.**getResourceAsStream**(resource);
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。//
  // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
  public  static SqlSession getSqlSession(){
    return **sqlSessionFactory**.openSession();
    
  }
}

(4)编写代码

·实体类

//实体类
public class User {
  private int id;
  private String name;
  private String pwd;
  public User() {
  }
  public User(int id, String name, String pwd) {
    this.id = id;
    this.name = name;
    this.pwd = pwd;
  }
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getPwd() {
    return pwd;
  }
  public void setPwd(String pwd) {
    this.pwd = pwd;
  }
  @Override
  public String toString() {
    return "User{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", pwd='" + pwd + '\'' +
        '}';
  }
}

·Dao接口

public interface UserDao {
  List<User> getUserList();
}

·接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件

<?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=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.mybatis.dao.UserDao">
<!--  查询语句-->
<!-- 标签id相当于重写方法的名字,标签内写执行的sql语句-->
  <select id="getUserList" resultType="com.mybatis.pojo.User" >
    select * from mybatis.user;
  </select>
</mapper>

(5)测试

注意点:MapperRegistry是什么?

核心配置文件中注册mappers

·junit测试

@Test
public void test(){
  //第一步:获得SqlSession对象
  SqlSession sqlSession = MybatisUtils.**getSqlSession**();
  //执行SQL
  //方式一:getMapper
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  List<User> userList= mapper.getUserList();
  for (User user : userList) {
    System.**out**.println(user);
  }
  //关闭SqlSession
  sqlSession.close();
}

测试中可能会遇到的问题:

\1. 配置文件没有注册

\2. 绑定接口错误

\3. 方法名不对

\4. 返回类型不对

\5. Maven到处资源问题

*核对接口:*

*SqlSessionFactoryBuilder*

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

*SqlSessionFactory*

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

*SqlSession*

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

*3.CRUD*

1.namespace(命名空间)

namespace中的包名要和Dao/mapper接口的包名一致

2.Select

选择,查询语句:

·id::就是对应的namespace中的方法名;

·result Type:SQL语句执行的返回值(Class,int...)

·parameterType:参数类型;

(1)编写接口

//根据id查询用户
User getUserById(int id);

(2)编写对应的mapper中的sql语句

<select id="getUserById" parameterType="int" resultType="com.mybatis.pojo.User">
  select * from mybatis.user where id = #{0};
</select>

(3)测试

@Test
public void testUserById(){
  SqlSession sqlSession = MybatisUtils.**getSqlSession**();

  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  User user = mapper.getUserById(1);
  System.**out**.println(user);
  sqlSession.close();
}

\4. Insert、Update、Delete

注意点:增删改需要提交事务

(1)接口

(2)sql语句

(3)测试

//增删改需要提交事务
@Test
public void addUser(){
  SqlSession sqlSession = MybatisUtils.**getSqlSession**();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  int res= mapper.addUser(new User(4,"嘎嘎","12580"));
   if (res>0){
     System.**out**.println("插入成功");
   }

   //提交事务,不提交事务数据库不反应
  sqlSession.commit();
  sqlSession.close();
}

@Test
public void updateUser(){
  SqlSession sqlSession = MybatisUtils.**getSqlSession**();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  mapper.updateUser(new User(4,"警察局","110"));
  sqlSession.commit();
  sqlSession.close();

}

@Test
public void deleteUser(){
  SqlSession sqlSession = MybatisUtils.**getSqlSession**();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  mapper.deleteUser(4);
  sqlSession.commit();
  sqlSession.close();

}

*4.分析常见错误:*

·标签不要出错

·resource绑定mapper,需要使用路径

·报错读错从后往前读

·程序配置文件必须符合规范

·空指针异常(NullPointerException),没有注册到资源

·输出的xml文件中存在中文乱码问题

·maven资源没有导出

*5.万能的Map*

假设:我们的实体类或者数据库中的表,字段或者参数过多,我们应当考虑用Map。

//万能的map
int addUser2(Map<String,Object> map);

Xml:

<!--map尝试,传递map的key-->
  <insert id="addUser" parameterType="map">
    insert into mybatis.user(id, name, pwd) VALUES (#{userid},#{username},#{userpwd});
  </insert>

测试:

//map测试
@Test
public void addUser2(){
  SqlSession sqlSession = MybatisUtils.**getSqlSession**();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  Map<String,Object>map=new HashMap<String,Object>();
  map.put("userid",5);
  map.put("username","阿巴");
  map.put("userid","777777");
  mapper.addUser2(map);
  sqlSession.commit();
  sqlSession.close();
}

Map传递参数,直接在sql中取出key即可

parameterType="map"

对象传递参数,直接在sql中取对象的属性即可

parameterType="object"

只有一个基本类型参数的情况下,可以直接在sql中取到

多个参数用map或者注解

*6.模糊查询*

1.Java代码执行的时候,传递通配符%%

List<User> userList = mapper.getUserLike("%李%");

2.在sql拼接中使用通配符

select * from mybatis.user where name like “%”{value}”%”;

*7.配置解析*

·mybatis-config.xml

·Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息

标签的顺序不能打乱

\1. configuration(配置)

\2. Properties(属性)

\3. Settings(设置)

\4. typeAliases(类型别名)

\5. typeHandlers(类型处理器)

\6. objectFactory(对象工厂)

\7. Plugins(插件)

\8. environments(环境配置)

\9. Environment(环境变量)

\10. transactionManager(事务管理器)

\11. dataSource(数据源):dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

\12. databaseIdProvider(数据库厂商标识)

\13. mappers(映射器)

*环境变量*

MyBatis 可以配置成适应多种环境

*不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。*

*学会使用多套运行环境*

Mybatis默认的事务管理器就是JDBC,连接池:POOLED

*属性(properties)*

我们可以通过properties属性来实现引用配置文件

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

编写一个配置文件:

db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&uxdunicode=true&serverTimezone=Hongkong&characterEncoding=UTF-8&autoRecpnnect=true
username=root
password=753123

在核心配置文件中映入

如果在properties里也写了配置,其里面的配置优先,但是resource指定的外部文件配置会覆盖原有配置,所以最终的结果是显示外部配置文件中的配置。

*类型别名(typeAliases)*

·类型别名是为JAVA类型设置一个短的名字

·存在的意义仅在于用来减少类完全限定名的冗余。

<typeAliases>
  <typeAlias type="com.mybatis.pojo.User" alias="User"/>
</typeAliases>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean

扫描实体类的包,它的默认别名就为这个类的类名首字母小写

<typeAliases>
 <package name="com.mybatis.pojo"/>
</typeAliases>

在实体类比较少的情况下使用第一种,若实体类多则使用第二种

第一种可以DIY别名,第二种不能(除非用注解,在实体类上注解)

import org.apache.ibatis.type.Alias;

//实体类
@Alias("user")

 

*设置(settings)*

日志实现

*其他配置*

· typeHandlers(类型处理器)

· objectFactory(对象工厂)

· plugins(插件)

·mybatis-generator-core

·mybatis-plus

·通用mapper

*映射器(mappers)*

MapperRegistry:注册绑定我们的Mapper文件

方式一:{推荐使用}

<!--  每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
  <mapper resource="com/mybatis/dao/UserMapper.xml"/>
</mappers>

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

!--  每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
  <mapper class="com/mybatis/dao/UserMapper"/>
</mappers>

注意点:

·接口和他的Mapper配置文件必须同名

·接口和他的Mapper配置文件必须在用一个包下

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

!--  每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
  <mapper name="com/mybatis/dao"/>
</mappers>

注意点:

·接口和他的Mapper配置文件必须同名

·接口和他的Mapper配置文件必须在用一个包下

8. *生命周期和作用域*

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

*SqlSessionFactoryBuilder:*

·一旦创建了SqlSessionFactory,就不需要它了

·局部变量

*SqlSessionFactory:*

·说白了就是可以想象为数据库连接池

·SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,*没有任何理由丢弃它或重新创建另一个实例。*

·因此 SqlSessionFactory 的最佳作用域是应用作用域。

·最简单的就是使用单例模式或者静态单例模式。

*SqlSession:*

*·*连接到连接池的一个请求

·SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

·用完之后需要赶紧关闭,否则资源被占用

9. *解决属性名与字段名不一致的问题*

*(1)问题*

数据库中的字段

新建一个项目,拷贝之前的,测试实体类字段不一致的情况

public class User { private int id; private String name; private String password;

测试

出现问题

// select * from mybatis.user where id = #{id}; //类型处理器 // select id,name,pwd from mybatis.user where id = #{id};

解决方法:

·取别名

<select id="getUserById" parameterType="int" resultType="user">
  select id,name,pwd as password from mybatis.user where id = #{id};
</select>

(2)**resultMap**

结果集映射

Id name pwd

Id name password

<!--结果集映射-->
  <resultMap id="UserMap" type="User">
<!-- cilumn数据库中的字段 property实体类中的属性-->
    <result column="id" property="id"/>
    <result column="iname" property="name"/>
    <result column="pwd" property="password"/>
  </resultMap>
  <select id="getUserById" parameterType="int" resultMap="UserMap">
    select * from mybatis.user where id = #{id};
  </select>

·resultMap 元素是 MyBatis 中最重要最强大的元素。

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

·如果这个世界总是这么简单就好了。

10. *日志*

1. 日志工厂

如果一个数据库操作出现了异常,需要排错,日志是最好的助手

曾经:sout,debug

现在:日志

·SLF4J

| LOG4J

| LOG4J2

| JDK_LOGGING

| COMMONS_LOGGING

| STDOUT_LOGGING

| NO_LOGGING

具体使用哪一个在设置中设定

STDOUT_LOGGING标准日志输出

2. Log4j

什么是Log4j?

·Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件

·我们也可以控制每一条日志的输出格式

·通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

·通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

(1)先导入Log4j的包

<dependencies>
  <!-- https://mvnrepository.com/artifact/log4j/log4j -->
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
  </dependency>
</dependencies>

(2)Log4j.properties

log4j.rootLogger=DEBUG,console,file

\#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

\#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

\#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

(3)配置Log4j为日志的实现

<settings>
  <setting name="logImpl" value="LOG4J"/>
</settings>

(4)简单使用:

在log4j的类中导入包import org.apache.log4j.Logger;

日志对象:参数为当前类的class

Static Logger logger=Logger.getLogger(userDaoTest.class);

日志级别:

logger.info("info:进入了testLog4j"); logger.debug("debug:进入了testLog4j"); logger.error("error:进入了testLog4j");

(5)使用结果

(6)测试

11. *分页*

思考:为什么分页?

·减少数据的处理量

使用Limit分页

语法:Select * from user limit startindex,pageSize;

语法:Select * from user limit n;#指0,n

使用mybatis实现分页,核心sql

1. 接口

//分页实现
List<User> getUserByList(Map<String,Integer> map);

2. Mapper.xml

<!--分页实现-->
<select id="getUserByList" parameterType="map" resultMap="UserMap">
  Select * from mybatis.user limit #{startIndex},#{pageSize};
</select>

3. 测试

 @Test
  public  void  getUserByList(){
    SqlSession sqlSession=MybatisUtils.**getSqlSession**();
    UserMapper userMapper=sqlSession.getMapper(UserMapper.class);

  HashMap<String,Integer> map=new HashMap<String,Integer>();
  map.put("startIndex",0);
  map.put("pageSize",2);
  List<User> userList = userMapper.getUserByList(map);
  for (User user : userList) {
    System.**out**.println(user);
  }
}

12.使用注解开发

1.注解在接口上实现

@Select("select * from book")
List<Book> getBooks();

2.需要在核心配置文件绑定接口

<!--    绑定接口-->
    <mappers>
        <mapper class="com.mybatis.dao.BookMapper"/>
    </mappers>

3.测试

@Test
public void test1(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    BookMapper mapper = sqlSession.getMapper(BookMapper.class);//底层反射获取包的全类名,获得注解,获得sql语句,然后执行
    List<Book> books = mapper.getBooks();
    for (Book book : books) {
        System.out.println(book);
    }
    sqlSession.close();

}

本质

反射机制实现

底层

动态代理

增删改查

我们可以在工具类创建的时候自动提交事务

public  static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);//自动提交事务,若没有设置,数据库不会有反应的

}

编写接口,添加注解

@Select("select * from book")
List<Book> getBooks();

//方法存在多个参数,所有参数前面都必须加上@Param注解,且Param里的id对应的是sql语句的#{}
@Select("select * from book where id = #{id}")
Book getBookById(@Param("id") int id);

@Insert("insert into Book(id,title,author,publication_date,price) values (#{id},#{title},#{author},#{publication_date},#{price})")
int addBook(Book book);

@Update("update book set id=#{id},title=#{title},author=#{author},publication_date=#{publication_date},price=#{price} where id=#{id}")
int updateBook (Book book);

@Delete("delete from book where id=#{id}")
int deleteBook(@Param("id") int id);

测试类:

//查询全部
@Test
public void test1(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    BookMapper mapper = sqlSession.getMapper(BookMapper.class);//底层反射获取包的全类名,获得注解,获得sql语句,然后执行
    List<Book> books = mapper.getBooks();
    for (Book book : books) {
        System.out.println(book);
    }
    sqlSession.close();

}

注意:我们必须要将接口注册绑定到我们的核心配置文件中

关于@Param()注解

·基本类型的参数或者String类型,需要加上

·引用类型不需要加

·如果只有一个基本类型的话,可以忽略,但建议大家都加上

·我们在SQL中引用的就是我们这里的@Param(“xx”)中设定的属性名

即xx和属性名相同

#{}和${}的区别

#是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.

$是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.

使用#可以很大程度上防止sql注入

但是如果使用在order by 中就需要使用 $.

13.Lombok

提供的注解:

@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var

使用步骤:

1.安装Lombok插件,idea自带了已经

2.在项目中导入Lombok的jar包

@Data

这个注解提供:无参构造,get,set,tostring,hashcode,equals

@AllArgsConstructor

提供 有参构造,但是用上这个之后Data的无参构造会消失

显式的定义了有参构造,这时无参构造需要去手动赋值

@NoArgsConstructor

这个就是手动赋值无参构造的注解

14.多对一处理

例子:多个学生对应一个老师

·对于学生而言,关联..多个学生关联一个老师【多对一】

·对于老师而言,集合..一个老师有很多学生【一对多】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值