Java入门-JDBC和数据库连接池

JDBC和数据库连接池

今日语录:一日很长,十年很短

一、JDBC概述

  • 简介

    Java入门-JDBC和数据库连接池
    image-20220509215845074
    1. JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
    2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作
    3. JDBC的基本原理
    4. 模拟JDBC
  • JDBC带来的好处

    1. 如果Java直接访问数据库

      Java入门-JDBC和数据库连接池
      image-20220509215942103
    2. JDBC带来的好处

      Java入门-JDBC和数据库连接池
      image-20220509220022717
    3. 说明:JDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可。不同的数据库厂商,提供不同实现。

  • JDBC API

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

    Java入门-JDBC和数据库连接池
    image-20220509220324167
  • JDBC程序编写步骤

    1. 注册驱动 – 加载Driver类
    2. 获取链接 – 得到Connection
    3. 执行增删改查 – 发送SQL 给mysql执行
    4. 释放资源 – 关闭相关连接
  • JDBC第一个程序

    通过JDBC对表actor进行添加,删除和修改操作

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class JDBC01_ {
     public static void main(String[] args) throws SQLException {
      // 前置工作,在项目下创建一个文件夹比如:lib
      // 将驱动程序jar包复制到这个文件夹中,选中jar右击,add as Library

      // 1、注册驱动
      Driver driver = new com.mysql.cj.jdbc.Driver();
      // 2、得到连接
      // (1)127.0.0.1:本地ip
      // (2)3306:表示mysql坚挺的端口
      // (3)mysql_learn:指定连接的数据库
      // (4)mysql的连接本质是socket连接
      String url = "jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&serverTimezone=UTC";
      Properties properties = new Properties();
      properties.setProperty("user","root");
      properties.setProperty("password","123456");

      Connection connect = driver.connect(url, properties);
      // 3、执行sql
      String sql = "INSERT INTO actor values(2,'MM','女','2022-05-10 14:50:59','15371009565')";
      // statement 用于执行静态SQL语句并返回其生成的结果的对象
      Statement statement = connect.createStatement();
      // 如果是dml语句,返回的就是影响行数
      int rows = statement.executeUpdate(sql);
      System.out.println("新增了:"+rows+" 条数据");
     }
    }

    Java入门-JDBC和数据库连接池
    image-20220510150034951

二、获取数据库连接5种方式

  • 方式一

      // 1、注册驱动
      Driver driver = new com.mysql.cj.jdbc.Driver();
      // 2、得到连接
      // (1)127.0.0.1:本地ip
      // (2)3306:表示mysql坚挺的端口
      // (3)mysql_learn:指定连接的数据库
      // (4)mysql的连接本质是socket连接
      String url = "jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&serverTimezone=UTC";
      Properties properties = new Properties();
      properties.setProperty("user","root");
      properties.setProperty("password","123456");

      Connection connect = driver.connect(url, properties);
  • 方式二

      Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
      Driver driver = (Driver)clazz.newInstance();

      String url = "jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&serverTimezone=UTC";
      Properties properties = new Properties();
      properties.setProperty("user","root");
      properties.setProperty("password","123456");

      Connection connect = driver.connect(url, properties);
  • 方式三

    Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
      Driver driver = (Driver)clazz.newInstance();

      String url = "jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&serverTimezone=UTC";
      String user = "root";
      String password = "123456";

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

      Connection connection = DriverManager.getConnection(url, user, password);
  • 方式四

    Class.forName("com.mysql.cj.jdbc.Driver");
      String url = "jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&serverTimezone=UTC";
      String user = "root";
      String password = "123456";

      Connection connection = DriverManager.getConnection(url, user, password);
  • 方式五:配置文件更灵活

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class JDBC05_ {
     public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\application.properties"));

      // 获取相关值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      String driver = properties.getProperty("driver");

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


      // 3、执行sql
      String sql = "INSERT INTO actor values(2,'MM','女','2022-05-10 14:50:59','15371009565')";
      sql = "UPDATE actor set sex='5' where id = 1";
      // statement 用于执行静态SQL语句并返回其生成的结果的对象
      Statement statement = connection.createStatement();
      // 如果是dml语句,返回的就是影响行数
      int rows = statement.executeUpdate(sql);
      //System.out.println("新增了:"+rows+" 条数据");
      System.out.println("更新了:"+rows+" 条数据");

      // 4、关闭资源
      statement.close();
      connection.close();
     }
    }

三、Statement

  • 简介

    SQL注入示例

    Java入门-JDBC和数据库连接池
    image-20220510214145482
    -- SQL注入成功
    -- 用 1' or 替换正确的id值
    -- 用 or '1' = '1 替换正确的name (or '1' = '1在sql注入中称为万能钥匙)
    select * from actor where id = '1' or' and name = 'or '1' = '1';
    Java入门-JDBC和数据库连接池
    image-20220510215022609
    • Statement
    • PreparedStatement
    • CallableStatement
    1. Statement对象用于执行静态SQL语句并返回其生成的结果的对象
    2. 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过
    3. Statement对象执行SQL语句,存在SQL注入风险
    4. SQL注入是利用某些系统没有设置对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句或命令,恶意攻击数据库
    5. 要防范SQL注入,只要使用PreparedStatement(从Statement扩展而来)取代Statement就可以了

四、PreparedStatement

  • 简介

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

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class PreparedStatement_ {
     public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\application.properties"));

      // 获取相关值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      String driver = properties.getProperty("driver");

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

      // 3、执行sql
      String sql = "INSERT INTO actor values(2,'MM','女','2022-05-10 14:50:59','15371009565')";
      sql = "UPDATE actor set sex='6' where id = 1";
      sql = "SELECT * FROM actor";
      sql = "select * from actor where id = ? and name = ? ";
      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = connection.prepareStatement(sql);
      statement.setString(1,"1' or");
      statement.setString(2,"or '1' = '1");
      // 如果是dml语句,返回的就是影响行数
      //int rows = statement.executeUpdate(sql);
      ResultSet r = statement.executeQuery();
      //System.out.println("新增了:"+rows+" 条数据");
      System.out.println("更新了:"+r.next()+" 条数据");
     }
    }

    Java入门-JDBC和数据库连接池
    image-20220510220733278

预处理DML

  • 新增记录

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class PreparedStatement_ {
     public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\application.properties"));

      // 获取相关值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      String driver = properties.getProperty("driver");

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

      // 3、执行sql
      String sql = "";
      // 添加记录
         sql = "INSERT INTO actor values(?,?,?,?,?)";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = connection.prepareStatement(sql);
      statement.setString(1,"3");
      statement.setString(2,"CC");
      statement.setString(3,"女");
      statement.setString(4,"2022-05-10 21:50:59");
      statement.setString(5,"13048678192");
      
      // 如果是dml语句,返回的就是影响行数
      int r = statement.executeUpdate();
      System.out.println("新增了:"+r+" 条数据");
     }
    }
    Java入门-JDBC和数据库连接池
    image-20220510221544475
  • 修改记录

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class PreparedStatement_ {
     public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\application.properties"));

      // 获取相关值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      String driver = properties.getProperty("driver");

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

      // 3、执行sql
      String sql = "";
      // 修改文件
      sql = "UPDATE actor set sex = ? where id = ?";
      // 删除记录
      //sql = "DELETE FROM actor where id = 1";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = connection.prepareStatement(sql);
      statement.setString(1,"0");
      statement.setString(2,"1");
      // 如果是dml语句,返回的就是影响行数
      int r = statement.executeUpdate();
      System.out.println("修改了:"+r+" 条数据");
     }
    }

    Java入门-JDBC和数据库连接池
    image-20220510221734062
  • 删除记录

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class PreparedStatement_ {
     public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\application.properties"));

      // 获取相关值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      String driver = properties.getProperty("driver");

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

      // 3、执行sql
      String sql = "";
      // 删除记录
      sql = "DELETE FROM actor where id = ?";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = connection.prepareStatement(sql);
      statement.setString(1,"3");
      // 如果是dml语句,返回的就是影响行数
      int r = statement.executeUpdate();
      System.out.println("删除了:"+r+" 条数据");
     }
    }

    Java入门-JDBC和数据库连接池
    image-20220510221909966

五、ResultSet(结果集)

  • 简介
    1. 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
    2. ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
    3. next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集。

六、JDBC API

Java入门-JDBC和数据库连接池
image-20220510222103534
Java入门-JDBC和数据库连接池
image-20220510222735529

七、封装JDBCUtils

package JDBC_;

import IO.Properties_;

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

/**
 * @author chenx
 * @date 2022年05月10日
 * 这是一个工具类,完成mysql的连接和关闭资源
 */

public class JDBCUtils {
 //定义相关的属性(4个),因为只需要一份,因此,我们做出static
 private static String user;        // 用户名
 private static String password;    // 密码
 private static String url;         // 链接
 private static String driver;      // 驱动类

 // 在静态代码块中去初始化
 static{

  try {
   Properties properties = new Properties();
   properties.load(new FileInputStream("src\application.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);
  }
 }


 /**
  * 关闭相关资源
  * @param resultSet 结果集
  * @param statement 或者 PreparedStatement
  * @param connection
  */

 public static void close(ResultSet resultSet, Statement statement, Connection connection){
  // 判断是否为null
  try{
   if(resultSet != null){
    resultSet.close();
   }
   if(statement != null){
    statement.close();
   }
   if(connection != null){
    connection.close();
   }
  }catch(SQLException e){
   throw new RuntimeException(e);
  }
 }

}

  • 修改

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class JDBCUtils_use {
     public static void main(String[] args) {

      Connection connection = null;

      String sql = "UPDATE actor set sex = ? where id = ?";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = null;
      try {
       connection = JDBCUtils.getConnection();
       statement = connection.prepareStatement(sql);
       statement.setString(1,"8");
       statement.setString(2,"1");
       // 如果是dml语句,返回的就是影响行数
       int resultSet = statement.executeUpdate();
       System.out.println("更新:"+resultSet);
      } catch (SQLException throwables) {
       throwables.printStackTrace();
      }finally {
       JDBCUtils.close(null,statement, connection);
      }
     }
    }

  • 查询

    package JDBC_;

    import java.sql.*;

    /**
     * @author chenx
     * @date 2022年05月10日
     */

    public class JDBCUtils_use {
     public static void main(String[] args) {

      Connection connection = null;

      String sql = "SELECT * FROM actor";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = null;
      ResultSet resultSet = null;
      try {
       connection = JDBCUtils.getConnection();
       statement = connection.prepareStatement(sql);
       // 如果是dml语句,返回的就是影响行数
       resultSet = statement.executeQuery();

       while(resultSet.next()){
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        System.out.println("查询结果:"+ id + " | " + name);
       }

      } catch (SQLException throwables) {
       throwables.printStackTrace();
      }finally {
       JDBCUtils.close(resultSet,statement, connection);
      }



     }
    }

    Java入门-JDBC和数据库连接池
    image-20220510230051687

八、事务

  • 简介

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

    模拟转账业务

    package JDBC_;

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

    /**
     * @author chenx
     * @date 2022年05月11日
     */

    public class Transaction_ {
     public static void main(String[] args) {
      Connection connection = null;

      // 马云 -> 马化腾 转账 1000 元
      String sql = "UPDATE account_02 SET balance = balance - 1000 WHERE id = 1";

      String sql2 =  "UPDATE account_02 SET balance = balance + 1000 WHERE id = 2";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = null;

      ResultSet resultSet = null;

      int rows = 0;
      try {
       connection = JDBCUtils.getConnection();
       statement = connection.prepareStatement(sql);

       // 如果是dml语句,返回的就是影响行数
       rows = statement.executeUpdate();

       System.out.println("sql更新结果:"+ rows );

       int i = 1 / 0;
       statement = connection.prepareStatement(sql2);
       // 如果是dml语句,返回的就是影响行数
       rows = statement.executeUpdate();
       System.out.println("sql2更新结果:"+ rows );

      } catch (SQLException throwables) {
       throwables.printStackTrace();
      }finally {
       JDBCUtils.close(resultSet,statement, connection);
      }
     }
    }

    Java入门-JDBC和数据库连接池
    image-20220511221932485
    1. 使用传统方式(无事务),发生异常导致数据不一致
Java入门-JDBC和数据库连接池
image-20220511222056488

2.使用事务

//用事务来解决
 @Test
 public void useTransaction() {
  Connection connection = null;

  // 马云 -> 马化腾 转账 1000 元
  String sql = "UPDATE account_02 SET balance = balance - 1000 WHERE id = 1";

  String sql2 =  "UPDATE account_02 SET balance = balance + 1000 WHERE id = 2";

  // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
  PreparedStatement statement = null;

  ResultSet resultSet = null;

  int rows = 0;
  try {
   // 在默认情况下,connection是默认自动提交
   connection = JDBCUtils.getConnection();
   // 关闭自动提交
   connection.setAutoCommit(false);

   statement = connection.prepareStatement(sql);

   // 如果是dml语句,返回的就是影响行数
   rows = statement.executeUpdate();

   System.out.println("sql更新结果:"+ rows );

   int i = 1 / 0;
   statement = connection.prepareStatement(sql2);
   // 如果是dml语句,返回的就是影响行数
   rows = statement.executeUpdate();
   System.out.println("sql2更新结果:"+ rows );

   // 提交事务
   connection.commit();

  } catch (SQLException e) {
   // 这里我们可以设置自动回滚
   System.out.println("sql执行异常,回滚!");
   try {
    connection.rollback();
   } catch (SQLException throwables) {
    throwables.printStackTrace();
   }
   e.printStackTrace();
  }finally {
   JDBCUtils.close(resultSet,statement, connection);
  }
 }
Java入门-JDBC和数据库连接池
image-20220511223830918
Java入门-JDBC和数据库连接池
image-20220511223857875

九、批处理

  • 简介

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

    2. JDBC的批量处理语句包括下面的方法:

      addBatch():添加需要批量处理的SQL语句或参数

      executeBatch():执行批量处理语句

      clearBatch():清空批处理包的语句
    3. JDBC连接MySQL时,如果要使用批处理功能,请在url中加参数 :

      ?rewriteBatchedStatements=true
    4. 批处理往往和PreparedStatement一起搭配使用,既可以减少编译次数,又可以减少运行次数,效率大大提高

  • 使用实例

    1. 传统方式

      @Test
       public void batchDemo(){
        Connection connection = null;

        // 马云 -> 马化腾 转账 1000 元
        String sql = "INSERT INTO admin2 values(null,?,?)";

        // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
        PreparedStatement statement = null;

        ResultSet resultSet = null;

        int rows = 0;
        try {
         // 在默认情况下,connection是默认自动提交
         connection = JDBCUtils.getConnection();
         // 关闭自动提交
         connection.setAutoCommit(false);

         statement = connection.prepareStatement(sql);

         System.out.println("开始执行插入操作");
         long startTime = System.currentTimeMillis();

         for(int i=0;i<10000;i++){
          statement.setString(1,"chenx"+1);
          statement.setString(2,"888");
          statement.executeUpdate();
         }

         // 提交事务
         connection.commit();

         long endTime = System.currentTimeMillis();

         System.out.println("总耗时"+(endTime-startTime));

        } catch (SQLException e) {
         // 这里我们可以设置自动回滚
         System.out.println("sql执行异常,回滚!");
         try {
          connection.rollback();
         } catch (SQLException throwables) {
          throwables.printStackTrace();
         }
         e.printStackTrace();
        }finally {
         JDBCUtils.close(resultSet,statement, connection);
        }
       }
      Java入门-JDBC和数据库连接池
      image-20220511230452808
    2. 批处理方式

      # 将url连接路径加上参数:?rewriteBatchedStatements=true

      # 批处理方式
       @Test
       public void useBatch(){
        Connection connection = null;

        // 马云 -> 马化腾 转账 1000 元
        String sql = "INSERT INTO admin2 values(null,?,?)";

        // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
        PreparedStatement statement = null;

        ResultSet resultSet = null;

        int rows = 0;
        try {
         // 在默认情况下,connection是默认自动提交
         connection = JDBCUtils.getConnection();
         // 关闭自动提交
         connection.setAutoCommit(false);

         statement = connection.prepareStatement(sql);

         System.out.println("开始执行插入操作");
         long startTime = System.currentTimeMillis();

         for(int i=0;i<10000;i++){
          statement.setString(1,"chenx"+1);
          statement.setString(2,"888");

          // 把sql语句加入到批处理包中
          statement.addBatch();

          // 当有1000条数据就批量执行
          if((i+1)%1000 == 0){
           statement.executeBatch();

           // 清空一次
           statement.clearBatch();
          }
         }

         // 提交事务
         connection.commit();

         long endTime = System.currentTimeMillis();

         System.out.println(endTime-startTime);

        } catch (SQLException e) {
         // 这里我们可以设置自动回滚
         System.out.println("sql执行异常,回滚!");
         try {
          connection.rollback();
         } catch (SQLException throwables) {
          throwables.printStackTrace();
         }
         e.printStackTrace();
        }finally {
         JDBCUtils.close(resultSet,statement, connection);
        }
       }

      Java入门-JDBC和数据库连接池
      image-20220511230657193

十、源码解读(JDK 1.8.0)

Java入门-JDBC和数据库连接池
image-20220513184145833

十一、数据库连接池

传统获取Connection问题分析

  1. 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名和密码(0.05~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃

  2. 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄露,最终将导致重启数据库

  3. 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MYSQL崩溃

  4. 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)

    Java入门-JDBC和数据库连接池
    image-20220513191739569

数据库连接池基本介绍

  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去

  2. 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。

  3. 当应用程序向连接池请求的连接数量超过最大连接数量时,这些请求将被加入到等待队列中

    Java入门-JDBC和数据库连接池
    image-20220513191537524

数据库连接池种类

  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. ComboPooledDataSource类图

    Java入门-JDBC和数据库连接池
    image-20220515175115891

2.使用示例

  • 方式一
@Test
 public void testC3p001() throws IOException, ClassNotFoundException, SQLException, PropertyVetoException {
  //1、创建一个数据源对象
  ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
  //2、通过配置文件读取连接信息
  Properties properties = new Properties();
  properties.load(new FileInputStream("src\application.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);
  // 5、设置最大连接数
  comboPooledDataSource.setMaxPoolSize(50);

  // 测试频繁连接连接池的效率,测试对mysl50000次操作
  long start = System.currentTimeMillis();
  for(int i=0;i < 50000; i++){
   Connection connection = comboPooledDataSource.getConnection();
   //System.out.println("数据库连接成功!");
   connection.close();
  }
  long end = System.currentTimeMillis();

  System.out.println("C3P0 50000次连接mysql 耗时="+(end-start)/1000+" 秒");
 }
Java入门-JDBC和数据库连接池
image-20220515180349881
  • 方式二

     // 使用配置文件模板来完成
     // 1、将c3p0提供的c3p0.config.xml拷贝到src目录下
     // 2、该文件制定了连接数据库和相关参数
     @Test
     public void testC3P0002() throws SQLException {
      ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("phoenix");
      // 测试频繁连接连接池的效率,测试对mysl50000次操作
      long start = System.currentTimeMillis();
      for(int i=0;i < 50000; i++){
       Connection connection = comboPooledDataSource.getConnection();
       connection.close();
      }
      long end = System.currentTimeMillis();

      System.out.println("C3P0 50000次连接mysql 耗时="+(end-start)+" 毫秒");
     }
    <c3p0-config>
    <!-- 数据源名称代表连接池-->
      <named-config name="phoenix">
    <!-- 驱动类 -->
      <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
      <!-- url-->
       <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/mysql_learn?serverTimezone=UTC</property>
      <!-- 用户名 -->
       <property name="user">root</property>
        <!-- 密码 -->
       <property name="password">123456</property>
       <!-- 每次增长的连接数-->
        <property name="acquireIncrement">5</property>
        <!-- 初始的连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- 最小连接数 -->
        <property name="minPoolSize">5</property>
       <!-- 最大连接数 -->
        <property name="maxPoolSize">10</property>

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

Druid(德鲁伊)

  • 使用实例

    @Test
     public void druidTest001() throws Exception {
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\druid.properties"));

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

      long start = System.currentTimeMillis();
      for(int i=0;i < 500000000; i++){
       Connection connection = dataSource.getConnection();
       connection.close();
      }
      long end = System.currentTimeMillis();
      // 1254ms
      System.out.println("Druid 5000000次连接mysql 耗时="+(end-start)+" 毫秒");
     }
  • DruidUtils

    package Druid_;

    import com.alibaba.druid.pool.DruidDataSourceFactory;

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

    /**
     * @author chenx
     * @date 2022年05月15日
     * 基于druid数据库连接池的工具类
     */

    public class DruidUtils_ {
     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();
     }

     // 关闭连接
     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 throwables) {
       throw new RuntimeException(throwables);
      }

     }
    }

    工具类使用实例

    package Druid_;

    import JDBC_.JDBCUtils;
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    import org.junit.jupiter.api.Test;

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

    /**
     * @author chenx
     * @date 2022年05月15日
     */

    public class DruidUtilsDemo_ {
     @Test
     public void druidTest001() throws Exception {

      String sql = "SELECT * FROM actor";

      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      PreparedStatement statement = null;
      ResultSet resultSet = null;
      Connection connection = null;
      try {
       connection = DruidUtils_.getConnection();
       statement = connection.prepareStatement(sql);
       // 如果是dml语句,返回的就是影响行数
       resultSet = statement.executeQuery();

       while(resultSet.next()){
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        System.out.println("查询结果:"+ id + " | " + name);
       }

      } catch (SQLException throwables) {
       throwables.printStackTrace();
      }finally {
       DruidUtils_.close(resultSet,statement, connection);
      }
     }
    }

    Java入门-JDBC和数据库连接池
    image-20220515223143509

Apache-DBUtils

背景

传统方式,connection关闭之后,无法操作resultSet

Java入门-JDBC和数据库连接池
image-20220515223912450
Java入门-JDBC和数据库连接池
image-20220515224519433

简介

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

DBUtils类

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

  2. 使用QueryRunner类实现查询

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

    Java入门-JDBC和数据库连接池
    image-20220515225450472

DBUtils使用实例(apache-DBUtils工具类 + Druid)

package Druid_;

import java.util.Date;

/**
 * @author chenx
 * @date 2022年05月15日
 * Actor 对象和 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;
 }

 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 String getSex() {
  return sex;
 }

 public void setSex(String sex) {
  this.sex = sex;
 }

 public Date getBorndate() {
  return borndate;
 }

 public void setBorndate(Date borndate) {
  this.borndate = borndate;
 }

 public String getPhone() {
  return phone;
 }

 public void setPhone(String phone) {
  this.phone = phone;
 }

 @Override
 public String toString() {
  return "nActor{" +
    "id=" + id +
    ", name='" + name + ''' +
    ", sex='" + sex + ''' +
    ", borndate=" + borndate +
    ", phone='" + phone + ''' +
    '}';
 }
}

查询

  • 查询多行数据返回
// 使用apache-DBUtils工具类 + Druid 完成对表的crud操作
 @Test
 public void druidTest001() throws Exception {

  // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
  Connection connection = null;
  try {
   // 得到connection
   connection = DruidUtils_.getConnection();

   // 使用DBUtils类和接口,先引入DBUtils相关的jar,加入本项目的依赖
   // 创建QueryRunner
    QueryRunner queryRunner = new QueryRunner();
   // 执行相关的方法,返回ArrayList结果集
   String sql = "SELECT * 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)
   List<Actor> list =
     queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class),1);

   for(Actor actor : list){
    System.out.println(actor);
   }

  } catch (SQLException throwables) {
   throwables.printStackTrace();
  }finally {
   DruidUtils_.close(null,null, connection);
  }
 }
Java入门-JDBC和数据库连接池
image-20220515232720209
  • 查询单行数据返回

    // 演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
     @Test
     public void testQuerySingle(){
      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      Connection connection = null;
      try {
       // 得到connection
       connection = DruidUtils_.getConnection();

       // 使用DBUtils类和接口,先引入DBUtils相关的jar,加入本项目的依赖
       // 创建QueryRunner
       QueryRunner queryRunner = new QueryRunner();
       // 执行相关的方法,返回ArrayList结果集
       String sql = "SELECT * FROM actor where id = ?";
       // 因为我们返回的单行记录 --> 单个对象,使用的Handler是BeanHandler
       Actor query = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);

       System.out.println(query);

      } catch (SQLException throwables) {
       throwables.printStackTrace();
      }finally {
       DruidUtils_.close(null,null, connection);
      }
     }
    Java入门-JDBC和数据库连接池
    image-20220516174600345
  • 查询单行单列返回结果

    // 演示 apache-dbutils + druid 完成 返回的结果是单行单列记录(单个对象)
     @Test
     public void testQuerySingleColunm(){
      // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
      Connection connection = null;
      try {
       // 得到connection
       connection = DruidUtils_.getConnection();

       // 使用DBUtils类和接口,先引入DBUtils相关的jar,加入本项目的依赖
       // 创建QueryRunner
       QueryRunner queryRunner = new QueryRunner();
       // 执行相关的方法,返回ArrayList结果集
       String sql = "SELECT name FROM actor where id = ?";
       // 因为我们返回的单行记录 --> 单个对象,使用的Handler是BeanHandler
       Object query = queryRunner.query(connection, sql, new ScalarHandler(), 1);

       System.out.println(query);

      } catch (SQLException throwables) {
       throwables.printStackTrace();
      }finally {
       DruidUtils_.close(null,null, connection);
      }
     }
    Java入门-JDBC和数据库连接池
    image-20220516175024960

新增

@Test
 public void insertByDBUtils(){
  // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
  Connection connection = null;
  try {
   // 得到connection
   connection = DruidUtils_.getConnection();

   // 使用DBUtils类和接口,先引入DBUtils相关的jar,加入本项目的依赖
   // 创建QueryRunner
   QueryRunner queryRunner = new QueryRunner();
   // 执行相关的方法,返回ArrayList结果集
   String sql = "insert into actor values (?,?,?,?,?)";
   // (1)执行dml操作是queryRunner.update()
   // (2返回的值是受影响的行数(affected:影响的行数)
   int query = queryRunner.update(connection,sql,55,"PP","女","2022-05-10 14:50:59","15371009565");

   System.out.println(query);

  } catch (SQLException throwables) {
   throwables.printStackTrace();
  }finally {
   DruidUtils_.close(null,null, connection);
  }
 }
Java入门-JDBC和数据库连接池
image-20220516180621292

修改

@Test
 public void updateByDBUtils(){
  // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
  Connection connection = null;
  try {
   // 得到connection
   connection = DruidUtils_.getConnection();

   // 使用DBUtils类和接口,先引入DBUtils相关的jar,加入本项目的依赖
   // 创建QueryRunner
   QueryRunner queryRunner = new QueryRunner();
   // 执行相关的方法,返回ArrayList结果集
   String sql = "update actor set id=? where id=?";
   // (1)执行dml操作是queryRunner.update()
   // (2返回的值是受影响的行数(affected:影响的行数)
   int query = queryRunner.update(connection,sql,99,1);

   System.out.println(query);

  } catch (SQLException throwables) {
   throwables.printStackTrace();
  }finally {
   DruidUtils_.close(null,null, connection);
  }
 }
Java入门-JDBC和数据库连接池
image-20220516175943783

删除

@Test
 public void deleteByDBUtils(){
  // PreparedStatement 用于执行静态SQL语句并返回其生成的结果的对象(可防止SQL注入)
  Connection connection = null;
  try {
   // 得到connection
   connection = DruidUtils_.getConnection();

   // 使用DBUtils类和接口,先引入DBUtils相关的jar,加入本项目的依赖
   // 创建QueryRunner
   QueryRunner queryRunner = new QueryRunner();
   // 执行相关的方法,返回ArrayList结果集
   String sql = "delete from actor where id = ?";
   // (1)执行dml操作是queryRunner.update()
   // (2返回的值是受影响的行数(affected:影响的行数)
   int query = queryRunner.update(connection,sql, 100);

   System.out.println(query);

  } catch (SQLException throwables) {
   throwables.printStackTrace();
  }finally {
   DruidUtils_.close(null,null, connection);
  }
 }
Java入门-JDBC和数据库连接池
image-20220516180920305

BasicDAO

  • 引出使用BasicDAO的背景

    Java入门-JDBC和数据库连接池
    image-20220516181815136
  • 简介

    1. DAO:data access object数据访问对象
    2. 这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的CRUD操作。
    3. 在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表->Customer类(javabean)->CustomerDao.java
  • 使用实例

    package dao_.dao;

    import Druid_.Actor;
    import Druid_.DruidUtils_;
    import com.mysql.cj.QueryResult;
    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 java.sql.Connection;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;

    /**
     * @author chenx
     * @date 2022年05月16日
     */

    public class BasicDAO<T// 泛型指定具体类型

     QueryRunner queryRunner = new QueryRunner();

     // 开发通用dml方法,针对任意表
     public int update(String sql, Object... parameters){

      Connection connection = null;

      try {
       connection = DruidUtils_.getConnection();
       int update = queryRunner.update(connection, sql, parameters);
       return update;

      } catch (SQLException throwables) {
       throw  new RuntimeException(throwables);
      }finally {
       DruidUtils_.close(null,null,connection);
      }

     }

     // 返回多个参数(即查询的结果是多行),针对任意表
     /**
      *
      * @param sql sql语句,可以有 ?
      * @param clazz 传入一个类的Class对象
      * @param parameter 传入可变长参数
      * @return
      */

     public List<T> queryMulti(String sql, Class<T> clazz, Object... parameter){

      Connection connection = null;

      try {
       connection = DruidUtils_.getConnection();
       return  queryRunner.query(connection, sql, new BeanListHandler<T>(clazz),parameter );
      } catch (SQLException throwables) {
       throw  new RuntimeException(throwables);
      }finally {
       DruidUtils_.close(null,null,connection);
      }
     }

     /**
      * 查询单条结果
      * @param sql
      * @param clazz
      * @param parameter
      * @return
      */

     public T querySingle(String sql, Class<T> clazz, Object... parameter){

      Connection connection = null;

      try {
       connection = DruidUtils_.getConnection();
       return  queryRunner.query(connection, sql, new BeanHandler<>(clazz),parameter);
      } catch (SQLException throwables) {
       throw  new RuntimeException(throwables);
      }finally {
       DruidUtils_.close(null,null,connection);
      }
     }

     /**
      * 查单列结果
      * @param sql
      * @param parameter
      * @return
      */

     public Object queryScalar(String sql, Object... parameter){

      Connection connection = null;

      try {
       connection = DruidUtils_.getConnection();
       return  queryRunner.query(connection, sql, new ScalarHandler(),parameter);
      } catch (SQLException throwables) {
       throw  new RuntimeException(throwables);
      }finally {
       DruidUtils_.close(null,null,connection);
      }
     }

    }

    package dao_.dao;

    import dao_.domain.Actor;
    import org.junit.jupiter.api.Test;

    import java.util.List;

    /**
     * @author chenx
     * @date 2022年05月16日
     */

    public class ActorDAO extends BasicDAO<Actor>{

     /**
      * 查多条
      */

     @Test
     public void testUpdate(){
      ActorDAO actorDAO = new ActorDAO();
      List<Actor> actors = actorDAO.queryMulti("select * from actor where id > ?", Actor.class, 1);
      for(Actor actor : actors){
       System.out.println("结果:"+actor);
      }
     }

     /**
      * 查单条
      */

     @Test
     public void test002(){
      ActorDAO actorDAO = new ActorDAO();
      Actor actors = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 99);
      System.out.println("结果:"+actors);
     }

     /**
      * 查单列
      */

     @Test
     public void test003(){
      ActorDAO actorDAO = new ActorDAO();
      Object obj = actorDAO.queryScalar("select name from actor where id = ?"99);
      System.out.println("结果:"+obj);
     }

     /**
      * 更新
      */

     @Test
     public void test004(){
      ActorDAO actorDAO = new ActorDAO();
      int obj = actorDAO.update("update actor set name=? where id=?""DHH",99);
      System.out.println("结果:"+obj);
     }

     /**
      * 新增
      */

     @Test
     public void test005(){
      ActorDAO actorDAO = new ActorDAO();
      int obj = actorDAO.update("insert into actor values(?,?,?,?,?)"666,"HDH","男","2022-05-16 21:35:56","123321123321");
      System.out.println("结果:"+obj);
     }

     /**
      * 删除
      */

     @Test
     public void test006(){
      ActorDAO actorDAO = new ActorDAO();
      int obj = actorDAO.update("delete from actor where id = ?"99);
      System.out.println("结果:"+obj);
     }
    }


原文始发于微信公众号(Whisper Coding):Java入门-JDBC和数据库连接池

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/255636.html

(0)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!