Review: Java SE 05(JDBC、数据库连接池和DBUtils)

Java SE 05

一、JDBC

1. JDBC 概述

  • JDBC(Java Data Base Connectivity) 是 Java 访问数据库的标准规范。是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。是 Java 访问数据库的标准规范
  • JDBC 就是由 sun 公司定义的一套操作所有关系型数据库的规则 (接口),而不同的数据库厂商需要实现这套接口,提供数据库驱动 jar 包,开发者可以使用这套接口编程, 真正执行的代码是对应驱动包中的实现类

2. 使用 JDBC 实现类进行开发的流程

  • 添加 MySQL 驱动 jar 包到当前项目下

  • 注册驱动

    作用:将厂商的 jar 包中的实现类加载到当前类下,比如com.mysql.jdbc.Driver中定义了静态代码块当 Driver 类加载后执行静态代码块中的注册驱动语句

    从 JDBC3 开始,目前已经普遍使用的版本,可以不用注册驱动而直接使用,Class.forName 这句话可以省略

  • 获得连接

    使用 DriverManager 类中 getConnection 方法来获取连接即获取 Connection 对象

    getConnection 方法中需要传入的参数有 3 个

    URL(包括数据库厂商名称、服务器所在 IP、端口号、所选数据库、参数信息(可选)),数据库用户名,密码

  • 获取语句执行平台(获取 Statement 接口对象)

    通过 Connection 对象调用 createStatement 方法来获取 Statement 对象

    Statement 对象:代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象

    Statement 对象中常用方法:

    int executeUpdate(String sql) 用于执行 insert update delete 语句,也支持 DDL.返回 int 类型, 代表受影响的行数

    ResultSet executeQuery(String sql) 用于执行 select 语句, 返回 ResultSet 结果集对象

    注意:sql 字符串中写 SQL 语句,其中字符串使用单引号,语句结尾;可以省略

  • 处理结果集(只在查询时处理)

    只有在进行查询操作 executeQuery 的时候, 才会处理结果集 ResultSet

    ResultSet 常用方法:

    boolean next()游标指向下一行,还有下一条记录就返回 true

    xxx getXxx( String or int)重载方法,通过列名或列号(索引从 1 开始)进行获取数据

  • 释放资源

    • 需要释放的对象:ResultSet 结果集 (非查询,不用释放),Statement 语句,Connection 连接

    • 释放原则:先开的后关,后开的先关;ResultSet ==> Statement ==> Connection

      释放方法就是调用对应的 close 方法

    • 一般将释放资源的语句放在异常处理的 finally 代码块中

3. JDBC 实现增删改查

  • JDBC 工具类

    • 工具类:如果一个功能经常要用到,建议把这个功能做成一个工具类,可以在不同的地方重用

    • 获取 Connection释放资源在增删改查中会重复使用,所以将这两个操作的方法封装为 JDBCUtils 工具类中的静态方法

      获取 Connection 需要 URL、数据库用户名、密码、驱动全类名的字符串(可省)可以将这些字符串设置为工具类中的常量,

      若使用低版本的 JDBC 中还需要将 Class.forName 加载驱动的操作也封装在工具类中

    • 封装 JDBCUtils 工具类的实例(不含注册驱动的版本)

      public class JDBCUtils {
      	//1. 定义字符串常量, 记录获取连接所需要的信息
          public static final String URL = "jdbc:mysql://localhost:3306/db_jdbc_test";
          public static final String USERNAME = "root";
          public static final String PASSWORD = "xxxx";
      
      <span class="token comment">//2.获取连接的静态方法</span>
      <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Connection</span> <span class="token function">getConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">try</span> <span class="token punctuation">{</span>
              <span class="token comment">//获取连接对象</span>
              <span class="token class-name">Connection</span> conn <span class="token operator">=</span> <span class="token class-name">DriverManager</span><span class="token punctuation">.</span><span class="token function">getConnection</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">,</span> <span class="token constant">USERNAME</span><span class="token punctuation">,</span> <span class="token constant">PASSWORD</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token comment">//返回连接对象</span>
              <span class="token keyword">return</span> conn<span class="token punctuation">;</span>
          <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">SQLException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
              e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
      
      <span class="token comment">//关闭资源的方法(DML)</span>
      <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">close</span><span class="token punctuation">(</span><span class="token class-name">Connection</span> conn<span class="token punctuation">,</span> <span class="token class-name">Statement</span> stmt<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span>conn <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> stmt <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
              <span class="token keyword">try</span> <span class="token punctuation">{</span>
                  conn<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                  stmt<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">SQLException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                  e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span>
          <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
      
      <span class="token comment">//关闭资源的方法(重载方法用于DQL后的释放资源)</span>
      <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">close</span><span class="token punctuation">(</span><span class="token class-name">Connection</span> conn<span class="token punctuation">,</span> <span class="token class-name">Statement</span> stmt<span class="token punctuation">,</span> <span class="token class-name">ResultSet</span> res<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
              <span class="token keyword">try</span> <span class="token punctuation">{</span>
                  <span class="token comment">//对ResultSet对象进行关闭</span>
                  res<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">SQLException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                  e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span>
          <span class="token punctuation">}</span>
          <span class="token comment">//直接复用DML的释放资源方法</span>
          <span class="token function">close</span><span class="token punctuation">(</span>conn<span class="token punctuation">,</span> stmt<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
      

      }

  • DML 操作

    • 防止插入中文乱码的解决方案

      将工具类中 URL 字符串中指定字符的编解码格式

      jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8

    • 使用工具类进行插入、删除、更改

      public void test() throws SQLException { 
          //1. 通过工具类获取连接
          Connection connection = JDBCUtils.getConnection();
          //2. 获取 Statement
          Statement statement = connection.createStatement();
          //2.1 编写 Sql
          String sql = "xxx";
          //2.2 执行 Sql
          statement.executeUpdate(sql);
          //3. 通过工具类关闭流
          JDBCUtils.close(connection,statement);
      }
      
  • DQL 操作

    • 使用工具类进行查询操作

      public void test() throws SQLException { 
          //1. 通过工具类获取连接
          Connection connection = JDBCUtils.getConnection();
          //2. 获取 Statement
          Statement statement = connection.createStatement();
          //2.1 编写 Sql
          String sql = "xxx";
          //2.2 执行 Sql, 获取 ResultSet 对象
          ResultSet res = statement.executeUpdate(sql);
          //2.3 处理结果集
          //3. 通过工具类关闭流
          JDBCUtils.close(connection, statement, res);
      }
      

4. SQL 注入问题

  • 问题:用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了原有 SQL 真正的意义

    拼接后 WHERE 条件永远为真,相当于没有过滤,查询全部数据,造成密码不正确也能进行查询现象

  • 解决:防止用户输入的密码和执行查询的 SQL 语句进行拼接

5. PreparedStatement 接口

  • PreparedStatement 接口概述

    • PreparedStatement 接口是 Statement 接口的子接口,继承了 Statement 中的所有方法

    • PreparedStatement 是一个预编译的 SQL 语句对象

      预编译是指 SQL 语句被预编译, 并存储在 PreparedStatement 对象中,然后可以使用此对象多次高效地执行该语句

  • PreparedStatement 特点

    • 预编译可以提高 SQL 执行的效率
    • 可以有效防止 SQL 注入的问题
    • 可以减少编译次数提高数据库性能
    • PrepareStatement 是预编译的 SQL 语句对象,语句中可以包含动态参数?,在执行时可以为?动态设置参数值
  • 获取 PreparedStatement 对象

    通过 Connection 中的方法PreparedStatement prepareStatement(String sql)

    通过 Connection 中的方法获取 Statement 对象的方法Statement createStatement()中是空参的,

    使用 Statement 对象时才传入 SQL 参数

    指定预编译的 SQL 语句,SQL 语句中使用占位符 ? 创建一个语句对象

    如:"SELECT * FROM jdbc_user WHERE username=? AND password=?";

  • 使用 PreparedStatement 步骤

    1. 获取 PreparedStatement 对象(传入包含动态参数的 SQL 语句)

    2. 使用setXxx(占位符位置,真实的值)方法设置占位符参数

      占位符的起始索引是 1

    3. 执行 PreparedStatement 中处理 SQL 的方法

      PreparedStatement 接口中的常用方法同 Statement 接口,不同是 Statement 的方法中传参是完整的静态 SQL 字符串:

      int executeUpdate(); 执行 insert、update、delete 语句

      ResultSet executeQuery(); 执行 select 语句,返回结果集对象 ResuletSet

    4. 释放资源(调用 PreparedStatement 对象中的 close 方法)

  • Statement 与 PreparedStatement 的对比

    • 先通过 Connection 生成对象 Statement,Statement 对象每调用一次处理 SQL 的方法,就会将 SQL 发送给数据库,数据库要先编译再执行;

      假如有一万条数据,就会编译一万次

    • 通过 Connection 生成 PreparedStatement 对象的同时会将 SQL 语句发送给数据库进行预编译,后面执行多次插入操作,每次只需要调用 PreparedStatement 中的 setXxx 方法进行占位符的参数设置和调用处理 SQL 的方法;

      假如有一万条数据,数据库也只是进行一次编译,所以可以减少编译次数,提高效率

6. JDBC 控制事务

  • 可以使用 MySQL 的命令来操作事务,也可以使用 JDBC 实现类来操作事务

  • 一般使用 Connection 中的方法实现事务管理

    使用步骤:

    1. 获取连接

    2. 开启事务

      void setAutoCommit(boolean autoCommit)

      参数是 true 或 false 如果设置为 false,表示关闭自动提交,相当于开启事务

    3. 获取到 PreparedStatement , 执行更新操作

    4. 正常情况下提交事务

      void commit()

    5. 出现异常回滚事务

      void rollback()

    6. 关闭资源

二、数据库连接池和 DBUtils

1. 数据库连接池

  • 连接池概述

    • 作用:实际开发中获得连接释放资源是非常消耗系统资源的两个过程,为了解决性能的问题,通常采用连接池技术,来共享 Connection。这样就不需要每次都创建连接、释放连接了,将这些操作都交给连接池了。

      用池来管理 Connection,这样可以重复使用 Connection。 当使用完 Connection 后,调用 Connection 的 close() 方法也不会真的关闭 Connection,而是把 Connection“归还”给池。

    • Java 为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池。

  • DBCP 连接池

    • 在 DBCP 包中提供了 DataSource 接口的实现类,具体是连接池 BasicDataSource 类

      DataSource 接口中的常用方法:

      Connection getConnection()

    • 封装 DBCPUtils 工具类:

      1. 定义常量保存数据库连接的相关信息(DRIVERNAME、URL、USERNAME、PASSWORD)
      2. 创建连接池对象(使用 DBCP 提供的实现类 BasicDataSource 类,将该对象定义为静态成员变量)
      3. 使用静态代码块进行数据库连接的相关信息配置(使用 BasicDataSource 中的 setXxxx 方法)
      4. 封装获取连接的静态方法(使用 BasicDataSource 类创建的静态成员变量调用 getConnection 方法)
      5. 封装释放资源的静态方法(DML、DQL 两种 close 方法重载)
    • 使用 DBCPUtils 工具类:

      获得连接释放资源时使用 DBCPUtils 中的方法来实现对数据库的 DML、DQL 操作

  • C3P0 连接池

    • C3P0 提供的核心工具类, ComboPooledDataSource , 如果想使用连接池, 就必须创建该类的对象

    • 封装 C3P0Utils 工具类:

      1. 创建连接池对象通过使用 C3P0 对 DataSource 接口的实现类,将这个对象作为 C3P0Utils 的静态成员变量

        C3P0 中的配置信息都存储在配置文件 c3p0-config.xml 中,可以定义默认配置和指定配置名的配置

        在 ComboPooledDataSource 构造方法中空参为使用 XML 的默认配置,带参为使用 XML 中指定配置名

      2. 封装获取连接的方法(使用 ComboPooledDataSource 类创建的静态成员变量调用 getConnection 方法)

      3. 封装释放资源的方法(DML、DQL 两种 close 方法重载)

    • 使用 C3P0Utils 工具类

      获得连接释放资源时使用 C3P0Utils 中的方法来实现对数据库的 DML、DQL 操作

  • Druid 连接池

    • Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid 是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控 DB 池连接和 SQL 的执行情况

    • 必要的 jar 包

    • 封装 DruidUtils 工具类:

      1. 通过工厂类 DruidDataSourceFactory 的createDataSource(Properties p)方法获取连接池 DataSource 类型对象

        • Druid 中的配置信息一般存储在.properties文件中,文件名可自定义

        • 设置一个静态代码块在 DruidUtils 工具类加载时创建连接池 DataSource 类型的对象:

          Druid 连接池不能够主动加载配置文件 , 需要指定文件,将指定的文件加载到字节输入流对象中传入 Properties 类型对象的 load 方法中,传入工厂类createDataSource(Properties p)方法中来创建连接池对象

      2. 封装获取连接的方法(使用 DataSource 类型的静态成员变量调用 getConnection 方法)

      3. 封装释放资源的方法(DML、DQL 两种 close 方法重载)

    • 使用 DruidUtils 工具类

      获得连接释放资源时使用 DruidUtils 中的方法来实现对数据库的 DML、DQL 操作

2. DBUtils 工具类

  • DBUtils 中的核心功能

    DbUtils 类, 是一个工具类, 定义了关闭资源与事务处理相关方法

  • JavaBean 组件的概念

    • JavaBean 就是一个类,开发中通常用于封装数据
      1. 需要实现序列化接口 Serializable
      2. 提供私有字段
      3. 提供 getter 和 setter
      4. 提供空参构造
  • 使用 DBUtils 中的 QueryRunner 完成增删改

    • 创建 QueryRunner

      自动方式创建需要向 QueryRunner 构造中传入 DataSource 类型的数据库连接池对象

      手动方式创建需要使用 QueryRunner 空参构造

    • QueryRunner 实现增删改操作主要方法

      update(Connection conn, String sql, Object... params)

      update(String sql, Object... params)

      • update 方法是有重载的方法,当 QueryRunner 是自动方式创建的不用传 Connection 对象,

        手动方式创建 QueryRunner 要传入 Connection 对象,使用连接池工具类调用其中的 getConnection 方法获取

      • 动态 SQL 中使用?作为占位符

      • params 是可变长度 Object 类型的变量(一般传入 Object[] 类型数组),用来设置占位符上的参数

      • 当动态 SQL 中使用?作为占位符的参数只有一个时,不需要创建数组传参,直接在 params 位置传入这一个参数即可

    • QueryRunner 实现增删改操作主要流程

      1. 创建 QueryRunner 对象(自动或手动)
      2. 调用 QueryRunner 对象中的 update 方法
  • 使用 DBUtils 中的 QueryRunner 完成查询操作

    • 创建 QueryRunner

    • QueryRunner 实现查询操作主要方法

      query(Connection con,String sql,handler,Object[] param)

      query(String sql, handler ,Object[] param)

      query 方法的返回值都是泛型, 具体的返回值类型, 会根据结果集的处理方式, 发生变化

      其中 handler 需要的对象类型是 ResultSetHandler 接口的实现类,ResultSetHandler 可以对查询出来的 ResultSet 结果集进行处理,达到一些业务上的需求,每一种实现类都代表了对查询结果集的一种处理方式

      常用的 ResultSetHandler 实现类:

      ArrayHandler 将结果集的第一条数据封装到数组中

      ArrayListHandler 可以将每条数据先封装到数组中, 再将数组封装到集合中

      BeanHandler 将结果集的第一条数据封装到 JavaBean 中

      BeanListHandler 将结果集的每一条和数据封装到 JavaBean 中 再将 JavaBean 放到 list 集合中

      MapHandler 将结果集的第一条记录封装到 Map<String,Object> 中 key 对应的是 列名 value 对应的是 列的值

      ScalarHandler 用于封装单个的数据

    • QueryRunner 实现查询操作主要流程

      1. 创建 QueryRunner 对象(自动或手动)
      2. 调用 QueryRunner 对象中的 query 方法

3. 数据库批处理

  • 数据库批处理概述

    • 批处理指的是一次操作中执行多条 SQL 语句,批处理相比于一次一次执行效率会提高很多
    • 批处理操作主要使用了 Statement 或 PreparedStatement 中的批处理方法
  • 实现数据库批处理

    • mysql 批处理是默认关闭的,所以需要加一个参数才打开 mysql 数据库批处理,在 url 中添加

      rewriteBatchedStatements=true

    • 批处理主要方法

      void addBatch()

      将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中,

      通过调用方法 executeBatch 可以批量执行此列表中的命令

      int[] executeBatch()

      每次提交一批命令到数据库中执行,如果所有的命令都成功执行了,

      那么返回一个数组,这个数组是说明每条命令所影响的行数

    • 实现数据库批处理的主要流程

      1. 获取 Connection 对象
      2. 获取 PreparedStatement 或 Statement 对象
      3. 将 SQL 添加到批处理列表
      4. 统一进行批量处理

4. MySQL 元数据

  • MySQL 元数据概述

    • 除了表之外的数据都是元数据,包括查询结果信息、数据库和数据表的信息、MySQL 服务器信息
    • 查看 MySQL 元数据的常用命令
  • JDBC 获取数据库元数据信息

    connection 连接对象, 调用 getMetaData() 方法, 获取的是 DatabaseMetaData 数据库元数据对象

    调用 DatabaseMetaData 中的方法获取数据库元数据信息

  • JDBC 获取结果集元数据信息

    PreparedStatement 预处理对象调用 getMetaData() , 获取的是 ResultSetMetaData , 结果集元数据对象

    调用 ResultSetMetaData 中的方法获取结果集元数据信息