0%

数据库连接池

数据库连接池

★★★数据库连接池

  • 传统获取 Connection 连接的问题:

    1. 传统的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证 IP地址,用户名和密码 (0.05s ~ 1s时间)。

      需要数据库连接的时候,就向数据库要求一个,频繁地进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃,且多个数据库连接的频繁开启和关闭会非常耗时

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

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

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

    内存泄漏(Memory Leak是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

★★★数据库连接池原理
  1. 预先在连接池中放入一定数量的连接,当需要建立数据库连接时,只需从“连接池”中取出一个,使用完毕之后再放回去(而不是完全释放连接)

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

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

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C21-5.jpg)

  • 数据库连接池种类:

    1. JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现
    2. C3P0 数据库连接池,速度相对较慢,稳定性不错 (hibernate, spring)。
    3. DBCP 数据库连接池,速度相对 C3P0 较快,但不稳定。
    4. Proxool 数据库连接池,有监控连接池状态的功能,稳定性较 C3P0 差一点。
    5. BoneCP 数据库连接池,速度快。
    6. Druid(德鲁伊) 是阿里提供的数据库连接池,集DBCPC3POProxool 优点于一身的数据库连接池。
  • 注意:在使用数据库连接池时,应该先导入相应的 jar 包才行。

C3P0数据库连接池
  • 前置工作:在项目下创建一个文件夹,例如 lib ,将 C3P0jar 包拷贝到该目录下,通过添加库导入该 jar 包。-> 超详细的JDBC基础,内含 C3P0 和 Druid 等 JAR 包下载-CSDN博客

  • public final class ComboPooledDataSource extends AbstractComboPooledDataSource implements Serializable, Referenceable

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C21-6.jpg)

  • 有两种使用 C3P0 数据库连接池的方式:

    1. 手动给数据源设置相关的参数。
    2. 使用配置文件模板来完成相关参数的设置。-> 推荐使用第二种方式。

    C3P0 的配置文件模板为 c3p0-config.xml,需要将该配置文件放在项目的 src 目录下,配置文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <?xml version="1.0" encoding="UTF-8"?>

    <c3p0-config>
    <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
    <property name="user">root</property>
    <property name="password">java</property>

    <property name="initialPoolSize">10</property>
    <property name="maxIdleTime">30</property>
    <property name="maxPoolSize">100</property>
    <property name="minPoolSize">10</property>
    </default-config>
    <!-- 数据源名称,代表连接池 -->
    <named-config name="mySource">
    <!-- 驱动类 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <!-- url -->
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/java_db</property>
    <!-- 用户名 -->
    <property name="user">root</property>
    <!-- 密码 -->
    <property name="password">123456</property>
    <!-- 初始的连接数 -->
    <property name="initialPoolSize">10</property>
    <!-- 每次增长的连接数 -->
    <property name="acquireIncrement">5</property>
    <property name="maxIdleTime">30</property>
    <!-- 最大连接数 -->
    <property name="maxPoolSize">100</property>
    <!-- 最小连接数 -->
    <property name="minPoolSize">10</property>
    <!-- 可连接的最多的命令对象数 -->
    <property name="maxStatements">5</property>
    <!-- 每个连接对象可连接的最多的命令对象数 -->
    <property name="maxStatementsPerConnection">2</property>
    </named-config>
    </c3p0-config>

    连接 C3P0 的两种方式代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    package com.f.chapter21.jdbc.datasource;

    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.junit.jupiter.api.Test;

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

    /**
    * @author fzy
    * @date 2023/8/7 21:26
    * 演示C3P0数据库连接池的使用
    */
    public class C3P0_ {
    @Test
    //方式1:相关参数在程序中指定,如 user、password、url等
    public void testC3P0_01() throws Exception {
    //1.创建一个数据源对象
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    //2.通过配置文件获取相关的连接信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("file\\jdbc.properties"));
    String driver = properties.getProperty("driver");
    String url = properties.getProperty("url");
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    //3.手动给数据源 comboPooledDataSource 设置相关的参数
    // 连接是由 comboPooledDataSource 来管理的
    comboPooledDataSource.setDriverClass(driver);
    comboPooledDataSource.setJdbcUrl(url);
    comboPooledDataSource.setUser(user);
    comboPooledDataSource.setPassword(password);
    comboPooledDataSource.setInitialPoolSize(10); //初始化连接数为 10 个
    comboPooledDataSource.setMaxPoolSize(50); //设置连接池的最大连接数为 50 个
    //4.得到连接池中的数据库连接
    Connection connection = comboPooledDataSource.getConnection();
    System.out.println("第一种方式连接成功,其中一个连接为 " + connection);
    //5.将连接归还给数据库连接池
    connection.close();
    }

    @Test
    //方式2:使用配置文件模板来完成
    public void testC3P0_02() throws Exception {
    //1.将 C3P0 提供的 c3p0-config.xml 配置文件放在项目的 src 目录下
    // 在文件中指定连接数据库和连接池的相关参数
    //2.创建一个数据源对象,将配置文件中对应的数据源的名称作为参数传入
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mySource");
    //3.得到连接池中的数据库连接
    Connection connection = comboPooledDataSource.getConnection();
    System.out.println("第二种方式连接成功,其中一个连接为 " + connection);
    //4.将连接归还给数据库连接池
    connection.close();
    }
    }
    • 注意:在数据库连接池技术中,close 不是真的断掉连接,而是把使用的 Connection 对象放回连接池
★Druid数据库连接池
  • 前置工作:在项目下创建一个文件夹,例如 lib ,将 Druidjar 包拷贝到该目录下,通过添加库导入该 jar 包。-> 超详细的JDBC基础,内含 C3P0 和 Druid 等 JAR 包下载-CSDN博客

  • C3P0 类似,Druid 也有配置文件模板 druid.properties,配置文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #key=value
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/java_db?useSSL=false&rewriteBatchedStatements=true
    username=root
    password=123456
    #初始化连接数
    initialSize=10
    #最小连接数
    minIdle=5
    #最大连接数
    maxActive=50
    #最大超时(单位为ms)
    maxWait=5000
  • 然后就可以利用该配置文件进行 Druid 数据库连接池的连接了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.f.chapter21.jdbc.datasource;

    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.util.Properties;

    /**
    * @author fzy
    * @date 2023/8/12 21:03
    */
    public class Druid_ {
    @Test
    public void testDruid() throws Exception {
    //1.创建Properties对象,读取配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("file\\druid.properties"));
    //2.根据 Properties 对象创建 Druid 数据库连接池
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    //3.得到连接池中的数据库连接
    Connection connection = dataSource.getConnection();
    //4.将连接归还给数据库连接池
    connection.close();
    }
    }
Druid工具类
  • 在引入了数据库连接池技术后,就可以将 JDBC 工具类进一步完善升级,例如升级为使用 Druid 数据库连接池技术的 Druid工具类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package com.f.chapter21.jdbc.utils;

    import com.alibaba.druid.pool.DruidDataSourceFactory;

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

    /**
    * @author fzy
    * @date 2023/8/12 21:25
    */
    public class JDBCUtilsByDruid {
    private static DataSource ds;

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

    //得到Druid数据库连接池的Connection对象
    public static Connection getConnection() throws SQLException {
    return ds.getConnection();
    }

    //关闭连接,即将Connection对象归还给Druid数据库连接池
    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);
    }
    }
    }
  • Druid 工具类的使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package com.f.chapter21.jdbc.utils;

    import org.junit.jupiter.api.Test;

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

    /**
    * @author fzy
    * @date 2023/8/12 21:37
    */
    public class JDBCUtilsByDruid_Use {
    @Test
    public void testDML() throws SQLException {
    //1.得到连接
    Connection connection = JDBCUtilsByDruid.getConnection();
    //System.out.println(connection.getClass()); //class com.alibaba.druid.pool.DruidPooledConnection
    //2.组织一个dml sql语句
    String insertSQL = "INSERT INTO test_table VALUES (?, ?)";
    //3.创建PreparedStatement对象
    PreparedStatement preparedStatement = null;
    try {
    preparedStatement = connection.prepareStatement(insertSQL);
    //给占位符 ? 赋值
    preparedStatement.setInt(1, 1);
    preparedStatement.setString(2, "北京新闻");
    //执行dml sql语句
    preparedStatement.executeUpdate();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    } finally {
    //4.关闭资源
    JDBCUtilsByDruid.close(null, preparedStatement, connection);
    }
    }

    @Test
    public void testSelect() throws SQLException {
    //1.得到连接
    Connection connection = JDBCUtilsByDruid.getConnection();
    //2.组织一个select sql语句
    String selectSQL = "SELECT * FROM test_table WHERE id = ?";
    //3.创建PreparedStatement对象
    PreparedStatement preparedStatement = null;
    //4.创建ResultSet对象
    ResultSet resultSet = null;
    try {
    preparedStatement = connection.prepareStatement(selectSQL);
    //给占位符 ? 赋值
    preparedStatement.setInt(1, 1);
    //执行select sql语句,得到 ResultSet 结果集
    resultSet = preparedStatement.executeQuery();
    while (resultSet.next()) {
    System.out.println(resultSet.getInt("id") + " - " + resultSet.getString("name"));
    }
    } catch (SQLException e) {
    throw new RuntimeException(e);
    } finally {
    //5.关闭资源
    JDBCUtilsByDruid.close(resultSet, preparedStatement, connection);
    }
    }
    }
    • 可以看到,虽然 mysqlDruid 都实现了 Connection 接口,但是

      • mysqljar 包中,其 Connection 的运行类型是 com.mysql.jdbc.JDBC4Connection
      • Druidjar 包中,其 Connection 的运行类型是 com.alibaba.druid.pool.DruidPooledConnection

      所以对于 mysqlDruidConnection 对象,其实现的 Connection 接口的 close 方法是不一样的,前者是真的关闭了连接,而后者是把使用的 Connection 对象放回了数据库连接池。

★★★Apache-DBUtils

  • 在使用 ResultSet 得到查询的数据时,会有诸多问题:

    1. ResultSetConnection 是关联的,如果关闭连接,就不能使用结果集。

      而如果一直保持连接,就会持续占用数据库连接池的资源,降低资源利用率。

    2. 结果集不利于数据管理 [只能用一次],即结果集用完就会关连接,所以结果集不能复用

    3. 使用结果集中的数据也不方便,getStringgetInt 等方法包含的信息量太少,相比之下,更希望是 getNamegetAge 等明确表达得到的数据的含义的方法名。

    为了解决上面使用 ResultSet 的问题,可以使用下图的方式:

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C21-7.jpg)

    1. 创建一个 Java 类,该类的每一个对象都对应数据表中的一条数据,则该类的属性即为数据表中的字段。**这样的类称为 JavaBean**。
    2. 然后根据 ResultSet 中的数据,得到该 Java 类的一组对象,将这些对象添加到 ArrayList<>,就相当于将 ResultSet 中的数据保存了下来。
    3. 这时即使关闭了连接,也可以使用得到的数据,实现了数据的复用。
  • Apache DBUtils 就实现了上述的方式。

    前置工作:在项目下创建一个文件夹,例如 lib ,将 DBUtilsjarcommons-dbutils.jar 拷贝到该目录下,通过添加库导入该 jar 包。-> Apache Commons DBUtils - 下载安装和环境设置

★ApDBUtils查询(源码分析)
  • commons-dbutilsApache 组织提供的一个开源 JDBC 工具类库,它是对 JDBC 的封装,使用 dbutils 能极大简化 JDBC 编码的工作量。

  • DBUtils 类:

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

      使用 QueryRunner 类实现查询。

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

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

      上表中的 BeanListHandler 所用的就类似于前面图中所说的方式。

  • 例子:使用 DBUtils + Druid 的方式,完成对 account 表的查询:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    package com.f.chapter21.jdbc.utils;

    import com.f.chapter21.jdbc.Account;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    import org.junit.jupiter.api.Test;

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

    /**
    * @author fzy
    * @date 2023/8/13 14:10
    * 使用Apache DBUtils工具类 + Druid数据库连接池的方式,完成对 `account` 表的查询
    */
    public class ApDBUtils_Use_Select {
    @Test
    public void testDBUtils_Query() throws SQLException {
    //1.使用 Druid 得到连接
    Connection connection = JDBCUtilsByDruid.getConnection();
    //使用 DBUtils 的类和接口
    //2.使用 QueryRunner 执行查询的操作,将得到的结果存放在 List 中
    QueryRunner queryRunner = new QueryRunner();
    String selectSQL = "SELECT * FROM account WHERE id >= ?";
    //创建了一个Account类,该Account类对应于account表
    // (1)query方法就是执行一个sql语句,得到一个Resultset,并将 ResultSet 封装到 ArrayList 集合中
    // (2)返回该 ArrayList 集合
    // (3)connection:连接
    // (4)selectSQL:执行的查询sql语句
    // (5)new BeanListHandler<>(Account.class):ResultSet -> Account对象(用反射得到Account类的信息) -> ArrayList
    // 底层使用反射机制获取 Account 类的属性,然后对 ResultSet 进行封装,封装为 Account对象
    // (6)1:传给sql语句中占位符 ? 的参数,可以有多个值,因为是可变参数 Object... params
    // (7)在 query 方法中会关闭 ResultSet 和 PreparedStatement 对象,所以后面无需关闭
    /**
    * query方法源码:
    * public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
    * if (conn == null) {
    * throw new SQLException("Null connection");
    * } else if (sql == null) {
    * throw new SQLException("Null SQL statement");
    * } else if (rsh == null) {
    * throw new SQLException("Null ResultSetHandler");
    * } else {
    * Statement stmt = null; //定义Statement
    * ResultSet resultSet = null; //接收查询得到的ResultSet
    * T result = null; //最终返回的ArrayList
    *
    * try {
    * if (params != null && params.length > 0) {
    * PreparedStatement ps = this.prepareStatement(conn, sql); //得到PreparedStatement
    * stmt = ps;
    * this.fillStatement(ps, params); //将sql语句的 ? 填入值
    * resultSet = this.wrap(ps.executeQuery()); //得到ResultSet并包装
    * } else {
    * stmt = conn.createStatement();
    * resultSet = this.wrap(((Statement)stmt).executeQuery(sql));
    * }
    *
    * result = rsh.handle(resultSet); //进一步包装,包装为ArrayList,在这里用到了反射,对传入的Account.class进行使用
    * } catch (SQLException var12) {
    * this.rethrow(var12, sql, params);
    * } finally {
    * this.closeQuietly(resultSet); //关闭ResultSet
    * this.closeQuietly((Statement)stmt); //关闭Statement
    * }
    *
    * return result; //返回ArrayList
    * }
    * }
    */
    List<Account> queryResult
    = queryRunner.query(connection, selectSQL, new BeanListHandler<>(Account.class), 1); //1是传给?的值
    System.out.println(queryResult);
    //3.关闭连接,释放资源
    JDBCUtilsByDruid.close(null, null, connection);
    }
    }

    Account 类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    package com.f.chapter21.jdbc;

    /**
    * @author fzy
    * @date 2023/8/13 14:42
    * Account类,对应mysql中的account表
    */
    public class Account { //JavaBean、POJO、Domain
    private Integer id;
    private String name;
    private Double balance;

    public Account() {
    }

    public Account(Integer id, String name, Double balance) {
    this.id = id;
    this.name = name;
    this.balance = balance;
    }

    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 Double getBalance() {
    return balance;
    }

    public void setBalance(Double balance) {
    this.balance = balance;
    }

    @Override
    public String toString() {
    return "Account{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", balance=" + balance +
    '}';
    }
    }
    • 多行多列:用 BeanListHandler,返回 ArrayList

      单行多列:用 BeanHandler,返回相应的类对象。

      单行单列:用 ScalarHandler,返回 Object

表和JavaBean的类型映射关系
表中的数据类型Java类型
intInteger
varcharString
charString
doubleDouble
datejava.util.Date
ApDBUtils-DML
  • 例子:使用 DBUtils + Druid 的方式,完成对 account 表的增加、删除、修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.f.chapter21.jdbc.utils;

    import org.apache.commons.dbutils.QueryRunner;
    import org.junit.jupiter.api.Test;

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

    /**
    * @author fzy
    * @date 2023/8/13 15:29
    * 使用Apache DBUtils工具类 + Druid数据库连接池的方式,完成对 `account` 表的DML(insert、delete、update)
    */
    public class ApDBUtils_Use_DML {
    @Test
    public void testDBUtils_DML() throws SQLException {
    //1.使用 Druid 得到连接
    Connection connection = JDBCUtilsByDruid.getConnection();
    //使用 DBUtils 的类和接口
    //2.使用 QueryRunner 执行dml的操作
    // (1)执行dml操作的方法都是 queryRunner.update
    // (2)返回的值是受影响的行数
    QueryRunner queryRunner = new QueryRunner();

    //insert
    String insertSQL = "INSERT INTO account VALUES (null, ?, ?)";
    int insertAffectedRows = queryRunner.update(connection, insertSQL, "王五", 1000);
    System.out.println(insertAffectedRows > 0 ? "插入成功" : "插入失败");

    //delete
    String deleteSQL = "DELETE FROM account WHERE name = ?";
    int deleteAffectedRows = queryRunner.update(connection, deleteSQL, "李四");
    System.out.println(deleteAffectedRows > 0 ? "删除成功" : "删除失败/所要删除的数据不存在");

    //update
    String updateSQL = "UPDATE account SET balance = ? WHERE name = ?";
    int updateAffectedRows = queryRunner.update(connection, updateSQL, 10000, "张三");
    System.out.println(updateAffectedRows > 0 ? "更新成功" : "更新失败/所要更新的数据不存在");

    //3.关闭连接,释放资源
    JDBCUtilsByDruid.close(null, null, connection);
    }
    }
★★★JavaBean属性名和表列名
  • JavaBean 类的属性名和其相应的 MySQL 表的列名之间会有一些问题:

    • 首先,JavaBean 中的字段名与 MySQL 列名的命名风格有所不同。**JavaBean 遵循驼峰式命名法**,即首字母小写,后面每一个单词的首字母都大写;而 MySQL 则采用下划线命名法,即采用下划线分隔单词,所有字母均小写。

      因此,在将 JavaBean 对象的属性保存到 MySQL 数据库中时,需要将 JavaBean 的字段名转化为 MySQL 列名。最直接的方式是直接在 SQL 语句中使用 “AS” 关键字将 JavaBean 字段名变成 MySQL 列名。反之同理,例如下面的代码:

      1
      SELECT user_id AS 'userId', user_name AS 'userName', age FROM user;

      否则是无法处理 JavaBean 的相应属性和 MySQL 表的相应列的。

      • 例如,在某个 JavaBean 类中有属性 userId,而在 MySQL 表中有列 user_id

        在查询得到 MySQL 表中的记录后,Apache-DBUtils 底层会通过反射调用 setUser_id 方法对该 JavaBean 的实例对象的 user_id 属性赋值,但是 JavaBean 中只有 setUserId 方法,所以就无法对 JavaBean 的实例对象的 userId 属性赋值,则该对象的 userId 值就会为 null。相当于查询无效。

      • 另外可以看到,JavaBean 类中,需要有对其属性的 Setter 方法,否则是无法将查询结果赋值给 JavaBean 的实例对象的

      但是,对于大型系统而言,这种方法可能会导致 SQL 语句变得臃肿且难以维护。

    • 因此,更好的做法是MySQL 表中使用 JavaBean 字段名作为列名。这样可以避免在 SQL 语句中使用 “AS” 关键字进行字段名转化,从而使得 SQL 语句更加简洁。

  • 总之,为了能将查询结果赋值给 JavaBean 实例对象,需要满足两个条件

    1. JavaBean 类的属性名和其相应的 MySQL 表的列名需要一致,可以一开始就设置为一样,也可以在 MySQL 查询中使用 AS
    2. JavaBean 类中需要有对其属性的 Setter 方法

★★★BasicDAO

  • 虽然 Apache-DBUtils + Druid 简化了 JDBC 开发,但还有不足:

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

    所以,我们需要使用到 DAO(Data Access Objects),数据访问对象

  • 如下图所示,在 Utils 工具类的基础上,我们加入 DAO ,来操作数据库中对应的表:

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C21-8.jpg)

    • Utils:工具类,如前面的数据库连接池 Druid 以及 Apache-DBUtils

    • DAO:数据访问对象类,不同的 XxxDAO 类对应不同的表,通过 XxxDAO 类的对象操作数据库中对应的表,同时还允许有独属于表的特有操作。

      • 不同的 XxxDAO 类操作数据库中与其对应的表,但不同的 XxxDAO 类应该有类似的共同操作,例如连接数据库、释放连接等,可以将各个 XxxDAO 类的共同操作提取出来,抽象为 BasicDAO,以简化代码,提高维护性和可读性。

        其他的 XxxDAO 类皆继承 BasicDAO 类,为其子类

      • 这样的通用类,称为 BasicDAO 类,是专门和数据库交互的,即完成对数据库(表)的crud 操作。

        **在 BaiscDAO 的基础上,实现一张表对应一个 DAO**,以更好地完成功能,比如 Customer表 — Customer.java(javabean)CustomerDao.java

    • **Domain**:对应于数据库中的表的类,如前面例子中的 Account 类对应于 account 表。

BasicDAO分析
  • 如前所述,在实现 BasicDAO 时,应该有以下几部分:
    1. utils 包,存放工具类。
    2. dao 包,存放 XxxDAOBasicDAO 类。
    3. domain 包,存放 JavaBean 类。
    4. test 包,存放测试类。
★★★BasicDAO实现
utils包
  • 这里较为简单,只有一个 Druid 数据库连接池的工具类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package com.f.chapter21.dao_.utils;

    import com.alibaba.druid.pool.DruidDataSourceFactory;

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

    /**
    * @author fzy
    * @date 2023/8/12 21:25
    */
    public class JDBCUtilsByDruid {
    private static DataSource ds;

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

    //得到Druid数据库连接池的Connection对象
    public static Connection getConnection() throws SQLException {
    return ds.getConnection();
    }

    //关闭连接,即将Connection对象归还给Druid数据库连接池
    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);
    }
    }
    }
dao包
  • BasicDAO 类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    package com.f.chapter21.dao_.dao;

    import com.f.chapter21.jdbc.utils.JDBCUtilsByDruid;
    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.List;

    /**
    * @author fzy
    * @date 2023/8/13 20:12
    * BasicDAO,是其他DAO的父类
    */
    public class BasicDAO<T> { //泛型指定具体的类型
    private QueryRunner queryRunner = new QueryRunner();

    //通用的dml操作,针对任意的表
    public int dml(String sql, Object... params) {
    //1.获得连接
    Connection connection = null;
    try {
    //2.执行sql语句
    connection = JDBCUtilsByDruid.getConnection();
    return queryRunner.update(connection, sql, params);
    } catch (SQLException e) {
    throw new RuntimeException(e);
    } finally {
    //3.关闭连接
    JDBCUtilsByDruid.close(null, null, connection);
    }
    }

    /**
    * 通用的select操作,针对任意的表,返回多行多列数据
    *
    * @param sql sql语句,可以有?
    * @param clazz 类的Class对象,例如Account.class
    * @param params 传入?的具体的值,可以有多个
    * @return 根据类的类型,返回对应的ArrayList集合
    */
    public List<T> queryMulti(String sql, Class<T> clazz, Object... params) {
    //1.获得连接
    Connection connection = null;
    try {
    //2.执行sql语句
    connection = JDBCUtilsByDruid.getConnection();
    return queryRunner.query(connection, sql, new BeanListHandler<T>(clazz), params);
    } catch (SQLException e) {
    throw new RuntimeException(e);
    } finally {
    //3.关闭连接
    JDBCUtilsByDruid.close(null, null, connection);
    }
    }

    //通用的select操作,针对任意的表,返回单行多列数据
    public T querySingle(String sql, Class<T> clazz, Object... params) {
    //1.获得连接
    Connection connection = null;
    try {
    //2.执行sql语句
    connection = JDBCUtilsByDruid.getConnection();
    return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), params);
    } catch (SQLException e) {
    throw new RuntimeException(e);
    } finally {
    //3.关闭连接
    JDBCUtilsByDruid.close(null, null, connection);
    }
    }

    //通用的select操作,针对任意的表,返回单行单列数据,即返回单值
    public Object queryScalar(String sql, Object... params) {
    //1.获得连接
    Connection connection = null;
    try {
    //2.执行sql语句
    connection = JDBCUtilsByDruid.getConnection();
    return queryRunner.query(connection, sql, new ScalarHandler<>(), params);
    } catch (SQLException e) {
    throw new RuntimeException(e);
    } finally {
    //3.关闭连接
    JDBCUtilsByDruid.close(null, null, connection);
    }
    }
    }
  • AccountDAO 类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.f.chapter21.dao_.dao;

    import com.f.chapter21.dao_.domain.Account;

    /**
    * @author fzy
    * @date 2023/8/13 20:43
    */
    public class AccountDAO extends BasicDAO<Account> {
    //1.拥有BasicDAO的所有方法
    //2.可以根据业务需求,编写独属于该表的特有方法
    }
domain包
  • 对应于 account 表的 Account 类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    package com.f.chapter21.dao_.domain;

    /**
    * @author fzy
    * @date 2023/8/13 14:42
    * Account类,对应mysql中的account表
    */
    public class Account { //JavaBean、POJO、Domain
    private Integer id;
    private String name;
    private Double balance;

    public Account() {
    }

    public Account(Integer id, String name, Double balance) {
    this.id = id;
    this.name = name;
    this.balance = balance;
    }

    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 Double getBalance() {
    return balance;
    }

    public void setBalance(Double balance) {
    this.balance = balance;
    }

    @Override
    public String toString() {
    return "Account{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", balance=" + balance +
    '}';
    }
    }
test包
  • 用于测试 XxxDAO 类的 TestDAO 类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package com.f.chapter21.dao_.test;

    import com.f.chapter21.dao_.dao.AccountDAO;
    import com.f.chapter21.dao_.domain.Account;
    import org.junit.jupiter.api.Test;

    import java.util.List;

    /**
    * @author fzy
    * @date 2023/8/13 20:45
    */
    public class TestDAO {
    @Test
    //测试 AccountDAO 对 account 表的 crud 操作
    public void testAccountDAO() {
    AccountDAO accountDAO = new AccountDAO();
    //1.测试查询操作
    // (1)查询多行多列数据
    List<Account> accounts = accountDAO.queryMulti("SELECT * FROM account WHERE id >= ?", Account.class, 1);
    System.out.println(accounts);
    // (2)查询单行多列数据
    Account account = accountDAO.querySingle("SELECT * FROM account WHERE id = ?", Account.class, 1);
    System.out.println(account);
    // (3)查询单行单列数据,即查询单值
    Object name = accountDAO.queryScalar("SELECT name FROM account WHERE id = ?", 1);
    System.out.println(name);

    //2.测试dml操作
    int insertAffectedRows = accountDAO.dml("INSERT INTO account VALUES (null, ?, ?)", "王五", 1000);
    System.out.println(insertAffectedRows > 0 ? "插入成功" : "插入失败");
    int deleteAffectedRows = accountDAO.dml("DELETE FROM account WHERE name = ?", "李四");
    System.out.println(deleteAffectedRows > 0 ? "删除成功" : "删除失败/所要删除的数据不存在");
    int updateAffectedRows = accountDAO.dml("UPDATE account SET balance = ? WHERE name = ?", 20000, "张三");
    System.out.println(updateAffectedRows > 0 ? "更新成功" : "更新失败/所要更新的数据不存在");
    }
    }
---------------The End---------------