整体介绍

◆JDBC快速入门 ◆精讲JDBC开发细节 ◆连接池与DBC进阶使用

主要知识点

◆JDBC使用步骤 ◆数据库查询方法 ◆数据库写入方法 ◆SQL注入攻击的应对 ◆连接池的使用 Apache Commons DBUtils

JDBC快速入门

什么是JDBC

◆Java数据库连接-Java DataBase Connectivity ◆JDBC可让Java通过程序操作关系型数据库 ◆JDBC基于驱动程序实现与数据库的连接与操作

什么是驱动程序

驱动是每个厂商自己提供的,主要主板内安装了相应驱动,才能识别usb口插入的是什么设备 为啥我们插上设备就能用,没有安装驱动,那是windows内置了很多常见设备的驱动,不用我们自己安装,当使用高级设备如具备宏命令的鼠标时,就需要在相应的官网安装下载相应驱动了

JDBC驱动程序

JAVA无法为每种数据库提供对应的接口,只能提供统一接口JDBC,让每种数据库使用对应的厂商驱动即jar包进行对接访问,JDBC就能通过这个驱动识别数据库并对其访问操作了平台提供标准,厂商提供实现

JDBC的优点

◆统一的API,提供一致的开发过程 ◆易于学习,容易上手,代码结构稳定 ◆功能强大,执行效率高,可处理海量数据

快速上手JDBC

JDBC开发流程

1.加载并注册JDBC驱动 2.创建数据库连接 3.创建Statement对象 4.遍历查询结果 5.关闭连接,释放资源

准备:在mysql的HL连接下新建imooc数据库,或者导入imooc.sql文件

新建java类项目文件

浏览器搜索”所需驱动名“+ driver ,在各个厂商官网即可找到驱动 mysql jar

创建lib文件夹,将mysql的驱动jar包复制粘贴进去 在工程结构中,选择模块依赖’+‘号JAR

1.加载并注册JDBC驱动

//1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
  1. 创建数据库连接
//2. 创建数据库连接
Connection conn = null;
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
  1. 创建Statement对象
//3. 创建Statement对象
            stmt = conn.createStatement();
            //结果集
            System.out.println("select * from employee where dname='" + pdname + "'");
            rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
  1. 遍历查询结果
//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);
 
            }
  1. 关闭连接,释放资源
try{
   前面四步
} catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != 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();
            }
        }

注意:此处我的数据库的密码为空,所以设置如下:

//2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "");

创建数据库连接代码

Class.forName的作用

◆Class.forName用于加载指定的JDBC驱动类 ◆Class.forName本质是通知JDBC注册这个驱动类 ◆驱动由数据库厂商自行开发,连接字符串也不同 数据库存储非常重要,一般需要采购服务器进行存储,所以有对应ip和端口号,所以通过连接字符串进行表达

数据库与连接字符串

数据库连接配置

DriverManager

◆DriverManager用于注册/管理JDBC驱动程序 ◆DriverManager.getConnection(连接字符串,用户名,密码) ◆返回值Connection对象,对应数据库的物理网络连接

Connection对象

◆Connection对象用于DBC与数据库的网络通信对象 ◆java.sql.Connection是一个接口,具体由驱动厂商实现 ◆所有数据库的操作都建立在Connection基础上

可以不只建立一个connection对象连接数据库服务器 形成连接池,当JDBC连接服务器如何知道ip和端号以及哪个数据库? 这时需要连接字符串

MySQL连接字符串

◆格式:jdbc:mysql:/[主机ip][:端口]/数据库名?参数列表 ◆主机ip与端口是可选设置,默认值为127.0.0.1与3306 ◆参数列表采用u编码,格式:参数1=值1&参数2=值2&…

常用参数

局域网开发可以将useSSL设置为false,部署上线时最好设置为true 若工作时发现字符串没有包含其中某些字段,可能说明服务器端已经默认设置好了

创建connection和异常处理

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
public class ConnectionSample {
    public static void main(String[] args) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url="jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
            Connection conn = DriverManager.getConnection(url,"user","password");
            System.out.println(conn);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

allowPublicKeyRetrieval=true表示允许从客户端获取公钥加密传输,在生产开发环境中可以设置user为root,但是生产部署中不要对外开放root,因为权限太大,一旦密码泄露,后果不堪设想

记得选中JDBC五步骤,点击codesurround withtry/catch可以自动捕获异常

实现按部门查询员工功能

使用接口进行封装,新建Command.java接口

package com.imooc.jdbc.hrapp.command;
 
public interface Command {
    public void execute();
}

新建HumanResourceApplication.java文件,是app功能

package com.imooc.jdbc.hrapp;
 
import com.imooc.jdbc.hrapp.command.*;
 
import java.util.Scanner;
 
public class HumanResourceApplication {
    public static void main(String[] args) {
        System.out.println("1-查询部门员工");
        Scanner in = new Scanner(System.in);
        Integer cmd = in.nextInt();
        Command command = null;
        switch (cmd){
            case 1://查询部门员工
                command = new PstmtQueryCommand();
                command.execute();
                break;
           //后续增加增删查改功能
        }
    }
}

新建QueryCommand.java文件,实现查询功能,实现上面的接口

package com.imooc.jdbc.hrapp.command;
 
import java.sql.*;
import java.util.Scanner;
 
/**
 * 数据查询方法
 */
public class QueryCommand implements Command {
    @Override
    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 {
            //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", "root");
            //3. 创建Statement对象
            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){
                    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();
            }
        }
    }
}

rs里面有一系列针对字段的方法,根据字段的数据类型进行选择 JDBC中字段索引从1开始,而非0 代码有非常严重的内存泄露风险,导致可以进行sql注入攻击

SQL注入攻击

输入特殊格式字符从而嵌入到原有sql中破环原有sql筛选条件导致数据破坏泄露的攻击方式称之为sql注入攻击

根本原因是我们没有考虑处理单引号这样的特殊字符

PreparedStatement预编译SQL

用以解决sql注入攻击问题

SQL注入

◆SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为 ◆SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理 解决方法:放弃Statement改用PreparedStatement处理SQL

PreparedStatement

◆PreparedStatement预编译Statement是Statement的子接口 ◆PreparedStatement对SQL进行参数化,预防SQL注入攻击 ◆PreparedStatement比Statement执行效率更高

使用PreparedStatement

符号会进行转义处理

//3. 创建PreparedStatement对象
            String sql = "select * from employee where dname=? and eno > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1,pdname); //注意:参数索引从1
            pstmt.setInt(2,3500);

使用?占位,setString、setInt传入输入信息

JDBC实现写数据

新增、修改、删除数据

封装DbUtils工具类

将JDBC的重复五个步骤封装 实现打开和关闭两个接口 在文件下新建common文件夹,新增DbUtils。java类 实行封装,抛出异常:

package com.imooc.jdbc.common;
 
import java.sql.*;
 
public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public static Connection getConnection() throws SQLException, ClassNotFoundException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }
 
    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs , Statement stmt , Connection conn){
        try {
            if(rs != 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();
        }
    }
}

JDBC实现新增数据

JDBC执行INSERT语句

所有写操作都要使用execute.Update方法,而且不返回结果集,而是整数,表示写操作所影响行数 使用DbUtils封装工具类来实现新增数据操作

package com.imooc.jdbc.hrapp.command;
 
import com.imooc.jdbc.common.DbUtils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Scanner;
 
/**
 * 新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.print("请输入员工编号:");
        int eno = in.nextInt();
        System.out.print("请输入员工姓名:");
        String ename = in.next();
        System.out.print("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.print("请输入隶属部门:");
        String dname = in.next();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "insert into employee(eno,ename,salary,dname,hiredate ) 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:" + cnt);
            System.out.println(ename + "员工入职手续已办理");
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}

调用封装的接口conn = DbUtils.getConnection();,表示打开JDBC连接mysql数据库,使用四个进行占位,然后后面使用一系列set方法传入值,最后记得调用关闭接口进行资源释放DbUtils.closeConnection(null,pstmt,conn);,没有生成结果集,所以使用null 实现了第二个APP的功能:

package com.imooc.jdbc.hrapp;
 
import com.imooc.jdbc.hrapp.command.*;
 
import java.util.Scanner;
 
public class HumanResourceApplication {
    public static void main(String[] args) {
        System.out.println("1-查询部门员工");
        System.out.println("2-办理员工入职");
        System.out.println("请选择功能:");
        Scanner in = new Scanner(System.in);
        Integer cmd = in.nextInt();
        Command command = null;
        switch (cmd){
            case 1://查询部门员工
                command = new PstmtQueryCommand();
                command.execute();
                break;
            case 2:
                command = new InsertCommand();
                command.execute();
                break;
      }
    }
  }

JDBC实现更新与删除数据

JDBC执行UPDATE语句

JDBC执行DELETE语句

此处cnt的值为0或1,0为不存在,1为存在删除

更新操作:

package com.imooc.jdbc.hrapp.command;
 
import com.imooc.jdbc.common.DbUtils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
 
/**
 * 员工调薪
 */
public class UpdateCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.print("请输入员工编号:");
        int eno = in.nextInt();
        System.out.print("请输入员工新的薪资:");
        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) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}

删除操作:

package com.imooc.jdbc.hrapp.command;
 
import com.imooc.jdbc.common.DbUtils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
 
/**
 * 删除员工数据
 */
public class DeleteCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.print("请输入员工编号:");
        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.setFloat(1, eno);
            int cnt = pstmt.executeUpdate();
            if(cnt == 1){
                System.out.println("员工离职手续已完成");
            }else{
                System.out.println("未找到" + eno + "编号员工数据");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}

APP的增删查改功能

package com.imooc.jdbc.hrapp;
 
import com.imooc.jdbc.hrapp.command.*;
 
import java.util.Scanner;
 
public class HumanResourceApplication {
    public static void main(String[] args) {
        System.out.println("1-查询部门员工");
        System.out.println("2-办理员工入职");
        System.out.println("3-调整薪资");
        System.out.println("4-员工离职");
        System.out.println("请选择功能:");
        Scanner in = new Scanner(System.in);
        Integer cmd = in.nextInt();
        Command command = null;
        switch (cmd){
            case 1://查询部门员工
                command = new PstmtQueryCommand();
                command.execute();
                break;
            case 2:
                command = new InsertCommand();
                command.execute();
                break;
            case 3:
                command = new UpdateCommand();
                command.execute();
                break;
            case 4:
                command = new DeleteCommand();
                command.execute();
                break;
        }
    }
}

JDBC中的事务操作

什么是事务

◆事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元 ◆说人话:要么把事情做完,要么什么都不做,不要做一半 ◆事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带

事务的提交操作

案例:韩梅梅向李雷借了100元,韩梅梅余额加100,李雷余额减100,这是一个完整事务,我们不需要两次写入到数据表,我们可以不先更新到数据表,而更新到事务区,等到韩梅梅与李雷的数据全部修改后,再一次性写入到数据表,然后事务区清空,等待下一次事务

事物的回滚操作

回滚后不会对数据表产生任何影响

JDBC两种事务模式

◆JDBC允许两种事务模式 ◆自动提交事务模式 ◆手动提交事务模式

自动提交事务模式

◆自动提交模式是指每执行一次写操作SQL,自动提交事务 ◆自动提交开启方法:conn.setAutoCommit(true) ◆自动事务是DBC默认行为,此模式无法保证多数据一致性

手动提交事务模式

◆手动提交模式是指显式调用commit0与rollback()方法管理事务 ◆手动提交开启方法:conn.setAutoCommit(false) ◆手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法

package com.imooc.jdbc.sample;
 
import com.imooc.jdbc.common.DbUtils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
 
/**
 * JDBC中的事务控制
 */
public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            //JDBC默认使用自动提交模式
            String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i=1000;i<2000;i++){
                if(i==1005){
                    throw new RuntimeException("插入失败");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, i);
                pstmt.setString(2, "员工" + i);
                pstmt.setFloat(3, 4000f);
                pstmt.setString(4, "市场部");
                pstmt.executeUpdate();
            }

我们想要插入1000条员工数据,当执行到1005时会产生异常,这时我们想要撤回已经写入的5条数据并修改后重新再次提交到数据表,他是无法完成的,导致数据不完整,不因为是默认自动提交,所以我们需要使用事务控制

这时我们需要关闭自动提交,执行executeUpdate时,就会提交到事务区 ,需要自己手动提交,出现异常,自动回滚

package com.imooc.jdbc.sample;
 
import com.imooc.jdbc.common.DbUtils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
 
/**
 * JDBC中的事务控制
 */
public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            //JDBC默认使用自动提交模式
            conn.setAutoCommit(false);//关闭自动提交
            String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i=1000;i<2000;i++){
                if(i==1005){
                    //throw new RuntimeException("插入失败");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, i);
                pstmt.setString(2, "员工" + i);
                pstmt.setFloat(3, 4000f);
                pstmt.setString(4, "市场部");
                pstmt.executeUpdate();
            }
            conn.commit();//提交数据
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback();//捕捉异常回滚数据
                }
            }catch (SQLException ex){
                ex.printStackTrace();
            }
        } finally {
            DbUtils.closeConnection(null, pstmt, conn);
        }
    }
}

如果插入失败后,一条数据也不会写入到数据表中,必须全部成功才会写入数据表中

基于实体类实现分页数据封装

数据的处理不单是打印到屏幕这们简单,在日常开发中,通常将数据转化成一个个实体封装在集合中,即使结果集关闭,也不会影响到我们封装的数据,而且是通过面向对象的方法进行存储,后期更容易被我们的JAVA进行处理

员工实体类构造方法:

package com.imooc.jdbc.hrapp.entity;
 
import java.util.Date;
 
/**
 * 员工实体类
 */
public class Employee {
    /**
     * 1. 具备默认构造函数
     * 2. 属性私有
     * 3. 存在getter与setter
     */
    public Employee(){
 
    }
 
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;
    private Date hiredate;
 
    public Integer getEno() {
        return eno;
    }
 
    public void setEno(Integer 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 Date getHiredate() {
        return hiredate;
    }
 
    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
}

通常属性与mysql字段一致 进行分页查询操作: 新建PaginationCommond.java文件,类似上面的增删查改方式,实现Commond的接口

package com.imooc.jdbc.hrapp.command;
 
import com.imooc.jdbc.common.DbUtils;
import com.imooc.jdbc.hrapp.entity.Employee;
 
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
 
/**
 * 分页查询员工数据
 */
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<Employee> list = new ArrayList();
        try {
            conn = DbUtils.getConnection();
            String sql = "select * from employee limit ?,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);
                emp.setHiredate(hiredate);
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

每页10条数据,依托mysql独有方言特殊关键字limit,?表示从第几行开始,后面数字表示截取后面的几行记录 注意mysql页数索引从0开始

JDBC中Date日期对象处理

新增字段hiredate字段,默认date类型,保持到日期的天数 注意选择为java.util下的包

//JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
System.out.print("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.String转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取自1970年到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time);

JDBC批处理

package com.imooc.jdbc.sample;
 
import com.imooc.jdbc.common.DbUtils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
 
/**
 * JDBC批处理
 */
public class BatchSample {
    //标准方式插入若干数据
    private static void tc1(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            //JDBC默认使用自动提交模式
            conn.setAutoCommit(false);//关闭自动提交
            String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i=100000;i<200000;i++){
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, i);
                pstmt.setString(2, "员工" + i);
                pstmt.setFloat(3, 4000f);
                pstmt.setString(4, "市场部");
                pstmt.executeUpdate();
            }
            conn.commit();//提交数据
            long endTime = new Date().getTime();
            System.out.println("tc1()执行时长:" + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback();//回滚数据
                }
            }catch (SQLException ex){
                ex.printStackTrace();
            }
        } finally {
            DbUtils.closeConnection(null, pstmt, conn);
        }
    }
 
 
    //使用批处理插入若干数据
    private static void tc2(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            //JDBC默认使用自动提交模式
            conn.setAutoCommit(false);//关闭自动提交
            String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
            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();//提交数据
            long endTime = new Date().getTime();
            System.out.println("tc2()执行时长:" + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback();//回滚数据
                }
            }catch (SQLException ex){
                ex.printStackTrace();
            }
        } finally {
            DbUtils.closeConnection(null, pstmt, conn);
        }
    }
 
 
    public static void main(String[] args) {
        tc1();
        tc2();
    }
}

tc1()是新增100000个对象,每个对象新增1个数据 tc2()是创建一个对象,新增100000条数据

tc1()创建10万个prepareStatement(sql),而tc2()只需创建一个,发送一条sql语句,覆盖10万个参数,再由mysql一次性进行批处理,相反tc1()需要发送10万条sql语句,每一条都相同。浪费了时间

连接池和JDBC的进阶使用

连接池类似生活中的物资仓库,每使用一次mysql就要创建一个JDBC的连接,耗费时间,将连接预算放入连接池,直接在连接池提取使用即可,用完可以还到连接池,继续分配他人使用

阿里巴巴Druid连接池

◆Duid是阿里巴巴开源连接池组件是最好的连接池之一 ◆Duid对数据库连接进行有效管理与重用,最大化程序执行效率 ◆连接池负责创建管理连接,程序只负责取用与归还

JDBC程序一假设为负责数据的新增,JDBC程序二假设负责数据的查找,都需要与数据库建立连接,耗费时间,我们需要建立一个连接池,让JDBC与连接池通信即可,连接池可以在程序启动时创建,也可以在第一个JDBC与连接池通信时创建,其它JDBC分配即可

Druid连接池的配置与使用

alibaba/druid: 阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池 (github.com) 类似mysql的jar包导入方法,将druid的jar包导入lib目录 src下创建druid-config.properties进行druid配置,使其具有良好的维护性

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=********
initialSize=10
maxActive=20

initialSize=10表示连接池创建时默认创建10个连接,maxActive=20表示如果实际过程不够用,连接池会额外创建10个连接,即最多不大于20个,如果运行过程中达到上限,就会处于等待状态造成阻塞,等待其他使用的程序调用close()方法,将连接池回收

一般将这两个数值设为一致数值,一开始就分配好所有资源,提升性能和管理

执行完成后会全部消失,回收到连接池

新建DruidSample.java文件

package com.imooc.jdbc.sample;
 
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.common.DbUtils;
 
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
 
/**
 * Druid连接池配置与使用
 */
public class DruidSample {
    public static void main(String[] args) {
        //1. 加载属性文件
        Properties properties = new Properties();
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        //空格->%20 | c:\java code\druid-config.properties
        //c:\java%20code\druid-config.properties
        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //2. 获取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 empId = rs.getInt(1);
                String ename = rs.getString("ename");
                String dname = rs.getString("dname");
                Float salary = rs.getFloat("salary");
                System.out.println(dname + "-" + empId + "-" + ename  + "-" + salary);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            /**
             * 不使用连接池:conn.close()关闭连接
             * 使用连接池:conn.close()将连接回收至连接池
             */
             //之前封装的Dbutils工具
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }

配置文件会随着发布到out目录下,所以使用DruidSample.class.getResource("/druid-config.properties").getPath();获取类路径文件下的文件路径 propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");是为了容错考虑,防止文件路径中有中文或者空格,使用getpath方法会对路径进行编码,如空格变为%20,decode就是将%20还原成空格 properties.load(new FileInputStream(propertyFile));需要传入一个文件输入流,所以文件使用new FileInputStream()进行包裹

Apache Commons DBUtils

◆commons-dbutils是Apache提供的开源JDBC工具类库 ◆它是对DBC的简单封装学习成本极低 ◆使用commons-dbutils可以极大简化JDBC编码工作量 DbUtils – Download Apache Commons DbUtils 继续类似上面方法将jar包添加到lib文件夹

package com.imooc.jdbc.sample;
 
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.common.DbUtils;
import com.imooc.jdbc.hrapp.entity.Employee;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
 
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
 
/**
 * Apache DBUtils + Druid联合使用演示
 */
public class DbUtilsSample {
    private static void query(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //利用Apache DbUtils大幅简化了数据的提取过程
            QueryRunner qr = new QueryRunner(dataSource);
            List<Employee> list =  qr.query("select * from employee limit ?,10",
                    new BeanListHandler<>(Employee.class),
                    new Object[]{10});
            for (Employee emp : list) {
                System.out.println(emp.getEname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    public static void update(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.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);
            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();
    }
}

Apache Commons DBUtils没有结果集用来存储数据,所以使用new BeanListHandler<>(Employee.class)转换为实体类进行存储,要求必须是属性和字段一一对应的上,‘?’的参数的传递使用new Object[]{10}表示从第十行开始截取,{}中的数值对应’?‘里要传入的数值,使用集合进行接收,使用 for (Employee emp : list)进行遍历后可以打印或者后续处理

注意,在执行query方法的时候,Apache Commons DBUtils会自动连接连接池,执行完后会自动关闭,不需要我们手动打开和关闭 所以利用Apache DbUtils大幅简化了数据的提取过程

然而在执行非query查询方法的时候,如update更新方法,就需要自己创建与数据库的连接,update不止用于更新操作,加入删除等写操作都须使用update方法