Loading

1 2

韩顺平JDBC学习笔记

第一节 JDBC概述

1.1 JDBC原理图

Java不可能具体地去操作数据库,因为数据库有许多种,直接操作数据库是一种很低效且复杂的过程。

因此,Java引入JDBC,规定一套操作数据库的接口规范,从而要求数据库厂商去实现JDBC接口。

Java程序员只需要面向JDBC接口编程即可。

1.2 JDBC快速入门

具体步骤:

  1. 注册驱动
  2. 得到连接
  3. 执行SQL
  4. 关闭连接资源
public class JDBC01 {
    public static void main(String[] args) throws SQLException {
        //前置工作: 在项目下创建一个文件夹比如 libs
        // 将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
        
        //1. 注册驱动
        Driver driver = new Driver(); //创建driver对象

        //2. 得到连接
        // 老师解读
        //(1) jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
        //(2) localhost 主机,可以是ip地址
        //(3) 3306 表示mysql监听的端口
        //(4) hsp_db02 连接到mysql dbms 的哪个数据库
        //(5) mysql的连接本质就是前面学过的socket连接
        String url = "jdbc:mysql://localhost:3306/hsp_db02";
        //将 用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "zouwenhao"); //密码
        Connection connect = driver.connect(url, properties);

        //3. 执行sql
        //String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
        //String sql = "update actor set name='周星驰' where id = 1";
        String sql = "delete from actor where id = 1";
        //statement 用于执行静态SQL语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        // rows返回0,表示执行失败
        int rows = statement.executeUpdate(sql); // 如果是 dml语句,返回的就是影响行数,executeUpdate其实就是执行语句
        System.out.println(rows > 0 ? "成功" : "失败"); // 受影响的行数大于0,那么执行成功,反之为0,则执行失败

        //4. 关闭连接资源
        statement.close();
        connect.close();
    }
}

第二节 连接数据库的五种方式

2.1 使用Driver

属于静态加载,依赖第三方Driver

    @Test
    public void connect01() throws SQLException {
        Driver driver = new Driver(); //创建driver对象
        String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
        //将 用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "zouwenhao"); //密码
        Connection connect = driver.connect(url, properties);
        System.out.println("方式一:" + connect);
    }

2.2 使用反射机制

相对比上一种方法,现在是通过反射机制获取Driver

    //方式二
    @Test
    public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, SQLException {
        // 使用反射加载Driver类,动态加载,更加灵活,减少依赖性
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) aClass.getDeclaredConstructor().newInstance();
        String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
        //将 用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "zouwenhao"); //密码
        Connection connect = driver.connect(url, properties);
        System.out.println("方式二:" + connect);
    }

2.3 使用DriverManager.getConnection替代Driver.connect

相对比与上一种方法,使用DriverManager来注册,不用写配置文件, 更加方便

方式三,更加简便一些,连properties类都不需要使用。

    //方式3 使用DriverManager 替代 driver 进行统一管理
    @Test
    public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException, NoSuchMethodException, InvocationTargetException {

        //使用反射加载Driver
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) aClass.getDeclaredConstructor().newInstance();

        //创建url 和 user 和 password
        String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
        String user = "root";
        String password = "zouwenhao";

        DriverManager.registerDriver(driver); // 注册Driver驱动

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式三:" + connection);
    }

2.4 自动完成注册驱动(推荐使用) *

相对比于上一种方法,直接可以不用注册驱动

    @Test
    public void connect04() throws ClassNotFoundException, SQLException {
        // 使用反射机制加载Driver类
        // 在加载 Driver类时,完成注册
        Class aclass = Class.forName("com.mysql.cj.jdbc.Driver");

        //创建url 和 user 和 password
        String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
        String user = "root";
        String password = "zouwenhao";

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式四:" + connection);

        // 类加载的源码分析
        /*
            源码:
            1. 静态代码块,在类加载时,会执行一次.
            2. DriverManager.registerDriver(new Driver());
            3. 因此注册driver的工作已经完成
            static {
                try {
                    DriverManager.registerDriver(new Driver());
                } catch (SQLException var1) {
                    throw new RuntimeException("Can't register driver!");
                }
            }
         */
    }

甚至连反射机制都可以不写,但建议写上!!!

2.5 改善第四种方法——使用配置文件(推荐使用) **

将第四种方法中,把写死的连接数据库的配置信息,通过properties类来进行获取配置。

    @Test
    public void connect05() throws IOException, ClassNotFoundException, SQLException {
        // 通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        // 利用反射进行Driver注册
        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, password);

        System.out.println("方式五:" + connection);
    }

第三节 ResultSet(结果集)

查询数据库的结果,存放在一个结果集对象中。

public class ResultSet_ {
    public static void main(String[] args) throws Exception {
        //通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1. 注册驱动
        Class.forName(driver);//建议写上

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

        //3. 得到Statement
        Statement statement = connection.createStatement();
        //4. 组织SQL
        String sql = "select id, name , sex, borndate from actor";
        //执行给定的SQL语句,该语句返回单个 ResultSet对象
            /*
            +----+-----------+-----+---------------------+
            | id | name      | sex | borndate            |
            +----+-----------+-----+---------------------+-------+
            |  4 | 刘德华     | 男  | 1970-12-12 00:00:00 |
            |  5 | jack      | 男  | 1990-11-11 00:00:00 |
            +----+-----------+-----+---------------------+-------+
             */
        /*
            老韩阅读debug 代码 resultSet 对象的结构
         */
        ResultSet resultSet = statement.executeQuery(sql);

        //5. 使用while取出数据
        while (resultSet.next()) { // 让光标向后移动,如果没有更多行,则返回false
            int id  = resultSet.getInt(1); //获取该行的第1列
            //int id1 = resultSet.getInt("id"); //通过列名来获取值, 推荐
            String name = resultSet.getString(2);//获取该行的第2列 (select顺序)
            String sex = resultSet.getString(3);
            Date date = resultSet.getDate(4);
            System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
        }

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

ResultSet结果集对象结构:

ResultSet是接口,而JDBC42ResultSet才是实现接口的实例对象。

其中rowData才是存放查询数据的:

rows是一个ArrayLits,才是真正存放数据的地方:(放在其中elementData中)

第四节 SQL注入

不推荐使用 Statement(SQL注入问题很严重) ,而是使用PreparedStatement。

比如,万能密码登入数据库、一些复杂的密码破坏数据库。

第五节 PreparedStatement(预处理)

5.1 预处理DQL

使用Statement语句有存在SQL注入的风险,推荐使用PreparedStatement

总结要点:

  1. PreparedStatement使用 ? 作为占位符,而我们需要用set方法来指定第几个占位符的参数。(set里的序号,1、2等等是?的顺序)
  2. 注意占位之后,执行sql语句时,不要将sql参数放入其中,否则会执行带 ? 的sql语句。
public class PreparedStatement_ {
    public static void main(String[] args) throws Exception {

        //看 PreparedStatement类图

        Scanner scanner = new Scanner(System.in);

        //让用户输入管理员名和密码
        System.out.print("请输入管理员的名字: ");  //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 老师说明,如果希望看到SQL注入,这里需要用nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过Properties对象获取配置文件的信息

        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1. 注册驱动
        Class.forName(driver);//建议写上

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

        // =======重要部分=========
        //3. 得到PreparedStatement
        //3.1 组织SqL , Sql 语句的 ? 就相当于占位符
        String sql = "select name , pwd  from admin where name =? and pwd = ?"; // ?用于占位
        //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);//关联
        //3.3 给 ? 赋值
        preparedStatement.setString(1, admin_name);
        preparedStatement.setString(2, admin_pwd);

        //4. 执行 select 语句使用  executeQuery
        //   如果执行的是 dml(update, insert ,delete) executeUpdate()
        //   这里执行 executeQuery ,不要在写 sql,因为preparedStatement已经与sql关联

        ResultSet resultSet = preparedStatement.executeQuery(sql);
        if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
            System.out.println("恭喜, 登录成功");
        } else {
            System.out.println("对不起,登录失败");
        }

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

5.2 预处理DML

预处理DML(数据库操作语言DML:INSERT、UPDATE、DELETE):

        //1. 注册驱动
        Class.forName(driver);//建议写上

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

        //3. 得到PreparedStatement
        //3.1 组织SqL , Sql 语句的 ? 就相当于占位符
        //添加记录
        //String sql = "insert into admin values(?, ?)";
        //String sql = "update admin set pwd = ? where name = ?";
        String sql = "delete from  admin where name = ?";
        //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //3.3 给 ? 赋值
        preparedStatement.setString(1, admin_name);

        //preparedStatement.setString(2, admin_name);

        //4. 执行 dml 语句使用  executeUpdate
        int rows = preparedStatement.executeUpdate();
        System.out.println(rows > 0 ? "执行成功" : "执行失败");

第六节 JDBC API小结

第七节 JDBCUtils

7.1 JDBCUtils封装与开发

public class JDBCUtils {
    //定义相关的属性(4个), 因为只需要一份,因此,我们做出static
    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\\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) {
            //1. 将编译异常转成 运行异常
            //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
            throw new RuntimeException(e);
        }
    }

    //关闭相关资源
    /*
        1. ResultSet 结果集
        2. Statement 或者 PreparedStatement
        3. Connection
        4. 如果需要关闭资源,就传入对象,否则传入 null
     */
    public static void close(ResultSet set, Statement statement, Connection connection) {

        //判断是否为null
        try {
            if (set != null) {
                set.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            //将编译异常转成运行异常抛出
            throw new RuntimeException(e);
        }
    }
}

7.2 JDBCUtils的使用(DML)

    @Test
    public void testSelect() {
        //1. 得到连接
        Connection connection = null;
        //2. 组织一个sql
        String sql = "select * from actor where id = ?";
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtils.getConnection();
            System.out.println(connection.getClass()); //com.mysql.jdbc.JDBC4Connection
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, 5);//给?号赋值
            //执行, 得到结果集
            set = preparedStatement.executeQuery();
            //遍历该结果集
            while (set.next()) {
                int id = set.getInt("id");
                String name = set.getString("name");
                String sex = set.getString("sex");
                Date borndate = set.getDate("borndate");
                String phone = set.getString("phone");
                System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(set, preparedStatement, connection);
        }
    }

    @Test
    public void testDML() {//insert , update, delete

        //1. 得到连接
        Connection connection = null;
        //2. 组织一个sql
        String sql = "update actor set name = ? where id = ?";
        // 测试 delete 和 insert ,自己玩.
        PreparedStatement preparedStatement = null;
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtils.getConnection();

            preparedStatement = connection.prepareStatement(sql);
            //给占位符赋值
            preparedStatement.setString(1, "周星驰");
            preparedStatement.setInt(2, 4);
            //执行
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }

自己写的测试:

    @Test
    public void testInsert() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        // 1. 插入语句:向subject表插入数据
        String sqlInsert01 = "INSERT INTO subject(subjectname) VALUES (?),(?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sqlInsert01);
        preparedStatement.setString(1, "编译原理");
        preparedStatement.setString(2, "计算机图形学");
        System.out.println(preparedStatement.executeUpdate() > 0 ? "插入成功!": "插入失败!");

        // 2. 释放资源
        JDBCUtils.close(null, preparedStatement, connection);
    }

    @Test
    public void testDelete() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        // 1. 删除语句:向subject表删除数据
        String sqlDelete01 = "DELETE FROM subject WHERE subjectno BETWEEN 19 AND 22";
        PreparedStatement preparedStatement = connection.prepareStatement(sqlDelete01);
        System.out.println(preparedStatement.executeUpdate() > 0 ? "删除成功!": "删除失败!");

        // 2. 释放资源
        JDBCUtils.close(null, preparedStatement, connection);
    }

    @Test
    public void testUpdate() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        // 1. 修改语句:向subject表修改数据
        String sqlUpdate01 = "UPDATE subject set subjectname=\"计算机组成原理\" WHERE subjectname=\"计算机图形学\"";
        PreparedStatement preparedStatement = connection.prepareStatement(sqlUpdate01);
        System.out.println(preparedStatement.executeUpdate() > 0 ? "修改成功!": "修改失败!");

        // 2. 释放资源
        JDBCUtils.close(null, preparedStatement, connection);
    }

    @Test
    public void testSelect() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        // 1. 查询语句:向subject表查询数据
        String sqlSelect01 = "SELECT * FROM student";
        PreparedStatement preparedStatement = connection.prepareStatement(sqlSelect01);
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            System.out.println(resultSet.getString("studentname") + " 住在 " +
                    resultSet.getString("address"));
        }
        
        // 2. 释放资源
        JDBCUtils.close(null, preparedStatement, connection);
    }

第八节 事务

注意点:

  1. connection默认是自动提交事务,也就是执行完SQL语句后,成功就自动提交,数据库就此改变,无法回滚。
  2. 如果涉及到多条SQL语句组成的事务,为了保证事务执行的正确性,需要将connection的自动提交设置成false。

经典的转账问题,涉及到事务提交:

    //没有使用事务. 由于部分SQL语句因为异常而中断执行,因此最后数据库出现了一方扣账,而另一个并没有增加存款金额
    @Test
    public void noTransaction() {

        //操作转账的业务
        //1. 得到连接
        Connection connection = null;
        //2. 组织一个sql
        String sql = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";
        PreparedStatement preparedStatement = null;
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.executeUpdate(); // 执行第1条sql

            int i = 1 / 0; //抛出异常,此时下面的两条语句并不会得到执行
            
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate(); // 执行第3条sql


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }

    //事务来解决
    @Test
    public void useTransaction() {

        //操作转账的业务
        //1. 得到连接
        Connection connection = null;
        //2. 组织一个sql
        String sql = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";
        PreparedStatement preparedStatement = null;
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
            //将 connection 设置为不自动提交
            connection.setAutoCommit(false); //开启了事务,关闭自动提交事务
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.executeUpdate(); // 执行第1条sql

            int i = 1 / 0; //抛出异常
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate(); // 执行第3条sql

            //这里提交事务,只有在最后确认所有SQL语句都被成功执行后,才提交事务
            connection.commit();

        } catch (SQLException e) { // 出现异常后,在这里进行回滚,将之前已经执行了的SQL语句回滚回去
            //这里我们可以进行回滚,即撤销执行的SQL
            //默认回滚到事务开始的状态.
            System.out.println("执行发生了异常,撤销执行的sql");
            try {
                connection.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }

第九节 批处理

注解:batch:一批、批

  1. 效率提升?不再是执行一条语句就提交一次,而是多条语句一同提交处理。
  2. 减少编译次数体现在:预处理阶段使用占位符,减少编译次数,而不是通过拼接SQL语句。

使用批处理前后效率对比:

    @Test
    public void noBatch() throws Exception{ // 不使用批处理,一条SQL一条连接
        Connection connection = JDBCUtils.getConnection();
        String sql = "INSERT INTO test VALUES (null, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long begin = System.currentTimeMillis();
        for (int i = 0; i <  500; i++) {
            preparedStatement.setString(1, "Jack" + i);
            preparedStatement.setString(2, "666");
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        System.out.println("不使用批处理执行时间:" + (end - begin)); // 47342
        // 关闭连接
        JDBCUtils.close(null, preparedStatement, connection);
    }

    @Test
    public void useBatch() throws Exception{ // 使用批处理,多条SQL才连接一次进行处理
        Connection connection = JDBCUtils.getConnection();
        String sql = "INSERT INTO test VALUES (null, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long begin = System.currentTimeMillis();
        for (int i = 0; i <  500; i++) {
            preparedStatement.setString(1, "Jack" + i);
            preparedStatement.setString(2, "666");
            preparedStatement.addBatch(); // 添加到批处理
            if ((i + 1) % 100 == 0 ) {
                preparedStatement.executeBatch(); // 每一百个SQL,执行一次批处理
                preparedStatement.clearBatch(); // 清空
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("使用批处理执行时间:" + (end - begin)); // 328
        // 关闭连接
        JDBCUtils.close(null, preparedStatement, connection);
    }

addBatch()源码:使用批处理时,一定要在JDBC的url中添加rewriteBatchedStatements=true

            //将sql 语句加入到批处理包中 -> 看源码
            /*
            //1. //第一就创建 ArrayList - elementData => Object[]
            //2. elementData => Object[] 就会存放我们预处理的sql语句
            //3. 当elementData满后,就按照1.5扩容(因为底层是用ArrayList临时存放SQL语句)
            //4. 当添加到指定的值后,就executeBatch
            //5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
            public void addBatch() throws SQLException {
                synchronized(this.checkClosed().getConnectionMutex()) {
                    if (this.batchedArgs == null) {
                        this.batchedArgs = new ArrayList();
                    }

                    for(int i = 0; i < this.parameterValues.length; ++i) {
                        this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
                    }

                    this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
                }
            }
             */
            preparedStatement.addBatch();

第十节 数据库连接池

10.1 传统数据库连接的不足

引出问题:如果连接5000次数据库会发生什么

太多连接!!!

即使是连接之后立马关闭,也会造成很大开销!!!

因此,进行传统数据库连接的弊端总结:

10.2 数据库连接池的原理

解释:

  1. 使用完毕之后再放回去:Java程序与数据库交互完成后,将连接放回连接池中,并不是close 关闭连接,而是改变连接池中一个现有连接的引用。
  2. Java程序首先在连接池中创建一定数量的连接,这些连接是已经与数据库成功连接上的,已经通过用户、密码等验证

原理示意图:

  1. 当连接池中的连接数量是一定的,如果此时Java程序请求的连接数大于连接池中的数量,那么就会进入等待队列进行排队等待
  2. 连接池的作用,更多是限制当前与数据库建立连接的数量,以保证数据库能够平稳地运行,不至于发生内存泄露、数据库奔溃重启等现象。

10.3 数据库连接池的种类

注意,JDBC只提供一个数据库连接池的接口,具体接口实现靠第三方,因此诞生了许多数据库连接池。

Druid 很不错!

10.4 C3P0

C3P0实现了JDBC的DataSource接口,并提供了对应的jar包。

主要有两种方式获取连接对象:

    //方式1: 相关参数,在程序中指定user, url , password等
    @Test
    public void c3p0Connect01 () throws Exception{
        // 1. 创建一个数据源对象
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();

        // 2. 通过配置文件 mysql.properties 获取相关连接的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        // 读取配置文件中的相关属性值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");

        // 3. 给数据连接池 comboPooledDataSource 设置相关参数
        // 因为数据库的连接是由 comboPooledDataSource 来进行管理的
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);

        // 4. 设置连接数
        // 设置初始化连接数
        comboPooledDataSource.setInitialPoolSize(10);
        // 设置连接池最大连接数
        // 当请求连接数据池的连接数大于50时,就需要进入等队列进行等待
        comboPooledDataSource.setMaxPoolSize(50);

        // 5. 得到连接对象(其中一个)
        Connection connection = comboPooledDataSource.getConnection();
    }

    //方式2: 使用配置文件模板来完成(xml文件)
    @Test
    public void c3p0Connect02 () throws Exception{
        // 1. 绑定配置文件中的数据源 configName
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("c3p0_test");
        Connection connection = comboPooledDataSource.getConnection();
    }

方式二中的配置文件:其中xml文件必须放在src目录下

<c3p0-config>
    <!-- 数据源名称代表连接池 -->
  <named-config name="c3p0_test">
<!-- 驱动类 -->
  <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
  <!-- url-->
  	<property name="jdbcUrl">jdbc:mysql://localhost:3306/school?rewriteBatchedStatements=true&useSSL=FALSE&serverTimezone=UTC</property>
  <!-- 用户名 -->
  		<property name="user">root</property>
  		<!-- 密码 -->
  	<property name="password">zouwenhao</property>
  	<!-- 每次增长的连接数-->
    <property name="acquireIncrement">5</property>
    <!-- 初始的连接数 -->
    <property name="initialPoolSize">10</property>
    <!-- 最小连接数 -->
    <property name="minPoolSize">5</property>
   <!-- 最大连接数 -->
    <property name="maxPoolSize">50</property>

	<!-- 可连接的最多的命令对象数 -->
    <property name="maxStatements">5</property> 
    
    <!-- 每个连接对象可连接的最多的命令对象数 -->
    <property name="maxStatementsPerConnection">2</property>
  </named-config>
</c3p0-config>

10.5 Druid(德鲁伊)

在大数据量面前,性能最好最稳定。

    @Test
    public void testDruid() throws Exception {
        //1. 加入 Druid jar包
        //2. 加入 配置文件 druid.properties , 将该文件拷贝项目的src目录
        //3. 创建Properties对象, 读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));

        //4. 创建一个指定参数的数据库连接池, Druid连接池
        DataSource dataSource =
                DruidDataSourceFactory.createDataSource(properties);

        //5. 获得连接
        Connection connection = dataSource.getConnection();
        connection.close();
    }

10.5.1 Druid JDBCUtils

基于Druid数据库连接池的工具类:

public class JDBCUtilsByDruid {

    private static DataSource ds;

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

    }

    //编写getConnection方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    //关闭连接, 老师再次强调: 在数据库连接池技术中,close 不是真的断掉连接
    //而是把使用的Connection对象放回连接池
    public static void close(ResultSet resultSet, Statement statement, Connection connection) {

        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

注意点:connection.close()这条语句,是根据connection实现的类,来决定是断开连接还是还原到连接池中,体现了Java多态,根据当前connection接口所实现的连接池类,来调用对应的close方法,是因为connection接口是由不同数据库连接池实现的。

10.6 Apache—DBUtils

10.6.1 引出问题—如何解决数据集的持久化问题

当前的一个问题:如何把结果集(resultSet)进行持久化?因为每当我们从数据库获得结果集ResultSet之后,只能使用一次就关闭了连接,一旦关闭连接,数据库返回的数据就无法再次使用,但我们不能保持与数据库的连接长期有效,这样会造成很大的高并发压力。

解决当前问题的思路:编写一个类,与数据库返回结果集相映射,通过一个类对象对应一条数据库查询记录的方式,通过ArrayList存储,以实现数据库查询结果的相对持久化的记录。

通过类的封装来实现数据集的复用:(土方法)

首先,新建一个Actor类

public class Actor { //Javabean, POJO, Domain对象

    private Integer id;
    private String name;
    private String sex;
    private Date borndate;
    private String phone;

    public Actor() { //一定要给一个无参构造器[反射需要]
    }

    public Actor(Integer id, String name, String sex, Date borndate, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.borndate = borndate;
        this.phone = phone;
    }
    ........

然后,看主代码

    //解决ResultSet =封装=> Arraylist
    @Test
    public ArrayList<Actor> testSelectToArrayList() {
        ......
        ArrayList<Actor> list = new ArrayList<>();//创建ArrayList对象,存放actor对象
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtilsByDruid.getConnection();
            System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, 1);//给?号赋值
            //执行, 得到结果集
            set = preparedStatement.executeQuery();
            //遍历该结果集
            while (set.next()) {
                int id = set.getInt("id");
                String name = set.getString("name");//getName()
                String sex = set.getString("sex");//getSex()
                Date borndate = set.getDate("borndate");
                String phone = set.getString("phone");
                //把得到的resultset 的记录,封装到 Actor对象,放入到list集合
                list.add(new Actor(id, name, sex, borndate, phone)); // 将查询记录,一条接一条放入list中
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtilsByDruid.close(set, preparedStatement, connection);
        }
        //因为ArrayList 和 connection 没有任何关联,所以该集合可以复用.
        return  list;
    }

10.6.2 DBUtils基本介绍

在上一小节中,使用了JavaBean方法来进行对查询结果集ResultSet的持久化保存,但存在部分代码是可复用的,于是引出本小节DBUtils

还是以Actor类为例,使用了DBUtils示例如下:

    //使用apache-DBUtils 工具类保存查询结果数据集
    public void testQueryMany() throws SQLException { //返回结果是多行的情况

        //1. 得到 连接 (druid)
        Connection connection = JDBCUtilsByDruid.getConnection();
        //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
        //3. 创建 QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4. 就可以执行相关的方法,返回ArrayList 结果集
        //String sql = "select * from actor where id >= ?";
        //   注意: sql 语句也可以查询部分列
        String sql = "select id, name from actor where id >= ?";
        
        // 老韩解读
        //(1) query 方法就是执行sql 语句,得到resultset ---封装到 --> ArrayList 集合中
        //(2) 返回集合
        //(3) connection: 连接
        //(4) sql : 执行的sql语句
        //(5) new BeanListHandler<>(Actor.class): 在将resultset -> Actor 对象 -> 封装到 ArrayList
        //    底层使用反射机制 去获取 Actor 类的属性,然后进行封装
        //(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数Object... params
        //(7) 底层得到的resultset ,会在query 关闭, 关闭PreparedStatment
        List<Actor> list =
                queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
        
        System.out.println("输出集合的信息");
        for (Actor actor : list) {
            System.out.print(actor);
        }

        //释放资源
        JDBCUtilsByDruid.close(null, null, connection);

    }

对query方法的源码解读:从源码中也能得知,在使用DBUtils之后,用户只需要关闭connection即可

        /**
         * 分析 queryRunner.query方法:
         * public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
         *         PreparedStatement stmt = null;//定义PreparedStatement
         *         ResultSet rs = null;//接收返回的 ResultSet
         *         Object result = null;//返回ArrayList
         *
         *         try {
         *             stmt = this.prepareStatement(conn, sql);//创建PreparedStatement
         *             this.fillStatement(stmt, params);//对sql 进行 ? 赋值
         *             rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
         *             result = rsh.handle(rs);//返回的resultset --> arrayList[result] [使用到反射,对传入class对象处理]
         *         } catch (SQLException var33) {
         *             this.rethrow(var33, sql, params);
         *         } finally {
         *             try {
         *                 this.close(rs);//关闭resultset
         *             } finally {
         *                 this.close((Statement)stmt);//关闭preparedstatement对象
         *             }
         *         }
         *
         *         return result;
         *     }
         */
        List<Actor> list =
                queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);

10.6.3 DBUtils应用实例

这一小节的要点就是:根据业务逻辑的不同,在queryRunner.query()中选择合适的handle

10.6.3.1 返回的结果是单行记录(单个对象)

演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)

使用BeanHandler,上一小节中返回多个对象,使用的是BeanListHandler

    public void testQuerySingle() throws SQLException {
        //1. 得到 连接 (druid)
        Connection connection = JDBCUtilsByDruid.getConnection();
        //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
        //3. 创建 QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4. 就可以执行相关的方法,返回单个对象
        String sql = "select * from actor where id = ?";
        // 老韩解读
        // 因为我们返回的单行记录<--->单个对象 , 使用的 Hander 是 BeanHandler , 底层使用的是反射机制
        Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);
        System.out.println(actor);

        // 释放资源
        JDBCUtilsByDruid.close(null, null, connection);
    }
10.6.3.2 完成查询结果是单行单列-返回的就是object

演示apache-dbutils + druid 完成查询结果是单行单列-返回的就是object

使用ScalarHandler

    public void testScalar() throws SQLException {
        //1. 得到 连接 (druid)
        Connection connection = JDBCUtilsByDruid.getConnection();
        //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
        //3. 创建 QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4. 就可以执行相关的方法,返回单行单列 , 返回的就是Object
        String sql = "select name from actor where id = ?";
        //老师解读: 因为返回的是一个对象, 使用的handler 就是 ScalarHandler
        Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
        System.out.println(obj);

        // 释放资源
        JDBCUtilsByDruid.close(null, null, connection);
    }
10.6.3.3 完成DML语句(update, insert ,delete)
    public void testDML() throws SQLException {
        //1. 得到 连接 (druid)
        Connection connection = JDBCUtilsByDruid.getConnection();
        //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
        //3. 创建 QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4. 这里组织sql 完成 update, insert delete
        //String sql = "update actor set name = ? where id = ?";
        //String sql = "insert into actor values(null, ?, ?, ?, ?)";
        String sql = "delete from actor where id = ?";

        //老韩解读
        //(1) 执行dml 操作是 queryRunner.update()
        //(2) 返回的值是受影响的行数 (affected: 受影响)
        //int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
        int affectedRow = queryRunner.update(connection, sql, 1000 );
        System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");

        // 释放资源
        JDBCUtilsByDruid.close(null, null, connection);
    }

10.7 BasicDAO(CRUD的通用方法)

10.7.1 引出BasicDAO

BasicDAO与数据库中表的示意图:

总结:

10.7.2 BasicDAO实现

public class BasicDAO<T> {  // 泛型指定具体类型
    private QueryRunner qr = new QueryRunner();

    // 开发通用的DML方法,针对任意表

    public int update (String sql, Object... parameters) {
        Connection connection = null;

        try {
            connection = JDBCUtilsDruid.getConnection();
            int update = qr.update(connection, sql, parameters);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e); // 将编译异常->运行异常,方便使用者处理(既可以抛出,也可以打印)
        } finally {
            JDBCUtilsDruid.close(null, null, connection); // 关闭连接
        }
    }

    //返回多个对象(即查询的结果是多行), 针对任意表
    /**
     *
     * @param sql sql 语句,可以有 ?
     * @param clazz 传入一个类的Class对象 比如 Actor.class
     * @param parameters 传入 ? 的具体的值,可以是多个
     * @return 根据Actor.class 返回对应的 ArrayList 集合
     */
    public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtilsDruid.getConnection();
            // 返回某类型的对象List数组列表
            return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e); // 将编译异常->运行异常,方便使用者处理(既可以抛出,也可以打印)
        } finally {
            JDBCUtilsDruid.close(null, null, connection); // 关闭连接
        }
    }

    // 查询单行结果的通用方法
    // 以Actor为例,查询结果肯定是返回一个Actor类型的对象
    public T querySingle(String sql, Class<T> clazz, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtilsDruid.getConnection();
            return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e); // 将编译异常->运行异常,方便使用者处理(既可以抛出,也可以打印)
        } finally {
            JDBCUtilsDruid.close(null, null, connection); // 关闭连接
        }
    }

    // 查询单行单列的方法,即返回单值的方法
    // 这里的Object是单行单列的某元素类型(int、string ....),如查询某行的姓名项,此时就是String类型,这里用Object类
    public Object queryScalar(String sql, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtilsDruid.getConnection();
            return qr.query(connection, sql, new ScalarHandler(), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e); // 将编译异常->运行异常,方便使用者处理(既可以抛出,也可以打印)
        } finally {
            JDBCUtilsDruid.close(null, null, connection); // 关闭连接
        }
    }
}

10.7.3 ActorDAO实现(具体实例)

ActorDAO类:

public class ActorDAO extends BasicDAO<Actor>{ // 继承BasicDAO并指定泛型类型为Actor类
    //1. 就有 BasicDAO 的方法
    //2. 根据业务需求,可以编写特有的方法.
}

测试方法:

public class TestActorDAO {
    public static void main(String[] args) {
        ActorDAO actorDAO = new ActorDAO();

        //1. 查询
        List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
        System.out.println("===查询结果===");
        for (Actor actor : actors) {
            System.out.println(actor);
        }

        //2. 查询单行记录
        Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6);
        System.out.println("====查询单行结果====");
        System.out.println(actor);

        //3. 查询单行单列
        Object o = actorDAO.queryScalar("select name from actor where id = ?", 6);
        System.out.println("====查询单行单列值===");
        System.out.println(o);

        //4. dml操作  insert ,update, delete
        int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张无忌", "男", "2000-11-11", "999");

        System.out.println(update > 0 ? "执行成功" : "执行没有影响表");
    }
}
posted @ 2021-10-02 14:43  Komorebi_WH  阅读(622)  评论(0编辑  收藏  举报