3.SpringBoot 数据访问

一、简介

​  对于数据访问层,无论是SQL还是NOSQL,SpringBoot默认采用整合Spring Data的方式进行统一处理,添加大量自动配置。引入xxxTemplate、xxxRepository来简化对数据访问层的操作。

<dependency>  <!-- mysql-connector-java 依赖是必须的(当使用Mysql数据库时) -->
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>

二、JDBC方式使用

1. 新增依赖

<dependency>
  	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

2. 使用

application.yml (mysql高版本须带下面url参数,指定时区,字符、SSL)

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
    username: xxxxxx
    password: ******

测试代码

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestDatasource {
	
	@Autowired
	DataSource datasource;
	
	@Test
	public void contextLoads() throws SQLException {
		System.out.println("数据源类"+datasource.getClass());
        //com.zaxxer.hikari.HikariDataSource SpringBoot2默认数据源
        //org.apache.tomcat.jdbc.pool.DataSource SpringBoot1.5默认数据源
		Connection connection = datasource.getConnection();
		System.out.println(connection);
		connection.close();
	}
}

3. 常见功能

核心包:org.springframework.boot.autoconfigure.jdbc

3.1 选择数据源

配置
spring.datasource.typeorg.apache.tomcat.jdbc.pool.DataSource
com.zaxxer.hikari.HikariDataSource(默认)
org.apache.commons.dbcp2.BasicDataSource

3.2 项目启动时执行sql文件

spring:
  datasource:
  	platform: mysql  # 指定平台
    initialization-mode: always  #自动执行schema-*.sql data-*.sql,不需要在
    schema:
    - classpath:sql/schema-jk.sql  # 自定义sql位置

  说明:

  1. 默认sql存放位置:classpath
  2. 默认执行sql:schema.sql、schema-all.sql、data.sql、data-all.sql
  3. 可自定义sql位置及名称
  4. 可指定平台platform:

3.3 数据库操作

核心类:JdbcTemplate

@Autowired
JdbcTemplate jdbcTemplate;

@ResponseBody
@GetMapping("/jdbcSelect")
public List<Map<String, Object>> jdbcSelect() {
    List<Map<String, Object>> queryForList = jdbcTemplate.queryForList("select * from t_user");
    return queryForList;
}

三、数据源 Druid

1. 切换数据源

​ 新增pom依赖:

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

修改数据源:application.yml

spring:
	datasource:
		type: com.alibaba.druid.pool.DruidDataSource

2. 新增druid配置

spring:
  datasource:
#   数据源其他配置
    initialSize: 5  #初始连接数
    minIdle: 5		#最小空闲
    maxActive: 20	#最大连接数
    maxWait: 60000	#最长等待时间 ms
    timeBetweenEvictionRunsMillis: 60000 #运行空闲回收器时间间隔 ms
    minEvictableIdleTimeMillis: 300000	#池中连接空闲5m后可被回收
    validationQuery: SELECT 1 FROM DUAL #验证使用的sql
    testWhileIdle: true		#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.  
    testOnBorrow: false		#获取一个实例时,是否验证可用
    testOnReturn: false		#在return给pool时,是否提前进行validate操作
    poolPreparedStatements: false #是否缓存游标
#   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙  
    filters: stat,wall,slf4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true  
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

java代码

@Configuration
public class ApplicationConfiguration {
	
	@Bean
	@ConfigurationProperties(prefix = "spring.datasource")
	public DruidDataSource getDataSource() {
		return new DruidDataSource();
	}
}

druid配置解析

配置说明
initialSize初始连接数
minIdle最小空闲数
maxActive最大连接数
maxWait配置获取连接等待超时的时间
timeBetweenEvictionRunsMillis配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery验证使用的sql
testWhileIdle指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除。
testOnBorrow获取一个实例时,是否验证可用
testOnReturn在return给pool时,是否提前进行validate操作
poolPreparedStatements打开PSCache
maxPoolPreparedStatementPerConnectionSize指定每个连接上PSCache的大小
filters配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall’用于防火墙
connectionProperties通过connectProperties属性来打开mergeSql功能;慢SQL记录
useGlobalDataSourceStat合并多个DruidDataSource的监控数据

3. 配置监控

//配置Druid监控
//1. 配置管理后台的Servlet 
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet(){
    ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<StatViewServlet>(new StatViewServlet(), "/druid/*");
    /* 可存放值到servlet初始中,键是固定的。可查看类ResourceServlet */
    Map<String, String> initParameters = new HashMap<>();
    initParameters.put("loginUsername", "jinkun");
    initParameters.put("loginPassword", "123456");
    initParameters.put("allow","");//默认允许所有
    initParameters.put("deny","192.168.56.1"); //指向无权访问的机器,值为ip地址
    servletRegistrationBean.setInitParameters(initParameters);
    return servletRegistrationBean;
}

//2.配置一个web监控的Filter
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter(){
    FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setFilter(new WebStatFilter());
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/*")); //过滤所有
    Map<String, String> initParameters = new HashMap<>();
    initParameters.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //排除不过滤的	
    filterRegistrationBean.setInitParameters(initParameters);
    return filterRegistrationBean;
}

监控页面:
在这里插入图片描述

四、整合Mybatis

帮助文档:https://mybatis.org/mybatis-3/zh/

1. 引入依赖

   mybatis并不是Spring Boot 官方出的依赖关系,所以需要加版本号。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

2. 注解版

2.1 注解

注解说明
@Mapper类前标注,指明该类是操作数据库的类
@MapperScan@Mapper是在每一个类前加注解,@MapperScan(value = “包名”) 放置在启动类前,那么此包下的类就不需要再用@Mapper注解了。
@Insert插入语句
@Select查询语句
@Update更新语句
@Delete删除语句
@Param方法参数标注,当数据库中字段名与Javabean字段名不一致的情况
@Results 和 @Result将数据库中查询到的数值转化为具体的字段
#{} 与 ${}# 是对 SQL 进行预处理,$ 是拼接 SQL1
@InsertProvider动态生成sql,type:动态生成 SQL 的类,method:类中具体的方法名
@SelectProvider动态生成sql,type:动态生成 SQL 的类,method:类中具体的方法名
@UpdateProvider动态生成sql,type:动态生成 SQL 的类,method:类中具体的方法名
@DeleteProvider动态生成sql,type:动态生成 SQL 的类,method:类中具体的方法名
@OptionsuseGeneratedKeys=true ; keyProperty = “id” 选择是否自增及自增的列
xml配置文件核心Bean:org.apache.ibatis.session.Configuration.ConfigurationCustomizer

@Insert、@Select、@Update、@Delete、@Param示例

@Select("SELECT * FROM users WHERE user_sex = #{user_sex}")
List<User> getListByUserSex(@Param("user_sex") String userSex); 

@Results 和 @Result 示例:

@Select("SELECT * FROM users")
@Results({
    @Result(property = "userSex",  column = "user_sex", javaType = UserSexEnum.class),
    @Result(property = "nickName", column = "nick_name")
})
List<UserEntity> getAll();

2.2 sql语句构建器

  sql语句构建器是针对于动态sql:if、choose、foreach、trim在注解方式上的使用

  sql语句构建器会创建一个返回sql的方法,结合@xxxProvider使用。

方法说明
SELECT(String)
SELECT(String …)
开始或插入到SELECT子句。可以被多次调用,参数也会添加到 SELECT子句。参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意类型。
SELECT_DISTINCT(String) SELECT_DISTINCT(String…)开始或插入到 SELECT子句, 也可以插入 DISTINCT关键字到生成的查询语句中。 可以被多次调用,参数也会添加到 SELECT子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意类型。
FROM(String)
FROM(String…)
开始或插入到 FROM子句。 可以被多次调用,参数也会添加到 FROM子句。 参数通常是表名或别名,也可以是数据库驱动程序接受的任意类型。
JOIN(String)
JOIN(String…)
INNER_JOIN(String)
INNER_JOIN(String…)
LEFT_OUTER_JOIN(String) LEFT_OUTER_JOIN(String…) RIGHT_OUTER_JOIN(String) RIGHT_OUTER_JOIN(String…)
基于调用的方法,添加新的合适类型的 JOIN子句。 参数可以包含由列命和join on条件组合成标准的join。
WHERE(String)
WHERE(String…)
插入新的 WHERE子句条件, 由AND链接。可以多次被调用,每次都由AND来链接新条件。使用 OR() 来分隔OR。
OR()使用OR来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用或生成不稳定的SQL。
AND()使用AND来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用或生成不稳定的SQL。因为 WHERE 和 HAVING 二者都会自动链接 AND, 这是非常罕见的方法,只是为了完整性才被使用。
GROUP_BY(String)
GROUP_BY(String…)
插入新的 GROUP BY子句元素,由逗号连接。 可以被多次调用,每次都由逗号连接新的条件。
HAVING(String)
HAVING(String…)
插入新的 HAVING子句条件。 由AND连接。可以被多次调用,每次都由AND来连接新的条件。使用 OR() 来分隔OR.
ORDER_BY(String)
ORDER_BY(String…)
插入新的 ORDER BY子句元素, 由逗号连接。可以多次被调用,每次由逗号连接新的条件。
LIMIT(String)
LIMIT(int)
此方法与SELECT()、UPDATE()和DELETE()一起使用时有效。此方法设计为在使用SELECT()时与OFFSET()一起使用。(从3.5.2开始提供)
OFFSET(String)
OFFSET(long)
此方法与SELECT()一起使用时有效。此方法与LIMIT()一起使用。(从3.5.2开始提供)
OFFSET_ROWS(String)
OFFSET_ROWS(long)
追加一个OFFSET n ROWS子句。此方法与SELECT()一起使用时有效。此方法设计为与FETCH_FIRST_ROWS_ONLY()一起使用。(从3.5.2开始提供)
FETCH_FIRST_ROWS_ONLY(String) FETCH_FIRST_ROWS_ONLY(int)追加一个FETCH FIRST n ROWS ONLY子句。此方法与SELECT()一起使用时有效。此方法设计用于与OFFSET_ROWS()一起使用。(从3.5.2开始提供)
DELETE_FROM(String)开始一个delete语句并指定需要从哪个表删除的表名。通常它后面都会跟着WHERE语句!
INSERT_INTO(String)开始一个insert语句并指定需要插入数据的表名。后面都会跟着一个或者多个VALUES() or INTO_COLUMNS() and INTO_VALUES()。
SET(String)
SET(String…)
针对update语句,插入到"set"列表中
UPDATE(String)开始一个update语句并指定需要更新的表明。后面都会跟着一个或者多个SET(),通常也会有一个WHERE()。
VALUES(String, String)插入到insert语句中。第一个参数是要插入的列名,第二个参数则是该列的值。
INTO_COLUMNS(String…)将列短语附加到insert语句。这应该一起调用到_VALUES()中。
INTO_VALUES(String…)将值短语附加到insert语句。这应该与一起调用到_COLUMNS()中。
ADD_ROW()为批量插入添加新行。(从3.5.2开始提供)

@InsertProvider、@SelectProvider、@UpdateProvider、@DeleteProvider示例

//接口中
@SelectProvider(type=DepartmentMapperClass.class ,method = "sql")
public Department getDeptbyId(Integer id);
//类中
public String sql() {
    return "select * from department where id = #{id}";
}

sql语句构建器示例:不用在意偶然性的 and 、where

String sql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
"P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
"FROM PERSON P, ACCOUNT A " +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
"OR (P.LAST_NAME like ?) " +
"GROUP BY P.ID " +
"HAVING (P.LAST_NAME like ?) " +
"OR (P.FIRST_NAME like ?) " +
"ORDER BY P.ID, P.FULL_NAME";

相当于:

private String selectPersonSql() {
  return new SQL() {{
    SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
    SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
    FROM("PERSON P");
    FROM("ACCOUNT A");
    INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
    INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
    WHERE("P.ID = A.ID");
    WHERE("P.FIRST_NAME like ?");
    OR();
    WHERE("P.LAST_NAME like ?");
    GROUP_BY("P.ID");
    HAVING("P.LAST_NAME like ?");
    OR();
    HAVING("P.FIRST_NAME like ?");
    ORDER_BY("P.ID");
    ORDER_BY("P.FULL_NAME");
  }}.toString();
}

2.3 xml版

Mybatis全局配置:

<?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>
	<!-- 全局配置:https://mybatis.org/mybatis-3/zh/configuration.html -->
</configuration>

Mybatis配置sql

<?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="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog" parameterType="int">
    select * from Blog where id = #{id}
  </select>
  <!-- 相关文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html -->
</mapper>

编写相关接口类:

​ namespace:包名.类名

​ id:方法名、resultType:返回类型、parameterType:参数类型

application.yml

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件
  mapper-locations:			#xml映射文件
  - classpath:mybatis/mapper/*.xml

五、SpringData JPA

1. 简介

​  Spring Data项目的目的是为了简化构建基于Spring框架应用的数据访问技术。包括非关系型数据库、Map-Reduce框架、云数据服务等等;另外也包含对关系型数据库的访问。

​  SpringData为我们提供使用统一的API来对数据访问层进行操作;这主要是SpringData Commons项目来实现的。Spring Data Commons让我们在使用关系型或者非关系型数据访问技术时都基于Spring提供的统一标准。标准包括CRUD、分页、排序的相关操作。

2. 优势

  • 统一的Repository接口
org.springframework.data.repository.Repository<T, ID>  //统一接口
org.springframework.data.repository.CrudRepository<T, ID> //基于CRUD操作
org.springframework.data.repository.PagingAndSortingRepository<T, ID> //分页和排序,也有CRUD
org.springframework.data.jpa.repository.JpaRepository<T, ID> //有CRUD、分页、排序
  • 提供数据访问模板类 xxxTemplate;如:MongoTemplate、RedisTemplate*
  • 核心在于可以不用写sql,来完成操作。

3. 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: update #更新或创建数据表结构
    show-sql: true  #控制台显示sql

4. 构建实体类(JavaBean)

​该类通过一些注解,实现自动在数据库中创建该实体类对应的表。

注解说明
@Entity标注类前,说明该类要映射至数据库中
@Table类前标注,属性:name:指向表
@Column属性标注,属性:columnDefinition:字段所属数据库类型,name:列名
lenght:Varchar长度,precision=8, scale=2: 浮点型精度,范围,
unique=true, nullable=false 是否唯一,是否为空。
@Temporal日历映射日期;日期映射时间;日期映射时间戳
@Transient不将属性保存到数据库
@Id主键列
@GeneratedValue自动生成,strategy:生成策略:IDENTITY
@SequenceGenerator创建序列,与@GeneratedValue搭配使用
@LobJava类型是byte[] Byte[]和Serializable类型映射到BLOB,而char[] Character[]和String对象映射到CLOB
@Basic属性:fetch:值LAZY/EAGER 延迟或急切加载;用于@Lob
帮助文档https://www.w3cschool.cn/java/jpa-manytomany-map.html

示例:

日期类型:支持的时间类型包括:java.sql.Date、java.sql.Time、java.sql.Timestamp、java.util.Date、java.util.Calendar。

@Column(name = "START_DATE", columnDefinition = "DATE DEFAULT CURRENT_DATE")
private java.sql.Date startDate;

@Temporal

@Temporal(TemporalType.DATE) //日历映射日期
private java.util.Calendar dob;
@Temporal(TemporalType.TIME) //日期映射时间
private java.util.Date dob;
@Temporal(TemporalType.TIMESTAMP) //日期映射时间戳
private java.util.Date dob;

@GeneratedValue

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@SequenceGenerator

@SequenceGenerator(name="Emp_Gen", sequenceName="Emp_Seq")
@Id @GeneratedValue(generator="Emp_Gen")
private int id;

5. 数据操作

  编写接口继承JpaRepository<实体类,主键>

在接口中书写sql,有三种方式,1:JpaRepository中的方法,2、自定义方法 3、@Query注解

  1. JpaRepository 方法请查看源代码
  2. 自定义方法-SpringData JPA 规则
关键词样品JPQL片段
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNull, NullfindByAge(Is)Null… where x.age is null
IsNotNull, NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(参数附后%)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(参数加前缀%)
ContainingfindByFirstnameContaining… where x.firstname like ?1(参数绑定在中%)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)
  1. @Query注解
//@Modifying
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
//?1 ?2 表示参数位置,@Query中有nativeQuery=true/false 属性表示 语句中字段是数据的字段(true)还是实体类中属性
//修改语句需要加 @Modifying 注解
  1. specification 查询,应对 动态sql构建。实现接口 JpaSpecificationExecutor

JpaSpecificationExecutor 的方法要传入Specification对象,此对象来构建动态sql。核心方法如下:

public interface Specification<T> extends Serializable{
    @Nullable
	Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}
//root:用来获取数据库字段
//query:用来组合条件,返回Specification对象
//criteriaBuilder:用来构建多条件之间关系:like、and、or

示例:

@RestController
public class JpaController {
	@Autowired
	UserMapper um;
	
	@GetMapping("/user/{id}")
	public User getUserbyId(@PathVariable Long id) {
		return um.getOne(id);
	}
}
//dao层
public interface UserMapper extends JpaRepository<User, Long>{

}
//实体类
@Entity
@Table(name ="user")
@JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" }) //当实体类中有字段为null时,在转成JSON时会报错,加此注解可解决。
public interface Specification<T> extends Serializable{
    @Nullable
	Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}
//root:用来获取数据库字段
//query:用来组合条件,返回Specification对象
//criteriaBuilder:用来构建多条件之间关系:like、and、or
@RestController
public class JpaController {
	@Autowired
	UserMapper um;
	
	@GetMapping("/user/{id}")
	public User getUserbyId(@PathVariable Long id) {
		return um.getOne(id);
	}
}
//dao层
public interface UserMapper extends JpaRepository<User, Long>{

}
//实体类
@Entity
@Table(name ="user")
@JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" }) //当实体类中有字段为null时,在转成JSON时会报错,加此注解可解决。
public class User { ... }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值