JDBC从菜鸟到大神

#JDBC从菜鸟版到大神版
##基础知识
程序获取连接,向数据库发出更新指令,数据库做相应改变
程序获取连接,向数据库发出获取指令,数据库做相应返回,程序需要将返回的resultSet中的信息使用具体类来接受(即将返回的属性值赋值给我们new的类的对象),多条数据就使用list来装。
##第一版(菜鸟版)
操作方式:
加载驱动、获取连接、获取语句、执行代码、释放资源,另外,要熟悉resultSet这个存放查询结果的类。
操作特点:这个版本的所有代码都是硬编码,无论是sql语句,还是执行方式,均毫无逻辑,只处于外行简单了解程序连接数据库操作的阶段。
##第二版
操作方式:
采用DAO思想,将增删改查等操作进行方法的封装,对不同表格写出不同的DAO类,并且对硬编码问题有了一点点改善。
特点:将之前对jdbc的模糊概念清晰化,逻辑化,将解决方案分为四个模块:(以学生表格为例)Student类,dao接口,dao实现类,dao测试类
**三个包里放上三个类**
这是详细类的命名方式
我们使用类就可以在测试类里体验dao的特点:我们要增删改查时只需要先new一个具体的Student对象或者查询方法时传入一个参数即可完成jdbc操作,以下是实现类实例:

 @Override
    @Test
    public void save(Student student) {
        String sql="INSERT INTO t_student(name,age) VALUES("+ student.getName()+","+student.getAge()+")";
        StringBuilder sb=new StringBuilder();
        sb.append("INSERT INTO t_student01(name,age) values(");
        sb.append("'"+student.getName()+"',");
        sb.append(student.getAge());
        sb.append(")");
        Connection connection=null;
        Statement statement=null;
        try{
            //1、贾
            Class.forName("com.mysql.jdbc.Driver");
            //2、连
           connection=DriverManager.getConnection("jdbc:mysql:///jdbcdemo?useSSL=false","root","admin");
            //3、欲
            statement=connection.createStatement();
            //4、执
            System.out.println(sb.toString());
            statement.executeUpdate(sb.toString());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //5、释
            try{
                if (statement!=null){
                    statement.close();
                }
            }catch (SQLException e){
                e.printStackTrace();
            }finally {
                try{
                    if (connection!=null){
                        connection.close();
                    }
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }
        }
    }
     @Override
    public List<Student> list() {
        Connection connection=null;
        Statement statement=null;
        String sql="SELECT * FROM t_student01";
        List list=new LinkedList();
        try{
            //1、贾
            Class.forName("com.mysql.jdbc.Driver");
            //2、连
            connection=DriverManager.getConnection("jdbc:mysql:///jdbcdemo?useSSL=false","root","admin");
            //3、欲
            statement=connection.createStatement();
            //4、执
            ResultSet resultSet=statement.executeQuery(sql);
            while (resultSet.next()){
                Student student=new Student();
                student.setAge(resultSet.getInt("age"));
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getNString("name"));
                list.add(student);
            }
        }catch (Exception e){
e.printStackTrace();
        }
       try{
           //5、释
           if (statement!=null){
               statement.close();
           }
       }catch (SQLException e){
           e.printStackTrace();
       }finally {
           try {
               if (connection!=null){
                   connection.close();
               }
           } catch (SQLException e) {
               e.printStackTrace();
           }
       }
        return list;
    }

以下是测试类:

  @Test
    public void testSave(){
        Student student1=new Student();
        student1.setName("西门官人");
        student1.setAge(28);
        dao.save(student1);

    }
    @Test
    public  void TestList() {
    System.out.println(dao.list());

    }

##第三版
操作目的:
1、由于每次都要加载驱动,连接数据库,这些是重复的操作,我们要减少。
2、数据库连接用户名,密码,连接的数据库的类型都是写在代码内的,不便于后期人员改动维护,要解决这个问题
操作方式:
1、因此封装成一个Util工具类,负责被dao类调用(负责加载驱动,生成连接)
2、建立properties文件,db.properties文件的解析见代码:

url=jdbc:mysql:///jdbcdemo?useSSL=false
driver=com.mysql.jdbc.Driver
username=root
password=admin

以下是Util类代码:
注意:为了节省程序运行时间,我们使驱动只加载一次,解决方案是将代码写在静态代码块内。

 static {
        try{

            ClassLoader loader=Thread.currentThread().getContextClassLoader();
           InputStream inputStream= loader.getResourceAsStream("db.properties");
           Properties properties=new Properties();
           properties.load(inputStream);
           driver=properties.getProperty("driver");
           password=properties.getProperty("password");
            url=properties.getProperty("url");
            username=properties.getProperty("username");
            Class.forName(driver);
            System.out.println(properties);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

以下是实现类代码

 public void save(Student student) {
        String sql="INSERT INTO t_student(name,age) VALUES("+ student.getName()+","+student.getAge()+")";
        StringBuilder sb=new StringBuilder();
        sb.append("INSERT INTO t_student01(name,age) values(");
        sb.append("'"+student.getName()+"',");
        sb.append(student.getAge());
        sb.append(")");
        Connection connection=null;
        Statement statement=null;
        try{
            //1、贾
            Class.forName(JdbcUtil.driver);

            //2、连
           connection=DriverManager.getConnection(JdbcUtil.url,JdbcUtil.username,JdbcUtil.password);
            //3、欲
            statement=connection.createStatement();

            //4、执
            System.out.println(sb.toString());
            statement.executeUpdate(sb.toString());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //5、释
          JdbcUtil.close(connection,statement,null);
        }
    }
 @Override
    public List<Student> list() {
        Connection connection=null;
        Statement statement=null;
        String sql="SELECT * FROM t_student01";
        List list=new LinkedList();
        ResultSet resultSet=null;
        try{
            //1、贾
            Class.forName(JdbcUtil.driver);
            //2、连
            connection=DriverManager.getConnection(JdbcUtil.url,JdbcUtil.username,JdbcUtil.password);
            //3、欲
            statement=connection.createStatement();
            //4、执
            resultSet=statement.executeQuery(sql);
            while (resultSet.next()){
                Student student=new Student();
                student.setAge(resultSet.getInt("age"));
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getNString("name"));
                list.add(student);
            }
        }catch (Exception e){
e.printStackTrace();
        }finally {
            JdbcUtil.close(connection,statement,resultSet);
        }
        return list;
    }

下面是测试类实例:

 public void testSave(){
        Student student1=new Student();
        student1.setName("八神");
        student1.setAge(28);
        dao.save(student1);
    }
    @Test
    public  void TestList() {
        System.out.println(dao.list());

    }

##第四版
背景知识:数据库服务器在执行sql语句时先检查,再编译并且存放到缓存区,如果有完全相同的SQL语句,那么就直接用缓存区内的编译好的语句,可以大大节省时间。
操作目的:
1、提高sql执行效率
2、为了解决sql注入问题
操作方式:使用预编译类PreparedStatement,其实就是在上一版对于需要传参数的方法的sql的参数的位置加上一个占位符“?”,list方法不需要,因为他没有参数,每个表就对应一个list方法,就一段代码。
操作特点:提高了sql执行效率,解决了sql注入问题
以下是实现类代码:

public void save(Student student) {
        String sql="INSERT INTO t_student01(name,age) VALUES(?,?)";
        StringBuilder sb=new StringBuilder();
        sb.append("INSERT INTO t_student01(name,age) values(");
        sb.append("'"+student.getName()+"',");
        sb.append(student.getAge());
        sb.append(")");
        Connection connection=null;
        PreparedStatement psmt=null;
        try{
            //1、贾
            Class.forName(JdbcUtil.driver);
            //2、连
           connection=DriverManager.getConnection(JdbcUtil.url, JdbcUtil.username, JdbcUtil.password);
            //3、欲
            psmt=connection.prepareStatement(sql);
            psmt.setString(1,student.getName());
            psmt.setInt(2,student.getAge());
            //4、执
           // System.out.println(sb.toString());
            psmt.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //5、释
          JdbcUtil.close(connection,psmt,null);
        }
    }

以下是测试类代码:

  public void testSave(){
        Student student1=new Student();
        student1.setName("八神");
        student1.setAge(28);
        dao.save(student1);
    }

##第五版
操作目的:增加sql执行效率
操作方式:加入批处理类的功能
操作特点:一批sql放到batch内,同时执行,不再是一条一条执行,大大节省时间
以下是代码:

 @Test
    public void testStatementBatch () throws SQLException {
        long start=System.currentTimeMillis();
        Connection connection= JdbcUtil.getConnection();
        Statement statement=connection.createStatement();
        for (int i=0;i<1000;i++){
            String sql="insert into t_student01(name,age) values ('乔峰"+i+"',30)";
            System.out.println(sql);
            statement.addBatch(sql);
        }
        statement.executeBatch();
        System.out.println(System.currentTimeMillis()-start+"ms");
        JdbcUtil.close(connection,statement,null);
    }

##第六版
操作目的:向数据库存入大数据类型,blob(拔萝卜)
操作方式:都是预编译,设置参数,这里在设置参数时需要使用IO流,在查看时也是使用IO流将文件复制到另一位置即可查看。
操作特点:需要使用IO流。
以下是添加方法和查看方法的代码:

 @Test
    //添加大数据
    public void testBlob () throws SQLException, FileNotFoundException {
        String sql="insert into t_jpg(name,heading) values (?,?)";
        Connection connection= JdbcUtil.getConnection();
        PreparedStatement psmt=connection.prepareStatement(sql);
        psmt.setString(1,"乔峰");
        psmt.setBlob(2,new FileInputStream("f:/mm.jpg"));
        psmt.executeUpdate();
        JdbcUtil.close(connection,psmt,null);
    }
//查找大数据
    @Test
    public void testGet() throws SQLException, IOException {
        String sql="select *from t_jpg where id=?";
        Connection connection= JdbcUtil.getConnection();
        PreparedStatement psmt=connection.prepareStatement(sql);
        psmt.setLong(1,1L);
        ResultSet resultSet=psmt.executeQuery();
        if (resultSet.next()){
           Blob blob= resultSet.getBlob("heading");
            InputStream binaryStream=blob.getBinaryStream();
            //保存
            Files.copy(binaryStream, Paths.get("e:/mm2.jpg"));
        }
        JdbcUtil.close(connection,psmt,resultSet);
    }

##第七版
背景需求:安装一个app,注册账号密码之后,需要完善资料,那么数据库那么多条资料怎么知道是向哪一条记录里添加信息呢,这就需要自动获取ID
操作目的:为了满足新的需求–插入一条数据,用户想要知道默认赋予的ID是多少
操作方式:使用statement的多参数方法
操作特点:解决了自动获取ID的需求
以下是代码:

  public void testAutoKey() throws SQLException {
        Connection connection= JdbcUtil.getConnection();
String sql="insert into t_student01 (name,age) values ('张三',15)";
        Statement statement=connection.createStatement();
statement.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
//装的就是单行单列的主键
        ResultSet resultSet=statement.getGeneratedKeys();
        if (resultSet.next()){
            System.out.println(resultSet.getLong(1));
        }
JdbcUtil.close(connection,statement,resultSet);
    }

##第八版
背景知识:数据库操作分类:DDL(create这种)、DML(数据操作语言,增删改)、DQL(数据查询语言,查)
操作目的:我们上几个版本对于Student表格的操作,将方法分为增删改查四个方法,其中大量代码重复,因此,我们想将他们统一起来。
操作方式:建立一个Template类,这也是一个工具类。就是对实现类中的方法再次进行抽象。
操作特点:代码量大大减少,新的工具类Template需要传入sql,Object。。。不确定长度数组。在使用时sql和数组元素都是由实现类中的方法定义
以下是工具类实现代码:

//还是针对student类
public class JdbcTemplate {
   private JdbcTemplate(){}
   public static void update(String sql,Object...params){
       Connection connection=null;
       PreparedStatement psmt=null;
       try{
           connection= DruidUtil.getConnection();
           psmt=connection.prepareStatement(sql);
           if (params!=null&&params.length>0){
               for (int i=0;i<params.length;i++){
                   psmt.setObject(i+1,params[i]);
               }
           }
           psmt.executeUpdate();

       }catch (Exception e){
           e.printStackTrace();
       }finally {
           DruidUtil.close(connection,psmt,null);
       }
   }
    public static List<Student> query(String sql,Object...params) {
        Connection connection=null;
        PreparedStatement statement=null;
        List list=new LinkedList();
        ResultSet resultSet=null;
        try{
            connection= DruidUtil.getConnection();
            statement=connection.prepareStatement(sql);
            //resultSet=statement.executeQuery();
            if (params!=null&&params.length>0){
                for (int i=0;i<params.length;i++){
                    statement.setObject(i+1,params[i]);
                }
            }
            resultSet=statement.executeQuery();
            while (resultSet.next()){
                Student student=new Student();
                student.setAge(resultSet.getInt("age"));
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getNString("name"));
                list.add(student);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            JdbcUtil.close(connection,statement,resultSet);
        }
        return list;
    }
}

以下是实现类代码:

public class StudentDAOImplYubianyi implements IStudentDAO {
    @Override
    @Test
    //DMLs三个方法
    //添加方法
    public void save(Student student) {
        String sql="INSERT INTO t_student01(name,age) VALUES(?,?)";
       Object[] params={student.getName(),student.getAge()};
        JdbcTemplate.update(sql,params);
    }

    //删除方法
    @Override
    public void delete(Long id) {
String sql="DELETE FROM t_student01 WHERE id = ?";
JdbcTemplate.update(sql,id);
    }
//修改方法
    @Override
    public void update(Student student) {
        String sql="UPDATE t_student01 SET name=? ,age=? WHERE ID=?";
Object[] params={student.getName(),student.getAge()};
JdbcTemplate.update(sql,params);
    }

    //查找单条数据方法
    @Override
    public Student get(Long id) {
        String sql="SELECT * FROM t_student01 WHERE id=?";
       List<Student> list=JdbcTemplate.query(sql,id);
     return list.size()>0 ?list.get(0):null;
    }
    //查找所有数据方法
    @Override
    public List<Student> list() {

        String sql="SELECT * FROM t_student01";
     return JdbcTemplate.query(sql);//没值就没值,不要加NULL
    }
}

以下是测试代码:

    public void TestUpdate(){
        Student student=new Student();
        student.setName("楚留香");
        student.setAge(30);
        student.setId(27L);
        System.out.println(student.getName());
        dao.update(student);
    }

##第九版
操作目的:为DQL操作做进一步封装。使其适用于不同的表格。
操作方式:要更加通用,就得抽象性更高,让我们的Template更通用。DQL有get一条数据和list查询整个表格两个方法,get需要参数(ID)而list不需要参数,为了统一,我们在参数设置时仍然和上版本一样设置可变参数。工具类要让每个表格(类)提供自己的处理resultSet的方法,于是我们在实现类中加入一个内部类,这个类提供将resultSet转化为Student的list的方法。
操作特点:工具类更加通用,已经可以对不同的表格(类)达到了通用。
以下是实现类的增加的代码:

 private class StudentResultSetHandler implements IResultsetHandler{
        @Override
        public List handle(ResultSet resultSet) throws SQLException {
            List list=new LinkedList();
            while (resultSet.next()){
                Student student=new Student();
                student.setAge(resultSet.getInt("age"));
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getNString("name"));
                list.add(student);
            }
            return list;
        }
    }

以下是工具类Template的变化后的query方法的代码:

public static List<Student> query(String sql, IResultsetHandler handler,Object...params) {
        Connection connection=null;
        PreparedStatement statement=null;
        List list=new LinkedList();
        ResultSet resultSet=null;
        try{
            connection= DruidUtil.getConnection();
            statement=connection.prepareStatement(sql);
            //resultSet=statement.executeQuery();
            if (params!=null&&params.length>0){
                for (int i=0;i<params.length;i++){
                    statement.setObject(i+1,params[i]);
                }
            }
            resultSet=statement.executeQuery();
//            while (resultSet.next()){
//                Student student=new Student();
//                student.setAge(resultSet.getInt("age"));
//                student.setId(resultSet.getLong("id"));
//                student.setName(resultSet.getNString("name"));
//                list.add(student);
//            }
            list=handler.handle(resultSet);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            JdbcUtil.close(connection,statement,resultSet);
        }
        return list;
    }

注意看,这里的注释部分的代码由每个类自己去实现,实现类只需要给工具类传入一个参数即可完成这种操作。
以下是测试类的代码:

    @Test
    public void TestGet(){
        System.out.println(dao.get(2184L));
    }
@Test
    public  void TestList() {
        System.out.println(dao.list());
    }

##第十版
操作目的:上面版本可以通用,但是只能返回list类型
操作方式:就是在实现类的实现的接口上加上泛型,实现类具体实现再加上具体的类型。这里不过是在实现类对resultSet的处理更加灵活,考虑到可能返回不同的类型的数据。于是在工具类Template的方法上也要加上泛型。
操作特点:使用泛型。
以下是接口代码:

public interface IResultsetHandler<T> {
    //规定处理完resultset之后返回一个T集合
    T handle(ResultSet resultSet) throws SQLException;

}

以下是实现类关键代码:

 private class StudentResultSetHandler implements IResultsetHandler<List<Student>>  {
        @Override
        public List<Student> handle(ResultSet resultSet) throws SQLException {
            List<Student> list=new LinkedList();
            while (resultSet.next()){
                Student student=new Student();
                student.setAge(resultSet.getInt("age"));
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getNString("name"));
                list.add(student);
            }
            return list;
        }
    }

以下是工具类代码:

  public static <T> T query(String sql, IResultsetHandler<T> handler, Object...params) {
        Connection connection=null;
        PreparedStatement statement=null;
        //List<T> list=new LinkedList<T>();
        ResultSet resultSet=null;
        try{
            connection= DruidUtil.getConnection();
            statement=connection.prepareStatement(sql);
            //resultSet=statement.executeQuery();
            if (params!=null&&params.length>0){
                for (int i=0;i<params.length;i++){
                    statement.setObject(i+1,params[i]);
                }
            }
            resultSet=statement.executeQuery();
            return handler.handle(resultSet);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            JdbcUtil.close(connection,statement,resultSet);
        }
        return null;
    }

##第十一版(大神版)
操作目的:上面版本的方法,每次都要创建那个内部类private class StudentResultSetHandler,于是我们希望可以建立一个通用的结果集resultSet处理器。
操作方式:我们不再使用那个内部类来处理结果集,而使用beanhandler来处理。
操作特点:使用BeanHandler,BeanHandlerList,使用了内省的机制。参数增加了一个class,并且使用的bean的遍历设置方法。
以下是单个和多个的handler类:

public class BeanHandler<T> implements IResultsetHandler<T> {
   //beanHandler处理真是的javabean对象具体类型
private Class<T> type;
    public BeanHandler(Class<T> type) {
        this.type = type;
    }
    @Override
    public T handle(ResultSet resultSet) throws IllegalAccessException, InstantiationException, SQLException, IntrospectionException, InvocationTargetException {
        //1、通过type创建一个bean对象
        T bean=null;
        if (resultSet.next()){
           bean=this.type.newInstance();
            BeanInfo beanInfo=Introspector.getBeanInfo(this.type,Object.class);
            PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor pd:pds){

                String key=pd.getName();//表中的列名
                Object value=resultSet.getObject(key);
                pd.getWriteMethod().invoke(bean,value);
            }
        }
        return bean;
    }
}
###############################################
//将结果集处理成一个javabean对象集合,其中T表示一个具体的javabean对象
public class BeanListHandler<T> implements IResultsetHandler<List<T>> {
   //beanHandler处理真是的javabean对象具体类型
private Class<T> type;
    public BeanListHandler(Class<T> type) {
        this.type = type;
    }

    @Override
    public List<T> handle(ResultSet resultSet) throws IllegalAccessException, InstantiationException, SQLException, IntrospectionException, InvocationTargetException {
        //1、通过type创建一个bean对象
        List<T> list=new ArrayList<>();
       while (resultSet.next()){            //开始是if,只能一次,记得改成while
            T bean=this.type.newInstance();
            BeanInfo beanInfo=Introspector.getBeanInfo(this.type,Object.class);
            PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor pd:pds){

                String key=pd.getName();//表中的列名
                Object value=resultSet.getObject(key);
                pd.getWriteMethod().invoke(bean,value);
            }
            list.add(bean);
        }
        return list;
    }
}

工具类的代码
并没有任何变化,在此不做展示。

以下是实现类的关键代码:

  //查找单条数据方法
    @Override
    public Student get(Long id) {
        String sql="SELECT * FROM t_student01 WHERE id= ?";
       Student student= JdbcTemplate.query(sql,new BeanHandler<Student>(Student.class),id);
     return student;
    }
    //查找所有数据方法
    @Override
    public List<Student> list() {

        String sql="SELECT * FROM t_student01";
     return JdbcTemplate.query(sql,new BeanListHandler<>(Student.class));//没值就没值,不要加NULL
    }
//之所以把内部类放在这里,就是为了每个表格一个dao,每个dao实现类负责自己定义自己的list的结构
    //这样就能初步实现通用,但是由于返回的是一个list,所以不好
    //内部类:处理学生查询结果集
//    private class StudentResultSetHandler implements IResultsetHandler<List<Student>> {
//
//        @Override
//        public List<Student> handle(ResultSet resultSet) throws SQLException {
//            List<Student> list=new LinkedList();
//            while (resultSet.next()){
//                Student student=new Student();
//                student.setAge(resultSet.getInt("age"));
//                student.setId(resultSet.getLong("id"));
//                student.setName(resultSet.getNString("name"));
//                list.add(student);
//            }
//            return list;
//        }
//    }

注释部分是减少的代码,get,list方法中参数有变动,注意观察。
下面Druid的连接池,也是JDBC的一部分,工具类代码如下:

public class DruidUtil {
    private DruidUtil(){ }
private static DataSource ds;
    public static void main(String[] args) {
    }
    //问题4:只执行一次,加载驱动
    static {
        try{
            ClassLoader loader=Thread.currentThread().getContextClassLoader();
            InputStream inputStream= loader.getResourceAsStream("druid.properties");
            Properties properties=new Properties();
           properties.load(inputStream);
          ds= DruidDataSourceFactory.createDataSource(properties);
          //ds= DruidDataSourceFactory.createDataSource(properties);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static Connection getConnection(){
        try{
            return ds.getConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    return null;
    }

    //问题5 :重复代码;关闭资源
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        //5、释
        try{
            if (resultSet!=null){resultSet.close();}

        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            try{
                if (statement!=null){
                    statement.close();
                }
            }catch (SQLException e){
                e.printStackTrace();
            }finally {
                try{
                    if (connection!=null){
                        connection.close();
                    }
                }catch (SQLException e){
                    e.printStackTrace();
                }
        }
    }
        }

}

下面是测试类的,插入数据方法insert的代码:

    @Test
    public  void testInsert() throws SQLException {
        String sql="insert into t_student01(name,age) values(?,?)";
        **Connection connection= DruidUtil.getConnection();**
        PreparedStatement psmt=connection.prepareStatement(sql);
        psmt.setString(1,"花满楼猪12");
        psmt.setInt(2,28);
        psmt.executeUpdate();
        psmt.close();
        connection.close();
    }

学完这些,你就是精通JDBC的大神了。
(最后注意一点,运行JDBC程序首先检查服务是否启动。)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值