Java工程师-17周Java数据库开发基础-第三章(JDBC)

84 阅读8分钟

第三章 JDBC

image.png

3.1 JDBC的API

image.png

image.png

image.png

下载MySQL 8版本的jdbc驱动

MySQL :: MySQL Connectors

image.png

image.png

下载之后解压,找到里面的驱动,将驱动复制到项目工程的lib文件夹

image.png

进行工程配置,点击Project Settings,点击Module

image.png

image.png

image.png

image.png

image.png

image.png

image.png

JDBC应用本身是CS架构的

image.png

实际使用时,一般是完整的格式,不会使用默认值,数据库是独立部署的,为了安全,端口号也不会使用3306

image.png

  • useSSL:网络传输使用非对称加密
  • useUnicode:一般含有中文的网页都需要使用unicode编码
  • characterEncoding:具体编码方式
  • serverTimezone:服务器时区
  • allowPublicKeyRetrieval:允许从客户端获取公钥加密传输,一般开启

创建数据库连接以及异常处理:

public static void main(String[] args) {
    try {
        //1. 注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 使用DriverManager获取新的数据库连接
        String url = "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "zzzz";
        Connection connection = DriverManager.getConnection(url,username,password);
        System.out.println(connection);
    } catch (ClassNotFoundException e) {
        //ClassNotFoundException:JDBC驱动不存在的时候抛出
        throw new RuntimeException(e);
    } catch (SQLException e) {
        //SQLException:所有JDBC操作失败的异常
        throw new RuntimeException(e);
    }
}

3.2 JDBC的查询操作以及SQL注入漏洞

3.2.1 实现按部门查询员工的功能

public class QueryCommand implements Command {
    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            /**
             * 在实现类中实现按部门查询的方法
             * 标准的JDBC五个步骤
             */
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "zzzz");
            //3. 创建Statement对象(用于执行sql语句)
            stmt = conn.createStatement();
            //结果集
            System.out.println("select * from employee where dname='" + pdname + "'");
            rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){ //!=null说明已经被实例化了
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

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

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.2.2 SQL注入攻击

image.png

SQL注入起因是没有对引号进行处理,从而改变原有的SQL语句。导致原有的查询条件失效。虽然不会影响程序的运行,但是会造成数据泄露。

3.2.3 PreparedStatement预编译SQL

image.png

image.png

image.png

image.png

`?`是参数,要传入的数值,后面是要被preparedStatement解析的
public class PstmtQueryCommand implements Command {
    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            /**
             * 在实现类中实现按部门查询的方法
             * 标准的JDBC五个步骤
             */
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "zzzz");
            //3. 创建Statement对象(用于执行sql语句)
            String sql = "select * from employee where dname=? and eno > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1,pdname);
            pstmt.setInt(2, 3500);
            //结果集
            rs = pstmt.executeQuery();
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){ //!=null说明已经被实例化了
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

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

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.3 JDBC工具类的抽取以及增删改

3.3.1 封装DBUtils工具类

DBUtils主要是封装打开连接和关闭连接

public class DBUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public static Connection getConnection() throws SQLException, ClassNotFoundException {
        //异常先直接抛出,实际调用的时候再去捕获
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "zzzz");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn) {
        try {
            if(rs != null){ //!=null说明已经被实例化了
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

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

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

3.3.2 JDBC实现新增数据

image.png

cnt是影响数据库记录的条数

public class InsertCommand implements Command {
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);

        System.out.println("请输入员工编号");
        int eno = in.nextInt();

        System.out.println("请输入员工姓名");
        String ename = in.next();

        System.out.println("请输入员工薪资");
        float salary = in.nextFloat();

        System.out.println("请输入员工部门");
        String dname = in.next();

        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            conn = DBUtils.getConnection();
            String sql = "insert into employee(eno, ename, salary, dname) values (?,?,?,?);";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, eno);
            pstmt.setString(2, ename);
            pstmt.setFloat(3, salary);
            pstmt.setString(4, dname);

            int cnt = pstmt.executeUpdate(); //所有改变数据表的操作都是executeUpdate()
            System.out.println("数据库更新的记录条数为:"+cnt);
            System.out.println(ename + "员工入职手续已办理");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtils.closeConnection(null,pstmt,conn);
        }
    }
}

3.3.3 实现JDBC更新与删除数据的操作

image.png

image.png

public class UpdateCommond implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        System.out.println("请输入员工薪资");
        float salary = in.nextFloat();

        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DBUtils.getConnection();
            String sql = "update employee set salary = ? where eno = ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setFloat(1, salary);
            pstmt.setInt(2, eno);
            int cnt = pstmt.executeUpdate();
            if (cnt == 1) {
                System.out.println("员工薪资调整完毕");
            } else {
                System.out.println("未找到"+ eno + "编号员工数据");
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtils.closeConnection(null, pstmt, conn);
        }

    }
}
public class DeleteCommond implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();

        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DBUtils.getConnection();
            String sql = "delete from employee where eno = ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, eno);
            int cnt = pstmt.executeUpdate();
            if (cnt == 1) {
                System.out.println("员工离职手续办理完成");
            } else {
                System.out.println("未找到"+ eno + "编号员工数据");
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtils.closeConnection(null, pstmt, conn);
        }

    }
}

3.4 JDBC的事务操作

事务:要么把事情一次性做完,要么什么都不做

image.png

image.png

image.png

image.png

image.png

使用批量导入员工举例,要么全部导入,要么全部取消

/**
 * JDBC中的事务控制
 */
public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = pstmt.getConnection();
            conn.setAutoCommit(false); //开启手动提交事务
            String sql = "insert into employee(eno, ename, salary, dname) values (?, ?, ?, ?)";
            for (int i = 1000; i < 1999; i++) {
                if (i == 1005) {
                    throw new RuntimeException("提交失败");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, i);
                pstmt.setString(2,"员工"+i);
                pstmt.setFloat(3,4000);
                pstmt.setString(4, "市场部");
                pstmt.executeUpdate(); //手动提交时,中间数据会放入到事务区中
            }
            conn.commit(); //提交数据
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //数据回滚
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            DBUtils.closeConnection(null, pstmt, conn);
        }
    }
}

3.5 基于实体类的封装分页

while (rs.next()) {
    Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
    String ename = rs.getString("ename");
    Float salary = rs.getFloat("salary");
    String dname = rs.getString("dname");
    System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
}

之前在查询时,只是把查询结果放入一个一个变量,而Java是变量对象的语言,实际开发中应该将查询结果封装到一个实体类中,再将一个一个实体类放入一个集合当中进行存储

/**
 * 员工实体类
 */
public class Employee {
    /**
     * JavaBean有书写格式的要求:
     * 1. 具备默认构造函数
     * 2. 属性私有
     * 3. 存在getter和setter
     */
    public Employee() {}

    //私有属性通常与数据库字段相同
    private int eno;
    private String ename;
    private float salary;
    private String dname;

    public int getEno() {
        return eno;
    }

    public void setEno(int eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

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

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }
}

封装完实体类后,要进行分页查询逻辑

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List list = new ArrayList<Employee>();

        try {
            conn = DBUtils.getConnection();
            //mysql分页依靠limit关键字实现
            String sql = "select * from employee limit ?, 10"; //从?开始,取10条记录
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, (page - 1) * 10);
            rs = pstmt.executeQuery();
            while(rs.next()) {
                Integer eno = rs.getInt("eno");
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //封装到实体类中
                Employee emp = new Employee();
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                //将实体类放入集合中
                list.add(emp);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtils.closeConnection(rs, pstmt, conn);
        }
    }
}

3.6 JDBC数据批处理

pstmt = conn.prepareStatement(sql);
for (int i=200000;i<300000;i++){
    pstmt.setInt(1, i);
    pstmt.setString(2, "员工" + i);
    pstmt.setFloat(3, 4000f);
    pstmt.setString(4, "市场部");
    pstmt.addBatch();//将参数加入批处理任务
    //pstmt.executeUpdate();
}
pstmt.executeBatch();//执行批处理任务
conn.commit();//提交数据

一条SQL附带100000组sql参数,节省了解析sql的时间

3.7 Druid连接池

程序启动时,统一对数据库连接进行统一管理;当程序需要数据库连接时,不是由程序自己创建,而是由连接池进行分配。

image.png

image.png

在druid的github官网下载jar包,然后加入到项目依赖中;随后在src根目录下创建配置文件druid-config.properties

driverClassName=com.mysql.cj.jdbc.Driver  
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true  
username=root  
password=zzzz

initialSize=10
maxActive=20 #最大连接数,超过最大连接数,其他应用程序将进行等待
/**
 * Druid连接池配置与使用
 */
public class DruidSample {
    public static void main(String[] args) {
        //1. 加载属性文件
        Properties properties = new Properties();
        //DruidSample.class.getResource获取当前类路径下对象文件的路径
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        try {
            //防止路径中的中文和空格干扰
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8");
            properties.load(new FileInputStream(propertyFile));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //2. 获取DataSource数据源对象(用DataSource指代要操作的数据库是什么)
            DataSource dataSource =  DruidDataSourceFactory.createDataSource(properties);
            //3. 创建数据库连接
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0, 10");
            rs = pstmt.executeQuery();
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //不使用连接池,conn.close直接关闭连接,而使用连接池,是将连接回收至连接池当中
            DBUtils.closeConnection(rs, pstmt, conn);
        }
    }
}

3.8 数据库工具组件

image.png

public class DBUtilsSample {
    public static void query() {
        Properties properties = new Properties();
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        try {
            //防止路径中的中文和空格干扰
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8");
            //加载配置文件
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource =  DruidDataSourceFactory.createDataSource(properties);
            //传入dataSource,就知道对哪一个数据库进行操作
            QueryRunner qr = new QueryRunner(dataSource);

            //BeanListHandler自动的将结果转为实体类
            List<Employee> list = qr.query("select * from employee limit ?, 10", //sql
                            new BeanListHandler<>(Employee.class), //每一条记录转换为那个实体对象
                            new Object[]{10}); //数组中的值与?一一对应

            for(Employee emp: list) {
                System.out.println(emp.getEname());
            }
            //不需要关闭连接
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void update() {
        Properties properties = new Properties();
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        Connection conn = null;
        try {
            //防止路径中的中文和空格干扰
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8");
            //加载配置文件
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource =  DruidDataSourceFactory.createDataSource(properties);
            //读写离不开事务,事务基于连接的,update就需要获取连接
            conn = dataSource.getConnection();

            //手动提交
            conn.setAutoCommit(false);
            String sql1 = "update employee set salary = salary + 1000 where eno = ?";
            String sql2 = "update employee set salary = salary - 500 where eno = ?";
            QueryRunner qr = new QueryRunner(); //查询需要传入数据源 写入的时候不需要
            qr.update(conn, sql1, new Object[]{1000});
            qr.update(conn, sql2, new Object[]{1001});
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        query();
        update();
    }
}