JDBC

143 阅读21分钟

JDBC介绍

JDBC是Java提供的一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可.同的数据库厂商,需要针对这套接口需提供不同实现

JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并到得到返回结果等各类操作,相关类和接口在java.sql包与javax.sql(扩展包)中

前置工作

  • mysql官网下载mysql-connector-java-8.0.30.jar
  • 在项目目录下创建一个文件夹lib,将jar文件放入,在idea中选该jar文件右键选择添加到项目

JDBC编写步骤

  1. 注册驱动 加载Driver类

  2. 获取连接 得到Connection 客户端到数据库的连接

  3. 执行sql语句 发送sql命令给mysql执行

  4. 释放资源 关闭相关连接

连接的五种方式

方式一

package src.day0824;

import com.mysql.cj.jdbc.Driver;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class Demo03 {
    public static void main(String[] args) throws SQLException {
        // 1 注册驱动
        Driver driver = new Driver();// 创建driver对象

        // 2 得到连接
        /*
         * jdbc:mysql://  是规定好的,通过jdbc的方式连接mysql
         * localhost      主机,远程连接也可以写成IP地址
         * 3306           表示mysql监听的端口
         * java           表示具体连接哪个数据库,这里连接的数据库名字就叫"java"
         * mysql连接的本质就是socket连接
         *  */
        String url = "jdbc:mysql://localhost:3306/java";
        // 将用户名和密码放入到Properties对象
        Properties properties = new Properties();
        properties.setProperty("user", "root");// 第一个参数user是规定好的,第二个参数填用户名
        properties.setProperty("password", "root");// 同上,第二个参数填密码
        // 连接数据库,返回的是一个网络连接,存到了connect对象中
        Connection connect = driver.connect(url, properties);

        // 3 执行sql语句
        String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
        // 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
        System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功

        // 4 关闭连接资源
        // 不关闭连接会浪费服务器端口
        statement.close();
        connect.close();
    }
}

方式二

和方式一区别就是使用反射加载Driver类,更加灵活

package src.day0824;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class Demo04 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        
        // 1 使用反射加载Driver类,动态加载,更加灵活,减少依赖性
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) aClass.newInstance();

        // 2 得到连接
        String url = "jdbc:mysql://localhost:3306/java";
        // 将用户名和密码放入到Properties对象
        Properties properties = new Properties();
        properties.setProperty("user", "root");// 第一个参数user是规定好的,第二个参数填用户名
        properties.setProperty("password", "root");// 同上,第二个参数填密码
        // 连接数据库,返回的是一个网络连接,存到了connect对象中
        Connection connect = driver.connect(url, properties);

        // 3 执行sql语句
        String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
        // 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
        System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功

        // 4 关闭连接资源
        // 不关闭连接会浪费服务器端口
        statement.close();
        connect.close();
    }
}

方式三

和第二种不同的是使用DriverManager替换Driver,进行统一管理

package src.day0824;

import java.sql.*;

public class Demo05 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        // 1 使用反射加载Driver
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) aClass.newInstance();

        // 2 得到连接
        String url = "jdbc:mysql://localhost:3306/java";
        String user = "root";
        String password = "root";
        DriverManager.registerDriver(driver);// 注册Driver驱动
        Connection connect = DriverManager.getConnection(url, user, password);// 用DriverManager来进行连接的获取


        // 3 执行sql语句
        String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
        // 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
        System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功

        // 4 关闭连接资源
        // 不关闭连接会浪费服务器端口
        statement.close();
        connect.close();
    }
}

方式四

使用Class.forName自动完成注册驱动,简化代码,用的最多,推荐使用

package src.day0824;

import java.sql.*;

public class Demo06 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        /*
        在加载Driver类时,完成注册,静态代码块在类加载时会执行一次
        DriverManager.registerDriver(new Driver());执行
        注册Driver的工作已经完成
        
        源码
        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        } */
        
        // 1 使用反射加载Driver类
        Class.forName("com.mysql.cj.jdbc.Driver");

        // 2 得到连接
        String url = "jdbc:mysql://localhost:3306/java";
        String user = "root";
        String password = "root";
        Connection connect = DriverManager.getConnection(url, user, password);

        // 3 执行sql语句
        String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
        // 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
        System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功

        // 4 关闭连接资源
        // 不关闭连接会浪费服务器端口
        statement.close();
        connect.close();
    }
}

方式五

在第四中的基础上写个配置文件,将用户名密码写在配置文件中,更灵活,最为推荐使用

下面是名为mysql.properties的配置文件

user=root
password=root
url=jdbc:mysql://localhost:3306/java
driver=com.mysql.cj.jdbc.Driver
package src.day0824;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class Demo07 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
        // 从配置文件中得到相关的值
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\day0912\\mysql.properties"));
        // 相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        // 1 使用反射加载Driver类(可以不写这句话,看下面提示)
        Class.forName(driver);

        // 2 得到连接
        Connection connect = DriverManager.getConnection(url, user, password);

        // 3 执行sql语句
        String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
        // 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
        System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功

        // 4 关闭连接资源
        // 不关闭连接会浪费服务器端口
        statement.close();
        connect.close();
    }
}

提示

Class.forName("com.mysql.cj.jdbc.Driver");不写也行,因为从jdk1.5以后使用了jdbc4,不再需要显式调用class.forName()注册驱动而是自动调用驱动,jar包下META-INF\services\java.sql.Driver文本中的类名称去注册,但是建议还是写上Class.forName("com.mysql.cj.jdbc.Driver"),更加明确

结果集

结果集(ResultSet),表示数据库结果集的数据表,通常通过执行查询数据库的语句生成

ResultSet对象保持一个光标指向其当前的数据行.最初,光标位于第一行之前.next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集

package src.month09.day0913.demo06;

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

public class Demo06 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
        // 这里用方式5连接数据库
        // 前置工作
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        // 注册驱动
        Class.forName(driver);

        // 得到连接
        Connection connect = DriverManager.getConnection(url, user, password);

        // 得到Statement
        Statement statement = connect.createStatement();

        // 组织select查询的sql语句
        String sql = "SELECT * FROM `tb01`";

        // 执行给定的sql语句,该语句返回单个ResultSet对象
        ResultSet resultSet = statement.executeQuery(sql);

        // 使用while取出数据,循环一次就是取一行
        while (resultSet.next()) {// 判断下一行还有没有数据,有的话继续取
            int id = resultSet.getInt(1);// 获取并接收第一列数据
            String name = resultSet.getString(2);// 获取并接收第二列数据
            System.out.println("id是" + id + ", 姓名是" + name);// 输出
        }

        // 关闭连接
        resultSet.close();
        statement.close();
        connect.close();
    }

}

SQL注入问题

Statement对象用于执行静态SQL语句并返回其生成的结果的对象

在建立连接后,需要对数据库进行访问,执行命令或是SQL语句,可以通过:

  • Statement [存在SQL注入问题] 在实际开发中不会用Statement
  • PreparedStatement [预处理]
  • CallableStatement

SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库

如SELECT * FROM xxx WHERE ' 1' OR ' = ' OR '1' = '1 '

要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了

PreparedStatement

  1. PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数,setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
  2. 调用executeQuery() 返回ResultSet对象
  3. 调用executeUpdate() 执行更新,包括增,删,改

好处:

  1. 不再使用+拼接sql语句,减少语法错误
  2. 有效的解决了sql注入问题
  3. 大大减少了编译次数,效率较高

由此引出以下采用PreparedStatement的连接方式

实际开发中,使用如下的方式连接

若是SELECT查询语句,ResultSet resultSet = preparedStatement.executeQuery()返回结果集

package src.month09.day0913.demo06;

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

public class Demo06 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
        // 前置工作
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        // 一 注册驱动
        Class.forName(driver);

        // 二 得到连接
        Connection connect = DriverManager.getConnection(url, user, password);

        // 三 得到PreparedStatement
        //  3.1 组织sql语句,?就是占位符
        String sql = "select * from tb01 where id = ?";
        //  3.2 preparedStatement对象实现了PreparedStatement接口的实现类的对象
        PreparedStatement preparedStatement = connect.prepareStatement(sql);
        //  3.3 给sql中的?赋值
        preparedStatement.setString(1, "3");// 第一个参数代表是第几个?,从1开始数,第二个参数是要填充的值

        // 四 执行给定的sql语句,该语句返回单个ResultSet对象,查询使用executeQuery方法,如果执行增删改语句用executeUpdate
        ResultSet resultSet = preparedStatement.executeQuery();// 这里执行executeQuery,不要再写sql

        // 五 使用while取出数据,循环一次就是取一行
        while (resultSet.next()) {// 判断下一行还有没有数据,有的话继续取
            int id = resultSet.getInt(1);// 获取并接收第一列数据
            String name = resultSet.getString(2);// 获取并接收第二列数据
            System.out.println("id是" + id + ", 姓名是" + name);// 输出
        }

        // 六 关闭连接
        resultSet.close();
        preparedStatement.close();
        connect.close();
    }

}

若是增删改语句,返回的是受影响的行数int rows = preparedStatement.executeUpdate()

package src.month09.day0914.demo02;

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

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 前置工作
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        // 一 注册驱动
        Class.forName(driver);

        // 二 得到连接
        Connection connection = DriverManager.getConnection(url, user, password);

        // 三 得到PreparedStatement
        //  3.1 组织sql语句,?就是占位符
        String sql = "insert into tb01 values (?,?)";
        //  3.2 preparedStatement对象实现了PreparedStatement接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //  3.3 给sql中的?赋值
        preparedStatement.setString(1, "8");
        preparedStatement.setString(2, "250");

        // 四 执行给定的sql语句,返回受影响的行数
        int rows = preparedStatement.executeUpdate();

        // 五 根据受影响的行数判断是否执行成功
        System.out.println(rows > 0 ? "成功" : "失败");

        // 六 关闭连接
        preparedStatement.close();
        connection.close();
    }
}

JDBC API总结

  1. DriverManager 驱动管理类

    ​ getConnection(url,user,pwd) 获取到连接

  2. Connection 接口

    ​ createStatement 创建Statement对象

    prepareStatement(sql) 生成预处理对象

  3. Statement 接口

    ​ executeUpdate(sql) 执行增删改语句,返回受影响的行数

    ​ executeQuery(sql) 执行查询语句,返回结果集ResultSet对象

    ​ execute(sql) 可以执行任意的sql语句,返回布尔值,可用于创建/删除表等

  4. PreparedStatement 接口

    executeUpdate() 执行增删改语句,返回受影响的行数

    executeQuery() 执行查询语句,返回结果集ResultSet对象

    execute() 可以执行任意的sql语句,返回布尔值,可用于创建/删除表等

    setXXX(展位符?的索引, 占位符的值) 解决sql注入问题,用来给带问号的sql语句赋具体值

    setObject(展位符?的索引, 占位符的值) 用法同上一条,不过是对象形式

  5. ResultSet 结果集

    ​ next() 向下移动一行,初始在第一行的前一行,如果没有下一行,会返回false

    ​ previous() 向上移动一行,如果没有上一行,会返回false

    ​ getXXX(字段的索引或字段名) 返回当前行的指定字段的值

    ​ getObject(字段的索引或字段名) 返回当前行的指定字段的值,接受类型为Object

JDBC 工具类

可以看出,每次操作数据库都有连接和关闭的步骤,所以可以将其封装到 JdbcUtils 工具类中,方便以后使用

JdbcUtils类

package src.month09.day0914.utils;

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

public class JdbcUtils {
    // 定义4个相关属性,因为只需要一份,所以定义成静态的
    private static String user;     // 用户名
    private static String password; // 密码
    private static String url;      // url
    private static String driver;   // 驱动名

    // 在static代码块中初始化
    static {
        try {
            Properties properties = new Properties();
            // 路径自己改,需要一份配置文件
            properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\mysql.properties"));
            // 读取相关属性值
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            // 实际开发中,我们可以这样处理
            // 1. 将编译异常转成运行异常
            // 2. 调用者可以选择捕获该异常,也可以选择默认处理该异常,比较方便
            throw new RuntimeException(e);
        }
    }

    // 连接数据库,返回一个connection
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // 关闭相关资源,如果需要关闭资源,就传入对象,否则传入null
    // 1. ResultSet结果集
    // 2. Statement 或者 PreparedStatement
    // 3. Connection
    public static void closeConnection(ResultSet set, Statement statement, Connection connection) {
        // 判断是否为空
        try {
            if (set != null) {
                set.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

使用JdbcUtils类进行连接,增删改操作

package src.month09.day0914.demo03;

import src.month09.day0914.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

// 使用jdbc工具类连接数据库
public class Demo03 {
    public static void main(String[] args) {
        // 1. 得到连接
        Connection connection = null;

        // 2. 组织sql语句
        String sql = "insert into tb01 values (?,?)";

        // 3. 创建一个PreparedStatement对象
        PreparedStatement preparedStatement = null;
        try {
            // 获得connection的语句放在这是为了统一捕获异常
            connection = JdbcUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            // 给占位符赋值
            preparedStatement.setInt(1, 9);
            preparedStatement.setString(2, "李易峰");
            // 执行
            int rows = preparedStatement.executeUpdate();
            System.out.println(rows > 0 ? "成功" : "失败");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(null, preparedStatement, connection);
        }
    }
}

使用JdbcUtils类进行连接,查询操作

package src.month09.day0914.demo04;

import src.month09.day0914.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo04 {
    public static void main(String[] args) {
        // 1. 得到连接
        Connection connection = null;

        // 2. 组织sql语句
        String sql = "select * from tb01 where id < ?";

        // 3. 创建一个PreparedStatement对象
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 获得connection的语句放在这是为了统一捕获异常
            connection = JdbcUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            // 给占位符赋值
            preparedStatement.setInt(1, 5);
            // 执行
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                System.out.println(id + "\t" + name);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(resultSet, preparedStatement, connection);
        }
    }
}

JDBC 事务

  1. JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务,即每次执行一个SQL语句preparedStatement.executeQuery()时,如果执行成功,就会向数据库自动提交,而不能回滚
  2. JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
  3. 调用Connection的setAutoCommit(false)可以取消自动提交事务
  4. 在所有的SQL语句都成功执行后,调用commit() 方法提交事务 在其中某个操作失败或出现异常时,调用rollback() 方法回滚事务
/*
获取到connection对象后,就设置setAutoCommit(false),取消自动提交,相当于开启了事务
在所有的SQL语句都成功执行后,调用connection对象的commit()方法提交事务
*/

package src.month09.day0914.demo06;

import src.month09.day0914.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Demo06 {
    public static void main(String[] args) {
        // 1. 得到连接
        Connection connection = null;

        // 2. 组织sql语句
        String sql1 = "update tb02 set money = money - ? where name = ?";
        String sql2 = "update tb02 set money = money + ? where name = ?";

        // 3. 创建一个PreparedStatement对象
        PreparedStatement preparedStatement = null;

        try {
            connection = JdbcUtils.getConnection();// 默认为自动提交事务
            connection.setAutoCommit(false);// 将connection设置为不自动提交,相当于开启了事务

            // 执行我给马云转账的操作
            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.setInt(1, 100);
            preparedStatement.setString(2, "me");
            int i = preparedStatement.executeUpdate();
            System.out.println(i > 0 ? "success" : "fail");

            // int a = 1 / 0; // 可以看到,这里一旦发生异常,sql1即使执行了,也不会在数据库里生效,要等到commit后才算成功,这就是事务

            // 执行马云收到转账的操作
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.setInt(1, 100);
            preparedStatement.setString(2, "mayun");
            int j = preparedStatement.executeUpdate();
            System.out.println(j > 0 ? "success" : "fail");

            // 要是转账途中没有异常,就会执行到这里,进行事务提交,这时sql语句就会真正永久地将数据存到数据库中
            connection.commit();

        } catch (SQLException e) {
            try {
                // 一旦转账过程中发生异常,就撤销已经执行的sql,不填保存点参数就会默认回滚到开启事务的地方
                connection.rollback();
                System.out.println("sql语句执行发生了异常,已经撤销了已执行的sql语句");
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(null, preparedStatement, connection);
        }
    }
}

批处理

当需要成批插入或者更新记录时,可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率,相当于将多条sql语句打包一块发送给数据库

JDBC的批量处理语句包括下面方法 addBatch() 添加需要批量处理的SQL语句或参数 executeBatch() 执行批量处理语句 clearBatch() 清空批处理包的语句

JDBC连接MySQL时,如果要使用批处理功能,请在url中添加参数 ?rewriteBatchedStatements=true 批处理往往和PreparedStatement一起搭配使用,既可以减少编译次数,又减少运行次数,效率大大提高

# 要想批处理,配置文件的url得加上参数,写成这样

user=root
password=root
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
driver=com.mysql.cj.jdbc.Driver
package src.month09.day0914.demo07;

import src.month09.day0914.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class Demo07 {
    public static void main(String[] args) throws Exception {
        Connection connection = JdbcUtils.getConnection();
        String sql = "insert into tb03 values ( null , ? )";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setString(1, "jack" + i + "号");
            // 将sql语句加入到批量处理包中
            preparedStatement.addBatch();
            // 每1000条语句,批量执行一次
            if ((i + 1) % 1000 == 0) {
                preparedStatement.executeBatch();
                // 清空一下,准备装下一批
                preparedStatement.clearBatch();
            }
        }
    }
}

连接池

  1. 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间).需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃
  2. 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库
  3. 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃
  4. 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)

数据库连接池实现原理

  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从"缓冲池"中取出一个,使用完毕之后再放回去
  2. 数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  3. 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中

数据库连接池种类

  1. JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
  2. C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate,spring底层在用),是个老牌连接池
  3. DBCP数据库连接池,速度相对C3P0较快,但不稳定
  4. Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一点
  5. BoneCP数据库连接池,速度快
  6. Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP,C3P0,Proxool优点于一身的数据库连接池,做项目一般用这个

C3P0

  1. C3P0获取连接,网上找jar包,复制到 libs 路径,再导入到项目才能用

    package src.month09.day0914.demo08;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import java.io.FileInputStream;
    import java.sql.Connection;
    import java.util.Properties;
    
    public class Demo08 {
        public static void main(String[] args) throws Exception {
            // 1 创建一个数据源对象(里面有一些连接,连接池)
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            // 2 通过配置文件获取相关连接信息
            Properties properties = new Properties();
            properties.load(new FileInputStream("src/mysql.properties"));
            // 3 读取相关属性值
            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driver = properties.getProperty("driver");
            // 4 给数据源comboPooledDataSource设置参数,现在整个的管理交给了数据源,它拿到用户名密码后负责和数据库打交道
            comboPooledDataSource.setDriverClass(driver);
            comboPooledDataSource.setJdbcUrl(url);
            comboPooledDataSource.setUser(user);
            comboPooledDataSource.setPassword(password);
            // 5 设置初始化连接数和最大连接数(到达最大连接数后会进入等待队列)
            comboPooledDataSource.setInitialPoolSize(10);
            comboPooledDataSource.setMaxPoolSize(50);
            // 6 获取连接
            Connection connection = comboPooledDataSource.getConnection();
            System.out.println("连接成功");
            // 7 关闭连接
            connection.close();
        }
    }
    
    
  2. 使用配置文件的方式也可以用C3P0

    先准备好名为 c3p0-config.xml 的配置文件,一般放在src目录下

    <?xml version="1.0" encoding="utf-8"?>
    <c3p0-config>
        <!--  连接池名称-->
        <named-config name="test_c3p0">
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
            <property name="user">root</property>
            <property name="password">root</property>
    
            <property name="initialPoolSize">5</property>
            <property name="maxPoolSize">10</property>
            <property name="checkoutTimeout">3000</property>
        </named-config>
    </c3p0-config>
    

    有配置文件后,通过名字直接连接

    package src.month09.day0914.demo09;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    public class Demo09 {
        public static void main(String[] args) throws SQLException {
            // 这里参数填的的名字就是配置文件里的<named-config name="test_c3p0">
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("test_c3p0");
            Connection connection = comboPooledDataSource.getConnection();
            System.out.println("连接成功");
            connection.close();
        }
    }
    

Druid

名为 druid.properties 的配置文件,一般放在src目录下

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
username=root
password=root
initialSize=10
minIdle=5
maxActive=20
maxWait=5000

Druid获取连接

package src.month09.day0914.demo10;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

public class Demo10 {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream("druid.properties"));

        // 创建一个指定参数的数据库连接池,Druid连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        // 得到连接
        Connection connection = dataSource.getConnection();
        System.out.println("连接成功~~");
        connection.close();
    }
}

Druid 工具类

既然有了这么吊的德鲁伊连接池,那么是时候重新封装一下以前封装的JDBC工具类了

JDBCUtilsByDruid.java

package utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

// 基于druid数据库连接池的工具类
public class JDBCUtilsByDruid {
    private static DataSource ds;

    // 在静态代码块完成ds初始化
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("druid.properties"));// 加载配置文件
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 得到连接,getConnection方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    // 这里是把connection对象连接放回到连接池,没有真正关闭
    public static void closeConnection(ResultSet rs, Statement statement, Connection connection) {
        try {
            if (rs != null) {
                rs.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

通过Druid工具类进行连接测试

package src.month09.day0914.demo11;

import utils.JDBCUtilsByDruid;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo11 {
    public static void main(String[] args) throws SQLException {
        Connection connection = null;
        String sql = "select * from tb01 where id < ?";
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();// 这里的connection连接是通过自己写的德鲁伊工具类得到的
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, 6);
            set = preparedStatement.executeQuery();
            while (set.next()) {
                int id = set.getInt("id");
                String name = set.getString("name");
                System.out.println(id + "\t" + name);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtilsByDruid.closeConnection(set, preparedStatement, connection);// 这里不是真关闭,而是将连接放回到连接池
        }
    }
}

Apache-DBUtils

前面讲了这么多,还是有瑕疵,因为查询语句返回的结果集是临时的,它和connection是关联的,只要connection关闭,结果集也将不能使用,结果集ResultSet不利于操作和管理数据,所以就要将查询到结果集中的数据重新放到一个集合中,假如从actor表中取了一些数据在结果集中,就可以再搞一个ArrayList<acotr>,这个集合里面放的每个元素就对应actor表中的每条记录,这样一来就可以不要结果集了,关闭连接后也能操作得到的数据

commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutilsi能极大简化JDBC编码的工作量

QueryRunner类 该类封装了SQL的执行,是线程安全的,可以实现增,删,改,查,批处理,QueryRunner类实现查询

ResultSetHandler接口 该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式

ArrayHandler 把结果集中的第一行数据转成对象数组 ArrayListHandler 把结果集中的每一行数据都转成一个数组,再存放到List中 BeanHandler 将结果集中的第一行数据封装到一个对应的JavaBean实例中 BeanListHandler 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里 ColumnListHandler 将结果集中某一列的数据存放到List中 KeyedHandler(name) 将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key MapHandler 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值 MapListHandler 将结果集中的每一行数据都封装到一个Map里,然后再存放到List

  1. 先需要自己创建一个类,该类new创建的实例用来和表中的记录对应
package src.month09.day0915.demo01;

// Tb01 创造出来的对象和 tb01 表中的记录对应
public class Tb01 {// Javaben / POJO / Domain 对象
    private Integer id;// 注意这里是Integer,对应数据库中的int
    private String name;

    // 需要给一个无参构造器(反射需要)
    public Tb01() {
    }

    public Tb01(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return id + "\t" + name;
    }
}

  1. 有了上面的类后,就可以用apache工具类连接数据库查询并保存数据了,结果保存在list集合中
package src.month09.day0915.demo01;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import utils.JDBCUtilsByDruid;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class Demo01 {
    // 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
    public static void main(String[] args) throws SQLException {
        // 1 通过德鲁伊工具类得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 3 queryRunner对象可以执行query方法,返回一个ArrayList集合
        String sql = "select * from tb01 where id < ?";
        /*
         * - query方法就是执行sql语句,得到resultSet---封装到-->ArrayList集合中,返回集合
         * - 参数解释:
         *       - connection 连接
         *       - sql 执行的sql语句
         *       - new BeanListHandler<>(Tb01.class) 在将resultset->Actor对象->封装到ArrayList
         *         底层使用反射机制去获取Actor类的属性,然后进行封装
         *       - 最后的参数5就是给sql语句中第一个?处填5,5的后面还可以有多个值,因为是可变参数的
         *  */
        List<Tb01> list = queryRunner.query(connection, sql, new BeanListHandler<>(Tb01.class), 7);


        for (Tb01 t : list) {
            System.out.println(t);
        }
        /*
            可以看出,都存到了集合中
                1	tom
                2	jack
                3	jerry
                4	smith
        */

        // 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,底层会自己关闭他们
        JDBCUtilsByDruid.closeConnection(null, null, connection);
    }
}

  1. 返回的查询结果除了是集合,也可以是单个对象,对应一条记录
package src.month09.day0915.demo01;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import utils.JDBCUtilsByDruid;

import java.sql.Connection;
import java.sql.SQLException;

public class Demo01 {
    // 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
    public static void main(String[] args) throws SQLException {
        // 1 通过德鲁伊工具类得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 3 queryRunner对象可以执行query方法,返回单个对象
        String sql = "select * from tb01 where id = ?";
        // 因为我们返回的是单行记录<--->单个对象,所以使用的Hander是BeanHandler
        Tb01 tb01 = queryRunner.query(connection, sql, new BeanHandler<>(Tb01.class), 2);

        System.out.println(tb01);
        // 结果为          2	jack

        // 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,query函数会自己关闭他们
        JDBCUtilsByDruid.closeConnection(null, null, connection);
    }
}

  1. 若查询结果是单行单列,返回的就是Object
package src.month09.day0915.demo01;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import utils.JDBCUtilsByDruid;

import java.sql.Connection;
import java.sql.SQLException;

public class Demo01 {
    // 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
    public static void main(String[] args) throws SQLException {
        // 1 通过德鲁伊工具类得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 3 queryRunner对象可以执行query方法,也可以是单行单列,返回的就是Object
        String sql = "select name from tb01 where id = ?";

        // 因为查询结果是单行单列,所以使用的handler就是ScalarHandler,返回的是个Object
        Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 2);

        System.out.println(obj);
        // 结果为          jack

        // 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,query函数会自己关闭他们
        JDBCUtilsByDruid.closeConnection(null, null, connection);
    }
}

  1. 增删改语句的使用
package src.month09.day0915.demo01;

import org.apache.commons.dbutils.QueryRunner;
import utils.JDBCUtilsByDruid;

import java.sql.Connection;
import java.sql.SQLException;

public class Demo01 {
    // 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
    public static void main(String[] args) throws SQLException {
        // 1 通过德鲁伊工具类得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 3 queryRunner对象可以执行query方法,也可以是单行单列,返回的就是Object
        String sql = "update tb01 set name = ? where id = ?";

        // 执行增删改语句用queryRunner.update方法,返回值是受影响的行数
        int affectedRows = queryRunner.update(connection, sql, "liyifeng", 1);
        System.out.println(affectedRows > 0 ? "成功" : "失败");

        // 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,query函数会自己关闭他们
        JDBCUtilsByDruid.closeConnection(null, null, connection);
    }
}

DAO

apache-dbutils+Druid简化了JDBC开发,但还有不足/(ㄒoㄒ)/~~

  1. SQL语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
  2. 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型
  3. 将来的表很多,业务需求复杂,不可能只靠一个Java类完成

DAO data access object数据访问对象

通用类称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作,在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表 ==> Customer.java(javabean) ==> CustomerDao.java

DAO.png

如何使用DAO操作数据库

  1. 建4个包,分别是dao,domain,test,utils

    • dao 包里放BaiscDao和每个表对应的dao,如actor表对应的dao就是ActorDAO,ActorDAO继承了BaiscDao里的操作数据库的方法,当然也可以加一些特有的方法,用来专门对actor表操作
    • domain 包里放 Javaben / POJO / Domain 的类,即actor表对应的domain类就是Actor,该类有actor表中字段对应的属性,无参构造器,有参构造器,getter,setter,重写toString等,将来用来创建实例和表中的记录一一对应
    • test 包里放测试类,用来对dao包里的如ActorDAO进行操作
    • utils 包里放工具类,如JDBCUtilsByDruid类
  2. 在dao包里创建BaiscDao,实现crud的方法

    package src.month09.day0915.demo04.dao;
    
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    import org.apache.commons.dbutils.handlers.ScalarHandler;
    import src.month09.day0915.demo04.utils.JDBCUtilsByDruid;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.List;
    
    // 开发BasicDAO是其他DAO的父类
    public class BasicDAO<T> {// 泛型指定具体类型
    
        private QueryRunner qr = new QueryRunner();
    
        // 开发通用的dml增删改方法,针对任意表
        public int update(String sql, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsByDruid.getConnection();
                return qr.update(connection, sql, params);
            } catch (SQLException e) {
                throw new RuntimeException(e);// 将编译异常-->运行异常,抛出,
            } finally {
                JDBCUtilsByDruid.closeConnection(null, null, connection);
            }
        }
    
        // 返回多个对象(即查询的结果是多行的),针对任意表
        public List<T> queryMultiple(String sql, Class<T> clazz, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsByDruid.getConnection();
                return qr.query(connection, sql, new BeanListHandler<T>(clazz), params);
            } catch (SQLException e) {
                throw new RuntimeException(e);// 将编译异常-->运行异常,抛出,
            } finally {
                JDBCUtilsByDruid.closeConnection(null, null, connection);
            }
        }
    
        // 查询单行的通用方法
        public T querySingle(String sql, Class<T> clazz, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsByDruid.getConnection();
                return qr.query(connection, sql, new BeanHandler<T>(clazz), params);
            } catch (SQLException e) {
                throw new RuntimeException(e);// 将编译异常-->运行异常,抛出,
            } finally {
                JDBCUtilsByDruid.closeConnection(null, null, connection);
            }
        }
    
        // 查询单行单列的方法,即返回单值的方法
        public Object queryScalar(String sql, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsByDruid.getConnection();
                return qr.query(connection, sql, new ScalarHandler<>(), params);
            } catch (SQLException e) {
                throw new RuntimeException(e);// 将编译异常-->运行异常,抛出,
            } finally {
                JDBCUtilsByDruid.closeConnection(null, null, connection);
            }
        }
    }
    
    
  3. 如现数据库里有一个actor表,则在dao包里创建ActorDAO类,继承了BasicDAO

    package src.month09.day0915.demo04.dao;
    
    import src.month09.day0915.demo04.domain.Actor;
    
    public class ActorDAO extends BasicDAO<Actor>{
        // 因为继承了BasicDAO,所以有了BasicDAO的所有方法
        // 根据业务需求可以写特有方法
    }
    
    
  4. 还需要再domain包里创建一个Actor类,将来实例化的对象会和得到的记录一一对应,所以属性值得和字段对上(完全一致)

    package src.month09.day0915.demo04.domain;
    
    public class Actor {// Javaben / POJO / Domain 对象
        private Integer id;
        private String name;
        private Integer age;
        private String addr;
    
        // 需要给一个无参构造器(反射需要)
        public Actor() {
        }
    
        public Actor(Integer id, String name, Integer age, String addr) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.addr = addr;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getAddr() {
            return addr;
        }
    
        public void setAddr(String addr) {
            this.addr = addr;
        }
    
        @Override
        public String toString() {
            return id + "\t" + name + '\t' + age + "\t" + addr;
        }
    }
    
    
  5. 在utils包里创建一个JDBCUtilsByDruid工具类,具体配置啥的看前面的

    package src.month09.day0915.demo04.utils;
    
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    
    import javax.sql.DataSource;
    import java.io.FileInputStream;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;
    
    // 基于druid数据库连接池的工具类
    public class JDBCUtilsByDruid {
        private static DataSource ds;
    
        // 在静态代码块完成ds初始化
        static {
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream("druid.properties"));
                ds = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 得到连接,getConnection方法
        public static Connection getConnection() throws SQLException {
            return ds.getConnection();
        }
    
        // 这里是把connection对象连接放回到连接池,没有真正关闭
        public static void closeConnection(ResultSet rs, Statement statement, Connection connection) {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (statement != null) {
                    statement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    
  6. 在test包里创建测试类,用来通过ActorDAO操作actor表

    package src.month09.day0915.demo04.test;
    
    import src.month09.day0915.demo04.dao.ActorDAO;
    import src.month09.day0915.demo04.domain.Actor;
    
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
            // 测试ActorDAO对actor表的crud操作
            ActorDAO actorDAO = new ActorDAO();
    
            // 插入
            int update = actorDAO.update("insert into actor values (?,?,?,?)", 10, "tom", 18, "beijing");
            System.out.println(update > 0 ? "插入成功" : "未生效");
    
            // 返回结果是多条记录的情况
            List<Actor> actors = actorDAO.queryMultiple("select * from actor where id > ?", Actor.class,5);
            for (Actor actor : actors) {
                System.out.println(actor);
            }
    
            // 返回结果是一条语句
            Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6);
            System.out.println(actor);
    
            // 返回结果是单行单列
            Object o = actorDAO.queryScalar("select name from actor where id = ?", 6);
            System.out.println(o);
        }
    }