JDBC数据库,连接池

1JDBC
1.1JDBC概述
1.1.1什么是JDBC?
JDBC( Java DataBase Connectivity ) 翻译过来就是Java数据库连接,其实就是通过Java语言操作数据库的一门技术。
1.1.2为什么要学习JDBC?
在Java开发中,我们不是通过CMD窗口来操作数据库,更多的是通过Java程序来操作数据库,而JDBC就可以实现这样一个操作.
这里写图片描述
1.1.3如何使用JDBC开发程序?
1.提出需求:
创建一个 jt_db 数据库,在库中创建一个Account表,并插入三条记录,然后利用Java程序查询出Account表中所有的记录,并打印在控制台上.
2.开发步骤:
(1)准备数据

drop database if exists jt_db;
create database jt_db;
use jt_db;
create table account(
    id int primary key auto_increment,  
    name varchar(50),
    money double
);
insert into account values(null, 'tom', 1000);
insert into account values(null, 'andy', 1000);
insert into account values(null, 'tony', 1000);

(2)创建工程
这里写图片描述
(3)导入jar包
这里写图片描述
这里写图片描述
(4)创建类并实现JDBC程序(六个步骤)

这里写图片描述

//1.注册数据库驱动
DriverManager.registerDriver(new Driver());

//2.获取数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jt_db", "root", "root");

//3.获取传输器
Statement stat = conn.createStatement();

//4.利用传输器发送SQL到数据库执行,并返回执行结果
ResultSet rs = stat.executeQuery("select * from account");

//5.打印结果
while (rs.next()) {
    int id = rs.getInt("id");
    String name = rs.getString("name");
    double money = rs.getDouble("money");
    System.out.println(id+" : "+name+" : "+money);
}

//6.释放资源
rs.close();
stat.close();
conn.close();

3.执行结果:
这里写图片描述
1.1.4总结
1.JDBC的由来
由于数据库厂商提供的数据库驱动(操作数据库的jar包)各不相同,导致开发人员的学习成本十分的高。因此SUN公司提出了JDBC这套规范,用来统一访问数据的标准。JDBC本质上是一套接口,SUN要求所有的数据库厂商在设计驱动时,都要实现JDBC这套标准。因此开发人员只要学会JDBC这套接口,所有的数据库驱动就都会使用了。

2.JDBC包的介绍
JDBC主要是由java.sql 和javax.sql包组成的,并且这两个包已经被集成到J2SE的规范中了,这意味着,只要一个普通的java程序就可以使用JDBC。
需要注意的是,JDBC包中大部分都是接口,因此在开发数据库程序时,除了如上的两个包,还需要手动的导入具体的数据库驱动。
1.2JDBC API详解
1.2.1注册数据库驱动
DriverManager.registerDriver(new Driver());

真正在实际开发中,我们一般不会使用DriverManager.registerDriver(new Driver()); 这种方式注册驱动, 因为这种方式注册驱动有两个问题:
问题1:这种方式注册驱动会导致驱动注册两次
问题2:这种方式将程序和具体的数据驱动绑死在了一起。

因此我们在开发中其实是通过下面的方式来注册驱动,:
Class.forName(“com.mysql.jdbc.Driver”);

这种方式就可以解决上面的两个问题:
(1)这种方式只会导致驱动注册一次(查看mysql的Driver类的源码可得知)
(2)这种方式只是和数据库驱动类的全路径名字符串绑死在了一起,而这个字符串后期可以提取到配置文件中,因此第二个问题也得到了解决.

1.2.2获取数据库连接
Connection conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/jt_db”, “root”, “root”);
1.数据库的的URL
数据库的url用于指定访问哪一个位置上的数据库服务器及服务器中的哪一个数据库,其写法为:
jdbc:mysql://localhost:3306/jt_db
———- ————– —–
协议名 主机名+端口 数据库的名字
其简写形式为:
jdbc:mysql:///jt_db

2.Connection连接对象
Connection连接对象是JDBC程序中最重要的一个对象,上面提供了一些常用的方法.
createStatement(); – 返回Statement传输器对象.
prepareStatement(); – 返回PreparedStatement传输器对象

1.2.3获取传输器
Statement stat = conn.createStatement();

1.Statement对象
Statement传输器对象用于向数据库服务器发送sql语句,该对象上提供一些常用的发送sql的方法.

executeQuery(String sql) – 用于向数据库发送查询类型的sql语句
executeUpdate(String sql) – 用于向数据库发送更新(增加、删除、修改)类型的sql语句

1.2.4ResultSet结果集对象
ResultSet对象用于封装sql语句查询的结果. 也是一个非常重要的对象. 该对象上提供了一些遍历数据及获取数据的方法.
1.遍历数据行的方法
next() – 使指向数据行的索引向下移动一行
previous() – 使指向数据行的索引向上移动一行

2.获取数据的方法
getInt(int columnName)
getInt(String columnLable)
getString(int columnName)
getString(String columnLable)
getLong(int columnName)
getLong(String columnLable)

1.2.5释放资源
rs.close();
stat.close();
conn.close();
此处释放资源必须按照一定的顺序释放,越晚获取的越先关闭. 所以先关闭 ResultSet对象,再关闭Statement对象,最后关闭Connection对象.
另,为了避免上面的程序抛出异常,释放资源的代码不会执行,应该把释放资源的代码放在finally块中.

try{
    ...
}catch(Exception e){
    ...
}finally{
    if (rs != null) {
        try {
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            rs = null;
        }
    }
    if (stat != null) {
        try {
            stat.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            stat = null;
        }
    }
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            conn = null;
        }
    }
}

1.3JDBC增删改查
1.3.1课堂练习: 使用JDBC完成对account表的增删改查操作.
1.增加:往account表中插入一条新的记录,name为”john”,money为“30000”

public static void add(){
    Connection conn = null;
    Statement stat = null;
    ResultSet rs = null;
    try {
        //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //获取连接
        conn = DriverManager.getConnection(
            "jdbc:mysql:///jt_db",
            "root",
            "root"
        );
        //获取传输器
        stat = conn.createStatement();
        //利用传输器发送SQL到数据库执行,返回执行结果
        String sql = "insert into account values "
                + "(null, 'john', 20000)";
        int row = stat.executeUpdate(sql);

        System.out.println("影响了"+row+"行。。。");
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                rs = null;
            }
        }
        if(stat != null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                stat = null;
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                conn = null;
            }
        }
    }
}

2.修改:修改account表中name为“john”的记录,将金额改为“2000”

public static void update(){
    Connection conn = null;
    Statement stat = null;
    ResultSet rs = null;
    try {
        //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //获取连接
        conn = DriverManager.getConnection(
            "jdbc:mysql:///jt_db",
            "root",
            "root"
        );
        //获取传输器
        stat = conn.createStatement();
        //执行SQL,返回结果
        String sql = "update account set money "
                + "=2000 where name='john'";
        int row = stat.executeUpdate(sql);
        System.out.println("影响了"+row+"行。。。");
    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                rs = null;
            }
        }
        if(stat!=null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                stat = null;
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                conn = null;
            }
        }
    }

}

3.删除:删除account表中name为“john”的记录

public static void delete(){
    Connection conn = null;
    Statement stat = null;
    ResultSet rs = null;
    try {
        //通过JDBCUtils工具类获取连接
        conn = JDBCUtils.getConnection();

        //获取传输器
        stat = conn.createStatement();
        //执行SQL,返回结果
        String sql = "delete from account "
                + "where name='john'";
        int row = stat.executeUpdate(sql);
        System.out.println("影响了"+row+"行。。。");
    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        //通过调用JDBCUtils类中的close方法将资源关闭
        JDBCUtils.close(conn, stat, rs);
    }
}

4.查询:查询account表中id为1的记录

public static void query(){
    Connection conn = null;
    Statement stat = null;
    ResultSet rs = null;
    try {
        conn = JDBCUtils.getConnection();
        stat = conn.createStatement();
        String sql = "select * from "
            + "account where id=1";
        rs = stat.executeQuery(sql);
        if(rs.next()){
            System.out.println(
                rs.getInt("id")+" : " +
                rs.getString("name")+" : "+
                rs.getDouble("money")
            );
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        JDBCUtils.close(conn, stat, rs);
    }
}

1.4JDBCUtils工具类的实现
1.需求分析:
通过上面的对account表增删改查的练习,我们可以发现,程序中出现了大量重复的代码,例如:获取连接以及最后释放资源。为了提高代码的复用,这里我们可以提供一个 JDBCUtils 工具类,用来封装 JDBC程序中的一些重复的代码。

2.实现步骤:
(1)创建一个 JDBCUtils 类,并提供私有构造方法,防止对此类进行实例化
这里写图片描述

(2)提供 getConnection方法,用于获取数据库连接对象
这里写图片描述

(3)提供 close 方法,用于释放资源
这里写图片描述

1.5PreparedStatement对象
在上面的增删改查的操作中,使用的是Statement传输器对象,而在开发中我们用的更多的传输器对象是PreparedStatement对,PreparedStatement是Statement的子接口,比Statement更加安全,并且能够提高程序执行的效率。
1.5.1模拟用户登陆案例
(1)准备数据

use mydb1; 
create table user( 
    id int primary key auto_increment,
    username varchar(50), 
    password varchar(50)
); 
insert into user values(null,'张三','123'); 
insert into user values(null,'李四','234');

(2)创建LoginDemo 类,提供 main 方法 和 login 方法。
这里写图片描述
这里写图片描述

执行时,输入:
这里写图片描述
或输入
这里写图片描述

1.5.2SQL注入攻击
通过上面的案例,我们发现在执行时,不输入密码只输入用户名也可以登陆成功。这就是SQL注入攻击。

SQL注入攻击:由于后台的SQL语句是拼接而来的。其中的参数是由用户提交的,如果用户在提交参数时,在其中掺杂了一些SQL关键字或者特殊符号,就可能会导致SQL语句的语意发生变化。从而执行一些意外的操作。
1.5.3防止SQL注入攻击
如果防止SQL注入攻击? 使用PreparedStatement对象来替代Statement对象。
添加loginByPreparedSatement方法,在方法中,使用PreparedStatement来代替Statement作为传输器对象使用!
这里写图片描述
1.5.4使用PreparedStatement对象的优势
(1)可以防止SQL注入攻击
采用预编译机制,提前将SQL语句的 骨架发送给数据库编译并确定下来,一旦SQL语句的骨架被确定,那么再次发送的就只能是SQL语句的参数,如果参数中再包含影响SQL骨架的关键字或者特殊符号。也只会被当作普通的文本来处理。
这里写图片描述这里写图片描述
(2)省去了拼接SQL语句的麻烦
这里写图片描述
(3)能够尽最大可能提高程序执行的效率
通过PreparedStatement对象发送sql的骨架到数据库会先进行编译,编译之后的SQL会被缓存下来,如果下次执行的sql骨架和上次相同,则无需在编译而是直接使用上次的缓存,可以减少SQL语句编译的次数,提高程序的执行效率。
使用Statement对象发送的sql语句到数据库之后虽然也会编译,但是由于Statement对象发送的sql是先拼接好,在发送给数据库,如果骨架相同但是每次执行的参数不同,整条sql语句也就不相同,所以每次都需要编译。
1.5.5练习:使用PreparedStatement对象完成对数据库中数据的增删改查操作!

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement statement = null;
    ResultSet rs = null;
    try {
        conn=Jdbcutils.geton();
        String sql ="insert into account values(null, ?, ?)";
    statement= conn.prepareStatement(sql);
    statement.setString(1,"zhangfei");
    statement.setDouble(2, 15000);
    int row=statement.executeUpdate();
    System.out.println(row);


    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        Jdbcutils.close(conn, statement, rs);
    }

1.6作业及总结
1.6.1总结
1.熟练掌握JDBC操作数据库
2.熟练掌握使用Statement对象对数据库中的数据进行增删改查的操作
3.熟练掌握使用Statement对象对数据库中的数据进行增删改查的操作
4.完成JDBCUtils工具类,简化JDBC代码
1.6.2作业

-- --------------------------------
-- 准备数据
-- --------------------------------
-- 创建 jt_db 数据库
create database if not exists jt_db;
-- 切换到 jt_db
use jt_db;
-- 如果存在 user 表,就先删除
drop table if exists user;
-- 创建 user 表
create table user(
    id int primary key auto_increment,
    username varchar(50),
    password varchar(50)
);
-- 初始化数据
insert into user values(null, 'tom', '123');
insert into user values(null, 'andy', '123');

1、使用 Statement 对象完成对 jt_db 数据库中 user 表的增删改查操作
(1) 查询 jt_db 数据库中 user 表中的的数据,并打印在控制台上。
(2) 插入一个 username 为 tony,password 为 123 的用户。
(3) 将 tony 用户的密码修改为 ‘123456’。
(4) 将 tony 用户从 user 表中删除。
2、完成 JDBCUtils 工具类
(1)提供 getConn 方法获取数据库连接对象
(2)提供 close 方法释放资源
(3)将 getConn 方法中的参数提取到配置文件中(例如conf.properties),在static块中读取配置文件中的内容,修改 getConn 方法动态获取参数
3、使用 PreparedStatement 对象完成对数据库的增删改查操作
(1) 查询 jt_db 数据库中 user 表中的的数据,并打印在控制台上。
(2) 插入一个 username 为 tony,password 为 123 的用户。
(3) 将 tony 用户的密码修改为 ‘123456’。
(4) 将 tony 用户从 user 表中删除。

批处理

1批处理
1.1批处理概述
1.1.1批处理概述
批处理: 能够将大量的SQL一次性发送到数据库执行的一门技术。
在开发中,如果有大量的sql需要发送到数据库执行,如果一条一条发送,有多少条就需要发送多少次,效率低下。
这里我们可以采用批处理技术,将多条sql添加到批中,再一次性将批发送给数据库,数据库收到后打开批,依次执行其中的SQL语句,这样可以减少发送sql语句的次数,从而提高程序执行的效率。
实现批处理的方式一共有两种,分别可以使用Statement和PreparedStatement实现批处理。
1.2实现批处理
1.2.1Statament实现批处理
1.将下列SQL语句通过Statement对象一次性发送到数据库执行。
2.准备需要执行的SQL语句

-- 切换到jt_db数据库
use jt_db;
-- 创建batch表
create table batch(id int primary key auto_increment, name varchar(50));
-- 插入若干条记录
insert into batch values(null, ‘test1’);
insert into batch values(null, ‘test2’);
insert into batch values(null, ‘test3’);
insert into batch values(null, ‘test4’);
insert into batch values(null, ‘test5’);

3.代码实现:创建StatementBatch类,完成上述需要
这里写图片描述
1.2.2PreparedStatment实现批处理
1.通过PreparedStatement对象将100条sql语句一次性发送给数据库执行。
2.代码实现:创建PSBatch类,实现上述需求。
这里写图片描述

1.2.3两种实现方式的优缺点
1.Statement实现批处理的优点:
(1)可以在一次批处理中包含结构不同的sql语句
这里写图片描述

2.Statement实现批处理的缺点:
(1)无法防止sql注入攻击
(2)没有预编译机制,效率低下
(3)如果每次发送的sql语句骨架相同(仅参数不同),则sql语句的骨架每次都需要编写

3.PreparedStatement实现批处理的优点:
(1)可以防止sql注入攻击
(2)有预编译机制,效率高
(3)如果每次发送的sql骨架相同,sql语句的骨架只需要写一次即可!

4.PreparedStatement实现批处理的缺点:
(1)在同一个批处理中,只能包含结构相同的SQL语句
这里写图片描述
2数据库连接池
2.1数据库连接池
2.1.1什么是连接池?
在开发中,所谓的池就是一个容器,来存储程序的中的数据.
而数据库连接池就是用来存储数据库连接的池子,用于在整个程序中共享连接,减少连接开关的次数,实现连接的复用,从而提高程序执行的效率.
2.1.2为什么要使用数据库连接池?
对于数据库来说,频繁的开关连接会非常的耗费资源,也会导致程序执行效率的低下. 我们可以在程序中创建一个池子,在程序启动时就初始化一批连接放在连接池中,当用户需要链接时,就直接从池子中拿一个连接使用,当用完连接后,也不要将连接关闭,而是将连接还回池中,下一个用户需要连接时也是如此,这样可以减少链接开关的次数,从而提供程序执行的效率.
1.传统方式操作数据库
这里写图片描述
2.使用连接池操作数据库
这里写图片描述
2.1.3如何自己实现一个数据库连接池?
SUN公司为实现连接池提供了一个接口 – javax.sql.DataSource,要求所有的连接池都要实现这个接口,因此 连接池也叫做数据源.

 实现数据库连接池的步骤
(1)写一个类,实现 DataSource 接口
这里写图片描述
(2)在实现类中,创建一个容器(LinkedList),当作连接池使用
这里写图片描述
(3)在静态代码块中,初始化一批连接放在连接池中
这里写图片描述
(4)实现 getConnection 方法,方便获取连接
这里写图片描述

(5)添加自定义方法 returnConn,用于将连接还回连接池中
这里写图片描述
(6)写测试类测试自定义连接池
这里写图片描述
2.2Connection中close方法的改造
2.2.1需求描述
在上面的测试程序中,最后连接用完之后,一定要还回连接池中,而不是通过 close方法将连接关闭。而这样违反了人们的习惯。下面我们可以对 close方法进行改造,改造为底层是还连接而不是关连接。
对一个方法进行改造,我们可以讨论这两种方式,继承和装饰者模式。
1.需求描述
创建Phone接口和Iphone类,让Iphone类实现Phone接口,并添加两个方法 call方法和message方法。
通过继承和装饰者模式对call方法进行改造,改造为听彩铃而不是打电话。
这里写图片描述
2.2.2继承
继承对方法的改造:写一个类,继承改造方法所属的类,在子类中重写父类中的方法实现对方法的改造。
这里写图片描述

继承这种方式只能对子类及子类对象中的方法进行改造,却无法改造父类对象中的方法。也就是说,继承不能对已有的对象中的方法进行改造。
2.2.3装饰者模式
装饰(Decorator)者模式也叫做包装者模式,是通过对已有对象进行包装来扩展对象功能的一种模式。是继承方式的一种替换方案。
如果需要对已有对象上的方法进行改造, 可以定义一个装饰类. 装饰类通常会提供构造函数将被装饰者传入, 并保存在类的内部, 基于已有对象上的功能, 添加更强的功能

1.实现装饰者模式的步骤
a)写一个装饰类, 要求装饰类(RingIphoneDecorate)和被装饰者所属的类(iphone)实现同一个接口(Phone)或者继承同一个父类。
这里写图片描述
b)装饰类必须提供构造方法接收被装饰者, 并将被装饰者保存在类的内部
这里写图片描述
c)对于想要改造的方法直接进行改造, 对于不想改造的方法, 直接调用原有对象(被装设者)上的方法。
这里写图片描述

2.2.4利用装饰者模式改造close方法
1、写一个类 ConnectionDecorate类,和被装饰者(Connection对象)所属的类(com.mysql.jdbc.Connection)实现相同的父接口(java.sql.Connection)。

这里写图片描述
2、提供构造方法将被装饰者传入,并保存在类的内部。
这里写图片描述
3、对于想要改造的方法直接进行改造,对于不想改造的方法直接调用原有对象上的方法。
这里需要在构造函数中添加一个MyPool参数,将自定义连接池对象保存在类的内部。
这里写图片描述
改造close方法
这里写图片描述

对于其他不想改造的方法

这里写图片描述
2.3开源数据库连接池-C3P0
2.3.1使用C3P0连接池开发步骤
1、导入开发包
这里写图片描述
2、创建数据库连接池

ComboPooledDataSource cpds = new ComboPooledDataSource();   

3、设置数据库连接的基本信息
(1)方式一:

cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///mydb1");
cpds.setUser("root");
cpds.setPassword("root");

(2)方式二:推荐!!
在类目录下(开发时可以放在src或者类似的源码目录下), 添加一个c3p0-config.xml文件, 配置内容如下:
这里写图片描述

(3)方式三:
在类目录下(开发时可以放在src或者类似的源码目录下), 添加一个c3p0.properties文件, 配置内容如下:
这里写图片描述
4.通过连接池实例获取连接进行使用
conn = pool.getConnection();

通过c3p0连接池获取到的连接对象, 已经经过了改造, 调用改造后的连接对象的close方法是将连接还回连接池中, 而不是关闭连接!
5.关闭链接

conn.close();//还连接到连接池中!
public static void main(String[] args) {
    Connection conn =null;
    Statement stat = null;
    ResultSet rs=null;
    ComboPooledDataSource pool = new ComboPooledDataSource();
    try {

        conn=pool.getConnection();
        stat=conn.createStatement();
        rs=stat.executeQuery("select * from account where id=3");
        if(rs.next()){
            System.out.println(
                    rs.getInt("id")+" "+rs.getString("name")+" "+rs.getDouble("money"));
        }
    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }finally{
        JDBCutils.close(conn, stat, rs);
    }

2.4作业及总结
2.4.1总结
(1)掌握使用Statement对象和PreparedStatement对象实现批处理
(2)理解在开发中为什么要使用数据库连接池(即传统方式的缺点)
(3)理解数据库连接池的原理
(4)了解自定义连接池的过程
(5)熟练掌握使用c3p0连接池开发数据库程序
2.4.2作业
1、分别使用 Statement 和 PreparedStatement 对象实现批处理操作
(1) Statement: 对 jt_db 数据库,执行如下sql:

这里写代码片drop table if exists batch;
    create table batch(id int primary key auto_increment, name varchar(50));
    insert into batch values(null, 'test1');
    insert into batch values(null, 'test2');
    insert into batch values(null, 'test3');
    insert into batch values(null, 'test4');
    insert into batch values(null, 'test5');```

(2) PreparedStatement: 往 jt_db 数据库的 user 表中插入 200 条记录

    insert into batch values(null, 'test6');
    ...

2、使用 C3P0 连接池 完成对 jt_db 数据库中 product 表的增删改查操作
(1) 查询 jt_db 数据库中 product 表中的的数据,并打印在控制台上。
(2) 往 product 表中插入一条新的商品信息, 如: oppoR11。
(3) 将 john 用户的密码修改为 '123456'。
(4) 将 john 用户从 user 表中删除。

(5) SQL脚本
-- ------------------------------------------------

– 在jt_db库中先创建product表,sql语句参考如下
– 1.创建数据库 jt_db
create database if not exists jt_db;
– 2.选中 jt_db 数据库
use jt_db;
– 3.在 jt_db 库中创建 product 表(商品表)
create table product(
id int primary key auto_increment, – 商品ID
name varchar(50), – 商品名称
category varchar(50), – 商品分类
price double, – 商品单价
pnum int, – 库存数量
description varchar(50) – 商品描述
);
– 4.往product表插入3条数据
insert into product values(null,’小米手机’,’手机数码’,1599.0,100,’小米为发烧而生!’);
insert into product values(null,’四星手机’,’手机数码’,3199.0,200,’四星手机一直被模仿,从未被超越!’);
insert into product values(null,’oppoR11’,’手机数码’,2899.0,300,’充电两小时,使用5分钟!’);
“`

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值