JAVA WEB从入门到精通 day17 JDBC学习(二)

JDBC学习第二天

事务

简介

    事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。是一系列操作,这些操作作为整体向系统提交,要么都执行,要么都不执行,事务是一个不可分割的工作逻辑单元。

    例如银行转账:张三给李四转账500块钱,需要完成两个操作,张三减500,李四加500,当张三出现异常时,没有减成功,而李四却加了500,这时候银行就会亏损。

事务的特性

    事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

    原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

    一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

    隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

    持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

mysql中事务的使用

        开始事务:START TRANSACTION
        提交事务:COMMIT
        回滚事务(撤销事务):ROLLBACK

        案例:
            (1)我们创建一个银行表 

            CREATE TABLE BANK(
                NAME VARCHAR(50),
                MONEY INT
            );
            (2)插入两行数据 

            INSERT INTO BANK VALUES('ZHANGSAN',1000);
            INSERT INTO BANK VALUES('LISI',1000);

            (3)开始事务
            START TRANSACTION;

            (4)执行转账操作
            UPDATE BANK SET MONEY=MONEY-100 WHERE NAME='ZHANGSAN';
            UPDATE BANK SET MONEY=MONEY+100 WHERE NAME='LISI';

            (5)提交或者回滚事务
            COMMIT (提交事务)
            ROLLBACK(回滚事务):刚才做的所有操作都取消  

JDBC操作事务

Connection中有三个方法和事务相关
            -setAutoCommit(boolean):设置是否自动提交事务,默认为true,每条语句都是一个单独的事务。设置false,那么就相当于开始事务,不自动提交,需要我们提交。 

            -commit():提交事务 

            -rollback():回滚事务
        案例:

            public class Demo4 {

                public static void main(String[] args) throws SQLException {
                    Connection conn=null;
                    try {
                        conn=JdbcUtils.getConnection();       //获取连接

                        String sql1="UPDATE BANK SET MONEY=MONEY-100 WHERE NAME='zhangsan'";
                        String sql2="UPDATE BANK SET MONEY=MONEY+100 WHERE NAME='lisi'";
                        Statement st=conn.createStatement();
                        conn.setAutoCommit(false);               //开启事务
                        st.executeUpdate(sql1);                  //执行语句
                        st.executeUpdate(sql2);
                        conn.commit();                           //提交事务    


                    } catch (Exception e) {
                        conn.rollback();                //如果出现异常则回滚事务
                    }

                }

            }

事务并发引起的问题

    -脏读,某个事务读取的数据是另一个事务正在处理的数据,而另一个事务可能会回滚,造成第一个事务读取的数据是错误的。

    -不可重复读:当一个事务读取某一数据后,另一事务对该数据执行了更新操作,使得第一个事务无法再次读取和前一次一样的数据。

    -幻读:例如第一个事务对一个表中的所有行进行修改,第二个事务是向表中插入一行数据,这时操作第一个事务的用户就会发现还有没修改的行,像发生了幻觉。

事务隔离级别

    -SERIALIZABLE(串行化):不会出现任何并发问题,因为对数据的访问是串行化,而不是并发访问。能解决上面三个问题,但性能最差,没人用。 

    -REPEATABLE READ(可重复读):可以防止脏读和不可重复读的问题 

    -READ COMMITTED(读已提交数据):可以防止脏读问题,性能比REPEATABLE READ好 

    -READ UNCOMMITTED:不能解决任何问题。性能最好。没人用


    Mysql默认隔离级别为REPEATABLE READ 。 
    可以通过 SELECT @@tx_isolation 来查看隔离级别。
    可以通过 set transaction isolationlevel[四个选一个]来设置隔离级别

    JDBC设置隔离级别
        connection.setTransactionisolation(int level)
        参数:
        -Connection.TRANSACTION_READ_UNCOMMITTED
        -Connection.TRANSACTION_READ_COMMITTED
        -Connection.TRANSACTION_REPEATABLE_READ
        -Connection.TRANSACTION_SERIALIZABLE

数据库连接池

概念

数据库连接池负责分配,管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接。
而不是再重新建立一个连接,能明显提高对数据库操作的性能。

当我们每次需要操作数据库时,如果我们每次调用方法都要新建一个连接,那样会大大影响程序的性能。
而我们只需要将创建好的连接对象放在连接池中,用时取出来,不用就再归还。这样能明显提高性能。

原理

    连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。
    使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。 

    同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。 

连接池基本参数

    初始大小:初始拥有的连接对象个数 

    最小空闲连接数:当我们的连接对象都被使用时,剩余的连接对象到达最小空闲连接数时,就要再创建一些连接对象。 

    最大连接数:如果数据库连接请求超过此数,后面的数据库请求将被加入等待队列中。

    还有很多的连接池参数,我们可以百度进行查询设置。

连接池特性

连接池的close()方法不是销毁连接,而是把当前连接归还给连接池。

java使用数据库连接池

数据库连接池必须实现DataSource接口。 
数据库连接池有很多开源的项目,不用我们来实现。
我们来学习几个常用的连接池。

dbcp连接池。

由apache提供。使用时需要导入commons-dbcp.jar,commons-pool.jar和 commons-logging.jar 三个jar包。

操作步骤:
        1.导入jar包
        2.创建连接池对象
        3.设置连接的四大参数 (驱动名字,url,数据库名字,数据库密码),我们也可以设置一些连接池的参数
        4。调用方法获取连接
    public class Demo5 {

            public static void main(String[] args) throws SQLException { 

                BasicDataSource datasource=new BasicDataSource();         //创建连接池对象 

                datasource.setDriverClassName("com.mysql.jdbc.Driver");    //设置四大参数 
                datasource.setUrl("jdbc:mysql://localhost:3306/mysql1");
                datasource.setUsername("root");
                datasource.setPassword("123456");

                Connection conn=datasource.getConnection();               //获取连接
            }

        }   

c3p0连接池

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。

操作步骤:
        1.导入jar包(c3p0.jar 和 mchange-commons-java.jar)
        2.创建连接池对象
        3.设置连接的四大参数参数(驱动名字,url,数据库名字,数据库密码),我们也可以设置连接池参数
        4.调用方法获取连接
            public class Demo6 {

                public static void main(String[] args) throws PropertyVetoException, SQLException {
                    ComboPooledDataSource datasource=new ComboPooledDataSource();    //创建连接池对象 

                    datasource.setDriverClass("com.mysql.jdbc.Driver");               //设置四大参数
                    datasource.setJdbcUrl("jdbc:mysql://localhost:3306/mysql1");
                    datasource.setUser("root");
                    datasource.setPassword("123456");

                    Connection conn=datasource.getConnection();                        //获取连接
                    System.out.println(conn);
                    conn.close();                                     //归还连接
                }
            }

使用c3p0配置文件设置数据库连接四大参数,和连接池参数

首先,必须在本类所在路径创建一个 c3p0-config.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
            <c3p0-config>
              <default-config>    //默认配置
                <property name="user">root</property>                             //数据库名字
                <property name="password">123456</property>                        //数据库密码
                <property name="driverClass">com.mysql.jdbc.Driver</property>      //驱动名字
                <property name="jdbcUrl">jdbc:mysql://localhost:3306/mysql1</property> //url

                <property name="initialPoolSize">10</property>                    //连接池参数
                <property name="maxIdleTime">30</property>
                <property name="maxPoolSize">100</property>
                <property name="minPoolSize">10</property>
              </default-config>

              <named-config name="myconfig">                 //不同的数据库参数。使用时需要用到name这个参数  
                <property name="user">root</property>
                <property name="password">java</property>
                <property name="driverClass">com.mysql.jdbc.Driver</property>
                <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>

                <property name="initialPoolSize">10</property>
                <property name="maxIdleTime">30</property>
                <property name="maxPoolSize">100</property>
                <property name="minPoolSize">10</property>
              </named-config>
            </c3p0-config>

案例:使用配置文件获取连接

public class Demo7 {
                public static void main(String[] args) throws SQLException {
                    ComboPooledDataSource datasource=new ComboPooledDataSource();//创建连接池对象
                    Connection conn=datasource.getConnection();                //获取连接,配置都在配置文件中       

                    System.out.println(conn);
                }
            }
        配置文件里可以写多个不同的数据库配置,使用时需要传入name参数。
            例如:使用上面配置文件中name=myconfig的配置
            public class Demo7 {
                public static void main(String[] args) throws SQLException {
                    ComboPooledDataSource datasource=new ComboPooledDataSource("myconfig");  //参数传入使用的配置的名字
                    Connection conn=datasource.getConnection();

                    System.out.println(conn);
                }
            }

ThreadLocal 类

简介

        泛型类。
        ThreadLocal翻译过来是“本地线程”,其实ThreadLocal并不是线程,而是线程的局部变量。相当于一个容器。用来存取变量。

        使用ThreadLocal操作变量时,ThreadLocal会为使用该变量的线程提供独立的副本,而不会影响其他线程对应的副本。

        ThreadLocal 不是用来解决共享对象的多线程访问问题的。
        一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。
        各个线程中访问的是不同的对象。 

        简单的说,A和B共享一个桌子,是不能通过ThreadLocal解决的,因为ThreadLocal为A和B一人给了个桌子。
        ThreadLocal解决的只是为了结构上需要而共享资源,其实根本没必要共享资源的情况。

原理

ThreadLocal是如何做到为每一个线程维护变量的副本? 

        在Threadlocal类中有一个Map集合,Map中元素键为当前线程对象,值则对应副本的值。 

        1.每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。  

        2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。 
我们大概实现一下ThreadMap
            public class ThreadLocalTest<T> {
                private Map<Thread,T> map=new HashMap<Thread,T>();  //内部有一个map对象

                public void set(T data)
                {
                    map.put(Thread.currentThread(), data);           //将当前线程对象作为键,我们传入的参数设为值
                }
                public T get()
                {
                    return map.get(Thread.currentThread());           //获取当前线程的值
                }
                public void remove()
                {
                    map.remove(Thread.currentThread());               //移除当前线程
                }

            }

ThreadLocal案例

public class ThreadLocal1 {

                public static void main(String[] args) {
                    final ThreadLocal <String>tl=new ThreadLocal<String>();    //创建ThreadLocal
                    tl.set("hello");                                           //设置值
                    new Thread(){                                              //开启一个新线程
                        public void run(){
                            System.out.println(tl.get());                      //获取不到设置的值,输出为null
                        }       
                    }.start();
                    System.out.println(tl.get());                               //输出hello
                }
            }

DBUtils学习

简介

Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库。
使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。

为什么使用DBUtils?

        在之前,我们对数据库的操作都是通过JDBC来实现的,我们需要获取连接,设置sql模板,给sql模板设置值,然后再执行语句。
        这样我们会写大量重复的代码,操作复杂,效率低。

自己简化一下JDBC繁琐的操作

(1)我们首先创建一个数据表

四列,分别是id,名字,性别,年龄
create table stu(
    id char(10),
    name varchar(50),
    gender varchar(10),
    age int
)

(2)创建一个Stu类,是一个Javabean,用来对stu进行封装

    stu类。javabean。对应数据库里stu表的 id,name,gender,age。
    用来对stu进行封装。  

    public class stu {
                private String Id;
                private String Name;
                private String Gender;
                private int Age;
                public stu()
                {

                }
                public stu(String id, String name, String gender, int age) {
                    super();
                    Id = id;
                    Name = name;
                    Gender = gender;
                    Age = age;
                }
                public String getId() {
                    return Id;
                }
                public void setId(String id) {
                    Id = id;
                }
                public String getName() {
                    return Name;
                }
                public void setName(String name) {
                    Name = name;
                }
                public String getGender() {
                    return Gender;
                }
                public void setGender(String gender) {
                    Gender = gender;
                }
                public int getAge() {
                    return Age;
                }
                public void setAge(int age) {
                    Age = age;
                }

            }

(3)我们写一个接口,用来对结果集进行操作。具体要怎么操作由调用者自己实现。

    interface RsHandler<T> {
                    public T handle(ResultSet rs)throws SQLException;
            }

(4)我们写一个工具类对JDBC的操作进行简化

    public class MyDbutils<T> {
                private DataSource datasource;

                public MyDbutils(){                                 //无参构造方法
                } 

                public MyDbutils(DataSource datasource)         //我们在构造方法中传入一个连接池对象
                {
                    super();
                    this.datasource=datasource;
                } 

                public int update(String sql,Object... params)               //对数据库进行增删改操作,参数为sql模板和操作数据库需要的参数
                {
                    Connection conn=null;                                    
                    PreparedStatement ps=null;
                    try {
                        conn=datasource.getConnection();        //通过传入的连接池获取连接                 
                        ps=conn.prepareStatement(sql);
                        initParams(ps,params);                                //调用给sql模板赋值的方法

                        return ps.executeUpdate();                            //执行操作并返回影响的行数

                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }finally {
                        try{
                        if(ps!=null) ps.close();
                        if(conn!=null) conn.close();
                        }catch (Exception e) {

                        }
                    }
                } 

                public void initParams(PreparedStatement pst,Object...params) throws SQLException      //给sql模板赋值的方法,传入PraparedStatement对象和一个参数数组
                {
                    for(int i=0;i<params.length;i++)
                    {
                        pst.setObject(i+1, params[i]);  //进行遍历赋值
                    }
                }

                public T query(String sql,RsHandler rh,Object...params)          //对数据库进行查询操作,参数为 sql模板,对结果集进行操作的接口,sql语句需要的参数
                {

                    Connection conn=null;
                    PreparedStatement ps=null;
                    ResultSet rs=null;
                    try {
                        conn=datasource.getConnection();
                        ps=conn.prepareStatement(sql);
                        initParams(ps,params); 
                        rs=ps.executeQuery();                                       //执行查询语句,得到结果集

                        return (T) rh.handle(rs);                                   //将结果集交给接口的处理方法,方法我们不写,由调用者处理

                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }finally {
                        try{
                        if(rs!=null) rs.close();    
                        if(ps!=null) ps.close();
                        if(conn!=null) conn.close();
                        }catch (Exception e) {

                        }
                    }
                }

            }

(5)我们来测试一下我们写的工具类

public class Test {

            public static void main(String[] args) {
                    /*stu s=new stu();
                    s.setId("0004");
                    s.setName("蓝猫");
                    s.setGender("man");
                    s.setAge(56);
                    addStu(s);*/               //调用addStu方法,将stu对象插入数据库
                    stu s=load("0002");       //调用查询方法,并返回stu对象
                    System.out.println(s.getAge());

            } 

            public static void addStu(Stu stu)            //我们向数据库插入一个stu对象
            {

                    DataSource datasource=JdbcUtils.getDataSource();  //获取连接池
                    MyDbutils md=new MyDbutils(datasource);            //实例化我们自己写的工具类,传入连接池 

                    //调用update方法,传入sql模板,参数数组
                    md.update("insert into stu values(?,?,?,?)", new Object[]{stu.getId(),stu.getName(),stu.getGender(),stu.getAge()});
            }  

            public static stu load(String id)              //我们通过ID查询数据库,返回一个stu对象
            {
                    DataSource datasource=JdbcUtils.getDataSource();
                    MyDbutils md=new MyDbutils(datasource); 

                    RsHandler <stu>rh=new RsHandler<stu>(){              //写一个实现接口的内部类,用来对结果集进行操作
                        public stu handle(ResultSet rs) throws SQLException {

                            if(!rs.next()) return null;                  //将结果集封装成一个对象返回
                            stu s=new stu();
                            s.setId(rs.getString("ID"));
                            s.setName(rs.getString("NAME"));
                            s.setGender(rs.getString("GENDER"));
                            s.setAge(rs.getInt("AGE"));
                            return s;
                        }
                    } ; 

                    return (stu) md.query("select * from stu where id=?", rh,new Object[]{id}); //调用query方法,传入sql模板,具体的实现类,参数数组
                }

            }

我们可以看到我们对数据库的操作已经简化了很多,省去了很多重复的代码,比如设置参数值,关闭资源等
其实Apache提供的DBUtils类和我们做的差不多,我们相当于自己实现了一个简易的DBUtils。
但是DBUtils提供了很多对结果集进行操作的实体类。不需要我们再自己来实现。

DBUtils的使用

DButils主要类
        QueryRunner类
            -update方法
                *int update(String sql,Object... params):可以执行增删改操作,用实例化QueryRunner时传入的连接池获取连接。
                *int update(Connection con,String sql,Object... params):不用连接池的连接,自己提供连接。这样可以保证使用的是同一个连接。我们用到事务的操作时,一般用这个。、

            -query方法
                *T query(String sql,ResultSetHandler rsh,Object... params):可以执行查询操作。
                会先查询得到结果集,然后调用ResultSetHandler的handle()方法 把结果集转换成需要的类型。

                *T query(Connection con,String sql,ResultSetHandler rsh,Object... params):自己传入connection。需要进行事务操作时使用。

            ResultSetHandler接口
                用于把结果集进行转换的接口。
                我们可以实现该接口自己来实现对结果集的操作。但apache提供了很多这个接口的实现类。

DBUtils使用小案例

public class DbutilsTest {

                public static void main(String[] args) throws SQLException {

                        //update();      //调用对数据库增删改操作的方法
                        //query();       //调用对数据库进行查询的方法
                } 

                public static void  update() throws SQLException                //对数据库进行增删改等操作
                {
                    QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource()); //获取DBUtils提供的工具类对象

                    String sql="insert into stu values(?,?,?,?)";                    //设置sql模板
                    Object[] params={"1008","康熙","man","1000"};                 //设置参数数组
                    qr.update(sql, params);                                         //调用update方法,传入sql模板和参数数组
                } 


                public static stu query() throws SQLException                   //对数据库进行查询操作,并返回一个对象
                {
                    QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());

                    String sql="select * from stu where id=?";
                    Object[] params={"1003"};
                    ResultSetHandler<stu> rsh=new ResultSetHandler<stu>(){       //我们需要实现这个接口来对查询出来的结果集进行操作,和我们自己写的几乎一样

                        public stu handle(ResultSet arg0) throws SQLException {
                            // TODO Auto-generated method stub
                            return null;
                        }
                    };
                    return qr.query(sql, params, rsh);                          //调用query方法,传入sql模板,对结果集操作的实现类,参数数组
                }
            }

Apache提供的ResultSetHandler接口的实现类

    DBUtils提供了很多的ResultSetHandler接口的实现类,供我们对结果集进行操作,不用我们再自己写实现类。

(1)BeanHandler类

需要一个javabean的class文件,会将结果集中的数据封装到该javabean对象中,然后返回该javabean对象
需要注意的是javabean中的成员变量必须和数据库中的列名称一样。 
public class DbutilsTest {

                public static void main(String[] args) throws SQLException {
                    QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());

                    String sql="select * from stu where id=?";  //sql模板
                    Object[] params={"0003"};                   //参数数组  

                    stu s=qr.query(sql, new BeanHandler<stu>(stu.class), params);  //提供stu类的class,返回一个stu对象
                    System.out.println(s.getName());
                }

            }

(2)BeanListHandler类

    多行。需要一个指定的class类型的参数,用来把结果集转换成list对象,list集合里是每一行的javabean对象。
    public class DbutilsTest {

                public static void main(String[] args) throws SQLException {
                    QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());

                    String sql="select * from stu ";  //sql模板,对所有行进行查询

                    List<stu> l=qr.query(sql, new BeanListHandler<stu>(stu.class));//返回list集合
                    System.out.println(l);
                }
            }

(3)MapHandler类

        单行。把一行结果转换成map对象。 
        public class DbutilsTest {

                    public static void main(String[] args) throws SQLException {
                        QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());

                        String sql="select * from stu where id=?";
                        Object[] params={"0001"};

                        Map l= qr.query(sql, new MapHandler(),params);//返回一个Map对象
                        System.out.println(l);
                    }
                }

(4)MapListHandler类

多行。把多行记录转换成多个Map。即list<Map>。
public class DbutilsTest {

                    public static void main(String[] args) throws SQLException {
                        QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());

                        String sql="select * from stu ";


                        List<Map<String, Object>> l= qr.query(sql, new MapListHandler());   //返回一个list<Map>集合对象
                        System.out.println(l);
                    }
                }

Apache还给我们提供了很多这种实现类,我们可以在查询使用。我写的并不全面,可以参考网上的文章进行深入学习

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 SpringMVC 中,我们可以通过 ResponseBody 注解返回音频流,具体步骤如下: 1. 在 Controller 中定义一个返回类型为 ResponseEntity<byte[]> 的方法,该方法需要使用 @ResponseBody 注解标注。 2. 在方法中获取音频文件的字节数组,并将其放入 ResponseEntity 对象中返回。 3. 在 ResponseEntity 对象中设置 Content-Type、Content-Disposition 等响应头,以便浏览器正确解析音频流并进行播放。 示例代码如下: ```java @RequestMapping("/audio") @ResponseBody public ResponseEntity<byte[]> getAudio() throws IOException { // 读取音频文件字节数组 InputStream in = getClass().getResourceAsStream("/static/audio/sample.mp3"); byte[] audioBytes = IOUtils.toByteArray(in); // 设置响应头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("audio/mpeg")); headers.setContentDispositionFormData("attachment", "sample.mp3"); // 返回音频流 return new ResponseEntity<>(audioBytes, headers, HttpStatus.OK); } ``` 上述代码中,我们将音频文件 sample.mp3 放在了项目的 /static/audio 目录下。在方法中,我们使用 IOUtils.toByteArray() 方法将音频文件转换为字节数组,并将其放入 ResponseEntity 对象中返回。在设置响应头时,我们使用 MediaType.parseMediaType() 方法设置 Content-Type,使用 setContentDispositionFormData() 方法设置 Content-Disposition。最后,我们通过 new ResponseEntity<>(audioBytes, headers, HttpStatus.OK) 创建 ResponseEntity 对象并返回。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值