一个JDBC访问oracle数据库表的例子,让你搞清三层架构与MVC框架模式之间的关系,以及满足设计原则的类的结构和各类的职责

问题描述

我们以访问oracl数据库的emp表为例,来对java对数据库的访问相关问题进行一下详细讨论。
我们需要完成的功能是可以通过命令控制台以及键盘输入,来实现对emp表的增,删,查,改等功能。
这里不讨论通过任何框架来实现数据库的访问,比如mybatis等。
但会考虑三层架构等设计问题。

类的设计

三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构,各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体。

在这里插入图片描述
从上图我们可以分析出,所需要的类有:

实体层(entity)

Emp类

Emp类,属业务实体类,因考虑到方便,实体类的名称与表的名称相一致。其属性与表中的字段一一对应,并有各属性的setter和getter方法。
所有实体类放在com.javaoldman.entity包中。

  • emp表结构
名称    是否为空? 类型
------- -------- ----------
EMPNO   NOT NULL NUMBER(4)
ENAME            VARCHAR2(10)
JOB              VARCHAR2(9)
MGR              NUMBER(4)
SAL              NUMBER(7,2)
COMM             NUMBER(7,2)
DEPTNO           NUMBER(2)
  • Emp类的定义
package com.javaoldman.entity;

public class Emp {
  private int empno;
  private String ename;
  private String job;
  private int mgr;
  private double sal;
  private double comm;
  private int deptno;

  public int getEmpno() {
    return empno;
  }

  public String getEname() {
    return ename;
  }

  public String getJob() {
    return job;
  }

  public int getMgr() {
    return mgr;
  }

  public double getSal() {
    return sal;
  }

  public double getComm() {
    return comm;
  }

  public int getDeptno() {
    return deptno;
  }

  public void setEmpno(int empno) {
    this.empno = empno;
  }

  public void setEname(String ename) {
    this.ename = ename;
  }

  public void setJob(String job) {
    this.job = job;
  }

  public void setMgr(int mgr) {
    this.mgr = mgr;
  }

  public void setSal(double sal) {
    this.sal = sal;
  }

  public void setComm(double comm) {
    this.comm = comm;
  }

  public void setDeptno(int deptno) {
    this.deptno = deptno;
  }

  @Override
  public String toString() {
    return "Emp{" +
            "empno=" + empno +
            ", ename='" + ename + '\'' +
            ", job='" + job + '\'' +
            ", mgr=" + mgr +
            ", sal=" + sal +
            ", comm=" + comm +
            ", deptno=" + deptno +
            '}';
  }
}

数据访问层(DAO)

数据访问层实现对于数据库的具体操作,包括增删改查等。
但考虑到可能会连接不同的数据库类型,所以, 为了解除业务逻辑层与DAO层的耦合,将DAO层进行符合依赖倒置原则的设计。即分成DAO接口和DAO具体实现类两层。
在这里插入图片描述
同理,为了解除表示层对业务逻辑层的依赖,也将业务逻辑层分成接口和实现类两层。

EmpDao 接口

EmpDao接口,定义数据访问的方法。主要方法有:

  • 查询所有emp记录
  • 按empno查询emp记录
  • 按empno删除emp记录
  • 添加新的emp记录
  • 按empno来更新emp记录
    所有dao接口放在com.javaoldman.dao包内。

EmpDao代码:

package com.javaoldman.dao;

import com.javaoldman.entity.Emp;

import java.util.List;

public interface EmpDao {
    List<Emp> selectAllEmp();
    Emp selectEmpByEmpno(int empno);
    int deleteEmpByEmpno(int empno);
    int insertEmp(Emp emp);
    int updateEmp(Emp emp);
}

从接口方法的申明中可以看出,方法的入口参数以及返回值多是为Emp对象或集合,这也是将关系数据库中的表记录与实体对象进行映射的关键。这可以让业务逻辑层只面向对象,不需要面向数据库的细节。

JDBCUtil工具类

因为访问数据库,有许多的代码是重复的,包括数据库的驱动注册,连接对象等,还有数据库访问结束后的资源释放等。所以,可能定义一个工具类,来实现这些重复性的工作。比如直接获得一个连接对象,或直接释放数据库资源等。
这个类可以放在com.javaoldman.util包中
代码如下:

package com.javaoldman.util;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * JDBC工具类
 */
public class JDBCUtil {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;

    //静态代码块,在程序编译的时候执行
    static {
        try {
            //创建Properties对象
            Properties p = new Properties();
            //获取文件输入流
            InputStream in = JDBCUtil.class.getResourceAsStream("/db.properties");
            //加载输入流
            p.load(in);
            //获取数据库连接驱动名字
            driver = p.getProperty("driver",null);
            //获取数据库连接地址
            url = p.getProperty("url",null);
            //获取数据库连接用户名
            username = p.getProperty("username",null);
            //获取数据库连接密码
            password = p.getProperty("password",null);
            if(driver != null && url != null
                    && username != null && password != null){
                //加载驱动
                Class.forName(driver);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接对象
     * @return Connection连接对象
     */
    public static Connection getConn(){
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url,username,password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 关闭连接(Connection连接对象必须在最后关闭)
     * @param conn Connection连接对象
     * @param st 编译执行对象
     * @param rs 结果集
     */
    public static void close(Connection conn, Statement st, ResultSet rs){
        try {
            if(rs != null){
                rs.close();
            }
            if(st != null){
                st.close();
            }
            if(conn != null){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

属性文件

数据库连接需要一些属性数据,比如URL,用户名,密码,还有驱动的类名等,为了让修改,可以将这些信息保存在一个属性文件中,方便修改。在工具类中,读取这个属性文件来设置相关的值。
在项目目录中创建一个properties的属性文件db.properties,里面定义了四行数据,分别对应着JDBC连接所需要的几个参数(注:Properties底层为一个Hashtable,配置文件中“=”之前的代表Map中的键,之后的代表相应键所对应的值)

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
username=scott
password=tiger

EmpDaoImpl实现类

EmpDaoImpl是对EmpDao接口的具体实现,分别实现接口中的所有方法。在该类中,实现具体的通过JDBC访问ORACLE数据库的相关操作。
该类放在com.javaoldman.dao.impl包中。
代码:

package com.javaoldman.dao.impl;

import com.javaoldman.dao.EmpDao;
import com.javaoldman.entity.Emp;
import com.javaoldman.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class EmpDaoImpl implements EmpDao {

    @Override
    public List<Emp> selectAllEmp() {
        Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
        String sql = "select * from emp";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List<Emp> empList = new ArrayList<>();
        try {
             preparedStatement = connection.prepareStatement(sql);
             resultSet = preparedStatement.executeQuery();
             while (resultSet.next()){
                 Emp emp = new Emp();
                 emp.setEmpno(resultSet.getInt("empno"));
                 emp.setEname(resultSet.getString("ename"));
                 emp.setJob(resultSet.getString("job"));
                 emp.setMgr(resultSet.getInt("mgr"));
                 emp.setSal(resultSet.getDouble("sal"));
                 emp.setComm(resultSet.getDouble("comm"));
                 emp.setDeptno(resultSet.getInt("deptno")); //组装emp对象
                 empList.add(emp); //组装emp集合
             }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.close(connection,preparedStatement,resultSet); //释放数据库资源
        }
        return empList;  //返回emp集合
    }

    @Override
    public Emp selectEmpByEmpno(int empno) {
        Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
        String sql = "select * from emp where empno = ?";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List<Emp> empList = new ArrayList<>();
        Emp emp = null;
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1,empno);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()){
                emp = new Emp();
                emp.setEmpno(resultSet.getInt("empno"));
                emp.setEname(resultSet.getString("ename"));
                emp.setJob(resultSet.getString("job"));
                emp.setMgr(resultSet.getInt("mgr"));
                emp.setSal(resultSet.getDouble("sal"));
                emp.setComm(resultSet.getDouble("comm"));
                emp.setDeptno(resultSet.getInt("deptno")); //组装emp对象
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.close(connection,preparedStatement,resultSet); //释放数据库资源
        }
        return emp;  //如果有记录,就返回emp,否则返回null
    }

    @Override
    public int deleteEmpByEmpno(int empno) {
        Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
        String sql = "delete from emp where empno = ?";
        PreparedStatement preparedStatement = null;
        int num = 0;
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1,empno);
            num = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.close(connection,preparedStatement,null); //释放数据库资源,因为没有记录集对象,所以,用null
        }
        return num;  //返回受影响的行数
    }

    @Override
    public int insertEmp(Emp emp) {
        Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
        int empno;
        String ename;
        String job;
        int mgr;
        double sal;
        double comm;
        int deptno;

        empno = emp.getEmpno();
        ename = emp.getEname();
        job = emp.getJob();
        mgr = emp.getMgr();
        sal = emp.getSal();
        comm = emp.getComm();
        deptno = emp.getDeptno();

        String sql = "insert into emp(empno,ename,job,mgr,sal,comm,deptno) values(?,?,?,?,?,?,?)";
        PreparedStatement preparedStatement = null;
        int num = 0;
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1,empno);
            preparedStatement.setString(2,ename);
            preparedStatement.setString(3,job);
            preparedStatement.setInt(4,mgr);
            preparedStatement.setDouble(5,sal);
            preparedStatement.setDouble(6,comm);
            preparedStatement.setInt(7,deptno);

            num = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.close(connection,preparedStatement,null); //释放数据库资源,因为没有记录集对象,所以,用null
        }
        return num;  //返回受影响的行数
    }

    @Override
    public int updateEmp(Emp emp) {
        Connection connection = JDBCUtil.getConn(); //通过工具类直接得到Connection对象
        int empno;
        String ename;
        String job;
        int mgr;
        double sal;
        double comm;
        int deptno;

        empno = emp.getEmpno();
        ename = emp.getEname();
        job = emp.getJob();
        mgr = emp.getMgr();
        sal = emp.getSal();
        comm = emp.getComm();
        deptno = emp.getDeptno();

        String sql = "update emp set ename=?,job=?,mgr=?,sal=?,comm=?,deptno=? where empno=?";
        PreparedStatement preparedStatement = null;
        int num = 0;
        try {
            preparedStatement = connection.prepareStatement(sql);

            preparedStatement.setString(1,ename);
            preparedStatement.setString(2,job);
            preparedStatement.setInt(3,mgr);
            preparedStatement.setDouble(4,sal);
            preparedStatement.setDouble(5,comm);
            preparedStatement.setInt(6,deptno);
            preparedStatement.setInt(7,empno);

            num = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.close(connection,preparedStatement,null); //释放数据库资源,因为没有记录集对象,所以,用null
        }
        return num;  //返回受影响的行数
    }
}

业务逻辑层(service)

业务逻辑层实现具体的业务功能,同样,为了解除表示层与业务逻辑层之间的耦合,实现依赖倒置原则,将业务逻辑层又分层为接口和具体的实现类。

EmpService接口

EmpService接口规范对Emp类对象的一些业务功能。
该接口定义在com.javaoldman.service包下。
代码如下:

package com.javaoldman.service;

import com.javaoldman.entity.Emp;

import java.util.List;

public interface EmpService {
    List<Emp> getAllEmps();
    Emp getEmpByEmpno(int empno);
    int deleteEmpByEmpno(int empno);
    int addEmp(Emp emp);  //如果empno的记录已存在,返回-1,其他,返回添加成功的数量
    int updateEmp(Emp emp); //如果empno的记录不存在,返回-1,其他,返回更新成功的数量
}

因为我们暂时需要的业务功能也只是简单的增删查改的业务功能,所以,看上去,EmpService接口的定义与EmpDao接口的定义有些类似。但是有区别。

  • EmpDao是只负责对数据库的访问方法,
  • EmpService是业务功能。这些业务功能需要使用EmpDao中的方法以及其他的一些业务逻辑代码来实现。甚至有许多情况下,还可能需要其他实体的Dao接口的实现类来合作完成。
    例如:在网上购物的需求中,买东西的业务类,可能就需要一个buy方法,而该方法中可能需要使用到订单Dao和库存Dao。因为buy的业务的实现,需要增加订单,同时减少库存。
    所以,从这个例子就可以看出,业务逻辑和DAO的所负责的责任是有较大的区别的。
    在本例中,包括addEmp方法以及updateEmp方法,由于Emp表涉及到部分编号deptno。所以,其实,这里的业务逻辑类的实现类,在实现addEmp和updateEmp方法时,其实是需要EmpDao以及DeptDao两个DAO类的。因为在添加和更新时,要查询在Dept表中,是否存在deptno所对应的条记录。如果不存在,是不能添加或更新的。因为会违反表的参考约束。但在本例中,暂不考虑以上问题。
    但为了对service和dao方法的实现有所区分,特意在addEmp和updateEmp中,都在业务逻辑上增加了先判断empno值对应的记录是否存在,然后再进行增加或更新。如此操作,仅为演示区分的效果。

EmpServiceImpl实现类

该实现类具体实现EmpService接口。
该类实义在com.javaoldman.service.impl包下。
代码如下:

package com.javaoldman.service.impl;

import com.javaoldman.dao.EmpDao;
import com.javaoldman.dao.impl.EmpDaoImpl;
import com.javaoldman.entity.Emp;
import com.javaoldman.service.EmpService;

import java.util.List;

public class EmpServiceImpl implements EmpService {
    EmpDao dao = new EmpDaoImpl();  //实例化EmpDao的实现类对象
    //下面,利用dao的方法来实现service的业务逻辑
    @Override
    public List<Emp> getAllEmps() {
        return dao.selectAllEmp();
    }

    @Override
    public Emp getEmpByEmpno(int empno) {
        return dao.selectEmpByEmpno(empno);
    }

    @Override
    public int deleteEmpByEmpno(int empno) {
        return dao.deleteEmpByEmpno(empno);
    }

    @Override
    public int addEmp(Emp emp) {
        //为了演示service与Dao之间的区别。这里假设,在添加业务中,需要先判断这个empno的记录是否存在,如果存在就不添加,返回-1。
        int empno;
        empno = emp.getEmpno();
        Emp emp2 = dao.selectEmpByEmpno(empno);
        if(emp2!=null){  //如果已存在empno值的记录,就返回-1
            return -1;
        }
        return dao.insertEmp(emp);
    }

    @Override
    public int updateEmp(Emp emp) {
        //为了演示service与Dao之间的区别。这里假设,在更新业务中,需要先判断这个empno的记录是否存在,如果不存在就不更新,返回-1。
        int empno;
        empno = emp.getEmpno();
        Emp emp2 = dao.selectEmpByEmpno(empno);
        if(emp2==null){  //如果不存在empno值的记录,就返回-1
            return -1;
        }
        return dao.updateEmp(emp);
    }
}

从以上代码可以看出,业务逻辑层只负责业务功能,而不会负责任何数据的展示,即不会进行打印,输出一类的操作。打印输出的操作会通过业务层返回的数据,交给表示层来完成。这也是单一职责原则的一种体现。

表示层(UI)

实现与用户之间的交互,并将数据转交给相应的业务逻辑层。
在这里插入图片描述
从上图可以看出,三层架构中的表示层,其他可以基本上包括MVC模型中的V(View)视图以及C(Controller),而MVC模型中的M(model),往往基本上包括了三层架构中的业务逻辑层,数据访问层以及实体类。
所以,前面,我们已实现了三层架构中的业务逻辑层和数据访问层,这也是MVC模型中的M模型。
现在还需要实现表示层。表示层在MVC模型中可以分成View视图层以及Controller控制层。

EmpController控制类

该类实现根据用户的选择,调用业务逻辑层的相关方法来实现用户的需求。
在本例中,用户会根据界面的提示,输入数字来选择相应的功能,所以,在本控制类中,主要是实现根据数字来调用业务功能。并将业务处理的结果返回给View进行显示。
该类定义在com.javaoldman.controller包下
代码如下:

package com.javaoldman.controller;

import com.javaoldman.entity.Emp;
import com.javaoldman.service.EmpService;
import com.javaoldman.service.impl.EmpServiceImpl;

import java.util.ArrayList;
import java.util.List;

public class EmpController {

    EmpService service = new EmpServiceImpl();
    Emp emp = null;
    public EmpController(){
    }

    public void setEmp(Emp emp) {
        this.emp = emp;
    }

    public List<Emp> doByOption(int option){
        switch (option){
            case 1:
                return service.getAllEmps();
            case 2:
                Emp emp2 = service.getEmpByEmpno(emp.getEmpno());
                List<Emp> empList = new ArrayList<>();
                empList.add(emp2);
                return empList;
            case 3:
                service.deleteEmpByEmpno(emp.getEmpno());
                return service.getAllEmps();
            case 4:
                service.addEmp(emp);
                return service.getAllEmps();
            case 5:
                service.updateEmp(emp);
                return service.getAllEmps();
            default:
                return null;
        }
    }
}

View层将用户的输入包装成一个emp对象,然后将这个对象以及用户的选择号提交给controller对象,由Controller对象根据选择调用相应的业务逻辑类的方法。

EmpView类

该类实现与用户的交互,包括:

  • 输出功能:将菜单显示给用户
  • 输入功能:获取用户的输入
  • 提交给控制层功能:并最后将用户的输入包装成为一个emp对象,和用户选择一起,提交给Controller层
  • 获得控制层的返回数据功能:获得控制层的返回数据
  • 输出功能:并从Controller获得处理结果,并加以显示

该类定义在com.javaoldman.view包下
代码如下:

package com.javaoldman.view;

import com.javaoldman.controller.EmpController;
import com.javaoldman.entity.Emp;

import java.util.List;
import java.util.Scanner;

public class EmpView {
    public void displayMenu(){
        Scanner scanner = new Scanner(System.in);
        EmpController controller = new EmpController();

        Emp emp = new Emp();
        int option = 0;

        int empno;
        String ename;
        String job;
        int mgr;
        double sal;
        double comm;
        int deptno;
		//输出功能:显示菜单
        System.out.println("功能菜单");
        System.out.println("1 列出EMP表的所有记录");
        System.out.println("2 删除EMP表中指定EMPNO的记录,并列出删除后的所有记录");
        System.out.println("3 查询指定EMPNO的记录");
        System.out.println("4 添加新的记录,并列出添加后的所有记录");
        System.out.println("5 修改指定EMPNO的记录的字段信息,显示修改后的该记录");
        System.out.println("6 退出");

		//输入功能:获得用户的选择和输入
        while(true){
            System.out.println("请输入菜单前面的数字,将执行相应的功能:");
            try {
                option = scanner.nextInt();
            }catch (Exception e){  //如果输入的不是数字,提示重新输入
                System.out.println("只能输入数字,请重新输入!");
                continue;
            }
            switch (option){
                case 1:
                    break;
                case 2:
                    System.out.println("请输入要查询的雇员编号,要求是整数");
                    empno = scanner.nextInt();  //获得雇员编号

                    emp.setEmpno(empno);  //将empno值包装到emp对象中
                    break;
                case 3:
                    System.out.println("请输入要删除的雇员编号,要求是整数");
                    empno = scanner.nextInt();  //获得雇员编号

                    emp.setEmpno(empno);  //将empno值包装到emp对象中
                    break;
                case 4:
                    System.out.println("请输入要添加的雇员编号,要求是整数");
                    empno = scanner.nextInt();  //获得雇员编号
                    System.out.println("请输入要添加的雇员名称");
                    ename = scanner.next();
                    System.out.println("请输入要添加的雇员工作");
                    job = scanner.next();
                    System.out.println("请输入要添加的雇员的领导的编号,要求是整数");
                    mgr = scanner.nextInt();
                    System.out.println("请输入要添加的雇员工资,要求是数字");
                    sal = scanner.nextDouble();
                    System.out.println("请输入要添加的雇员资金,要求是数字");
                    comm = scanner.nextDouble();
                    System.out.println("请输入要添加的雇员部门编号,要求只能是10,20,30,40中的一个");
                    deptno = scanner.nextInt();

                    emp.setEmpno(empno);  //将empno值包装到emp对象中
                    emp.setEname(ename);
                    emp.setJob(job);
                    emp.setMgr(mgr);
                    emp.setSal(sal);
                    emp.setComm(comm);
                    emp.setDeptno(deptno);

                    break;
                case 5:
                    System.out.println("请输入要更新的雇员编号,要求是整数");
                    empno = scanner.nextInt();  //获得雇员编号
                    System.out.println("请输入要更新的雇员名称");
                    ename = scanner.next();
                    System.out.println("请输入要更新的雇员工作");
                    job = scanner.next();
                    System.out.println("请输入要更新的雇员的领导的编号,要求是整数");
                    mgr = scanner.nextInt();
                    System.out.println("请输入要更新的雇员工资,要求是数字");
                    sal = scanner.nextDouble();
                    System.out.println("请输入要更新的雇员资金,要求是数字");
                    comm = scanner.nextDouble();
                    System.out.println("请输入要更新的雇员部门编号,要求只能是10,20,30,40中的一个");
                    deptno = scanner.nextInt();

                    emp.setEmpno(empno);  //将empno值包装到emp对象中
                    emp.setEname(ename);
                    emp.setJob(job);
                    emp.setMgr(mgr);
                    emp.setSal(sal);
                    emp.setComm(comm);
                    emp.setDeptno(deptno);
                    break;
                case 6:
                    return; //如果是选择6,则退出程序
            }
            //将用户输入提交给控制层,并得到控制层的返回数据
            controller.setEmp(emp);  //值emp对象给Controller类
            List<Emp> empList =  controller.doByOption(option);
            
            //输出功能:对从控制层返回的数据进行显示
            for(Emp emp1:empList){
                System.out.println(emp1);
            }
        }
    }
}

Client客户类

客户类作为整个功能的测试类,代码如下:

package com.javaoldman;

import com.javaoldman.view.EmpView;

public class Client
{
    public static void main( String[] args )
    {
        EmpView view = new EmpView();
        view.displayMenu();
    }
}

结束语

本文以oracle数据库样本库中的emp表为例,按三层架构和MVC模式,对类进行了设计,并满足开闭原则和依赖倒置原则。
下图说明了这些类之间的依赖和实现关系
在这里插入图片描述
下图为该例中所有类文件的包结构图
在这里插入图片描述
此文仅供同学们参考,希望能帮助同学们理解java利用JDBC访问数据库的主要结构和方法。特别是帮助理解三层架构与MVC之间的关系和各类各自的职责。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java Man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值