JDBC大总结

  • 所需要的所有依赖
  • 1. 概述
  • 1.1 什么是 JDBC
  • 1.2 JDBC的API
  • 1.3 开发步骤
  • 2. 获取数据库连接
  • 2.1 创建Java工程
  • 2.2 创建 JDBC 模块
  • 2.3 加入依赖
  • 2.4 编写程序来测试
  • 2.5 Statement
  • 2.6 ResultSet
  • 2.7 PreparedStatement
  • 3. 使用 JDBC来实现 CRUD
  • 3.1 插入数据
  • 3.1.1 编写 User 实体类
  • 3.1.2 编写Dao 接口
  • 3.1.3 编写Dao实现
  • 3.1.4 编写测试
  • 3.2 修改数据
  • 3.2.1 修改接口
  • 3.2.2 修改实现
  • 3.2.3 添加测试方法
  • 3.3 删除数据
  • 3.3.1 修改接口
  • 3.3.2 修改实现
  • 3.3.3 编写测试方法
  • 3.4 封装通用方法
  • 3.4.1 封装方法
  • 3.4.2 修改Dao实现
  • 3.5 编写工具类
  • 3.5.1 编写工具类
  • 3.5.2 修改实现类
  • 4. 查询
  • 4.1 查询多条数据
  • 4.1.1 修改Dao接口
  • 4.1.2 修改Dao实现类
  • 4.1.3 编写测试
  • 4.2 分页查询
  • 4.2.1 修改Dao接口
  • 4.2.2 修改Dao实现
  • 4.2.3 编写测试
  • 4.3 单条数据查询
  • 4.3.1 修改Dao接口
  • 4.3.2 修改Dao实现
  • 4.3.3 编写测试
  • 4.4 查询总记录数
  • 4.4.1 修改Dao接口
  • 4.4.2 修改Dao实现
  • 4.4.3 编写测试
  • 4.5 查询封装
  • 4.5.1 封装方法
  • 4.5.2 修改Dao实现类
  • 4.5.2 封装查询统计的方法
  • 4.5.4 修改Dao实现类
  • 5. 调整工具类
  • 5.1 创建配置文件
  • 5.2. 使用这个文件
  • 6. 使用连接池
  • 6.1 添加依赖
  • 6.2 修改连接数据库工具类
  • 7. ThreadLocal


所需要的所有依赖

<dependencies>
	 <!-- mysql依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    <!-- 连接池依赖 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    <!-- javabean类的依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <scope>provided</scope>
    </dependency>
    <!-- 添加日志的依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
     <!-- 测试依赖 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

1. 概述

在 Java 中,用于数据库存取的技术可以有以下几种:

  • JDBC直接访问数据库
  • JDO(Java Data Object)技术
  • 第三方ORM库,如 Hibernate、MyBatis

JDBC 是 Java 访问数据库基础。

1.1 什么是 JDBC

JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统,通用的 SQL数据库访问和存取的公共接口。定义了访问数据库的标准。

JDBC为访问不同的数据库提供了统一的途径和方法,为开发者屏蔽了一些细节问题。

对于数据库操作,实际上是数据库厂商自己提供 JDBC 的实现。

Java jacob依赖 jdbc依赖_java

1.2 JDBC的API

JDBC的API是一系列的接口,它统一和规范了应用程序与数据库连接、执行SQL的语句,并得到返回的结果。

Java jacob依赖 jdbc依赖_数据库_02

1.3 开发步骤

1.加载驱动器类,把数据库厂商操作的程序加载到内存中。

2.通过DriverManager类来创建 Connnection 对象

3.通过Connection 对象来创建 PreparedStatement 对象

4.如果有参数,则设置参数

5.执行 SQL 语句

6.如果执行有返回结果,那么就对结果进行处理

7.关闭资源

2. 获取数据库连接

2.1 创建Java工程

在 IDEA 中创建一个空的工程,我们后续的内容都是在这个空工程中创建子模块的方式来进行。

打开 IDEA 工具,然后点击 New Object 按钮。

Java jacob依赖 jdbc依赖_数据库_03

选择 Empty Project

Java jacob依赖 jdbc依赖_intellij-idea_04

然后输入工程名称和存放路径后,点击 Finish 完成空工程的创建。

2.2 创建 JDBC 模块

在刚创建的 工程中,点击 File -> new -> module 进入模块的创建界面。

Java jacob依赖 jdbc依赖_数据库_05

在这个界面中在边选择 Maven,在右边的 Module SDK 处选择你的 JDK 安装版本,然后点击 Next。

Java jacob依赖 jdbc依赖_Java jacob依赖_06

输入完上面的信息后,点击 Finish

Java jacob依赖 jdbc依赖_Java jacob依赖_07

2.3 加入依赖

我们要使用 JDBC 来连接 MySQL 数据库,那么就需要把 MySQL 数据库厂商提供的 JDBC 实现的 jar 文件添加到工程中来。也就是它的依赖添加到 pom.xml 文件中。

打开浏览器,输入 https://mvnrepository.com 进入中央仓库地址,然后输入 mysql 来进行查询。

Java jacob依赖 jdbc依赖_Java jacob依赖_08

然后点击 MySQL Connector/J

Java jacob依赖 jdbc依赖_Java jacob依赖_09

我在此时选择 5.1.49 然后点击它。

Java jacob依赖 jdbc依赖_java_10

然后复制上面的代码到项目的 pom.xml 文件中。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

2.4 编写程序来测试

在项目的 src 下的 main 下的 java 目录中创建包 com.xianopeng ,然后在这个包下创建 JdbcTest 类。

package com.xianopeng;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * @Description TODO
 * @Author Jock
 * @Date 2022/2/23
 */
public class JdbcTest {
    public static void main(String[] args) throws Exception {
        // 1. 加载驱动器类
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 通过 DriverManager 来创建 Connection 对象
        /**
         * String url:用于指定连接数据库的字符串,这个字符串的格式说明:
         * jdbc:mysql://192.168.72.71:3306/mydb
         * 1、jdbc:mysql://  它是代表协议,是 jdbc 的协议,jdbc:是一级协议,而 mysql: 是二级协议
         * 2、192.168.72.71  它是代表数据库的主机名,如果是本机则可写为 localhost
         * 3、3306  它是代表数据库端口号
         * 4、mydb 它代表的是要连接的数据库名称
         *
         * String user: 用于连接数据库时的用户名
         * String password:用于连接数据库时的密码
         */
        String url = "jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8";
        String user = "root";
        String password = "123456";
        // 2. 创建连接对象
        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);

        // 3. 创建执行SQL的对象
        Statement stm = conn.createStatement();
        // 4. 执行SQL
        ResultSet rs = stm.executeQuery("select * from tb_user");
        // 5. 处理数据
        System.out.println("id\t\tname\t");
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id + "\t\t" + name);
        }

        // 6. 关闭资源
        rs.close();
        stm.close();
        conn.close();
    }
}

2.5 Statement

Statement 对象是通过 Connection 对象的 createStatement() 方法来创建的。这个对象中有两个方法需要大家关注:

  • ResultSet executeQuery(String sql) throws SQLException; 这个方法是执行 SQL 查询的方法,执行后会返回查询的结果,并存入 ResultSet 对象中。
  • int executeUpdate(String sql) throws SQLException; 这是执行增、删、改的方法,执行后会返回成功操作后影响的行数。

这两个方法都需要传入一 SQL 语句来作为执行的参数。

2.6 ResultSet

ResultSet 对象是查询的结果集对象,它是 Statement 执行完查询数据的 SQL 后产生的一个对象,它里面有以下几个方法:

  • boolean next() throws SQLException; 这是用于判断是否有下一条数据
  • XX getXX(int columnIndex) throws SQLException; 从结果集对象中获取需要的数据,是通过列的索引下标来获取,这个下标是从 1 开始。
  • XXX getXXX(String columnLabel) throws SQLException; 从结果集对象中获取需要的数据,是通过列的名称来获取
  • void close() throws SQLException; 关闭结果集资源方法

2.7 PreparedStatement

PreparedStatement 是 Statement 的子类,它的一大好处是可以防止 SQL 注入。

Statement是在执行时才传入 SQL 语句,而 PreparedStatement 是在创建这个对象时就已经把 SQL 语句给它了。

如果 SQL 中需要条件,是通过 ? 号来进行占位,然后需要通过这个对象提供的一系列的 set 方法来设置这些参数,从而就避免了 SQL 注入。

3. 使用 JDBC来实现 CRUD

CRUD是增、删、改、查的简写。

C: create,就是增加数据,或叫插入数据

R:read,就是查询,也是 query

U:update,就是修改

D:delete, 就是删除

3.1 插入数据

我们还以表 tb_user 为例来进行演示

3.1.1 编写 User 实体类

package com.xianopeng.entity;

/**
 * @Description TODO
 * @Author Jock
 * @Date 2022/2/23
 */
public class User {
    private int id;
    private String name;
    private int age;

    public User() {
    }

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

上面实体类中大部分的代码都是封装的代码,我们可以使用 Lombok 来简化这部分代码的编写。

在 IDEA 2020 版本后就已经集成了 Lombok 插件,我们只需要在项目中添加 lombok 的依赖就可以在项目中使用了。

在项目的 pom.xml 文件中添加 lombok 的依赖。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <scope>provided</scope>
</dependency>

在项目中引入这个插件后,我们就可以把实体类简化为如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Accessors(chain = true) // 用于链式编程
public class User {
    private int id;
    private String name;
    private int age;
}

3.1.2 编写Dao 接口

UserDao.java

package com.xianopeng.entity;

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
}

3.1.3 编写Dao实现

UserDaoImpl.java

package com.xianopeng.entity;

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

public class UserDaoImpl implements UserDao {
    @Override
    public void insert(User user) throws Exception {
        // 1. 加载驱动器类
        Class.forName("com.mysql.jdbc.Driver");

        // 2. 创建 Connnection
        String url = "jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8";
        String username = "root";
        String password = "123456";
        Connection conn = DriverManager.getConnection(url, username, password);
        // 3. 创建 PreparedStatement 对象
        String sql = "INSERT INTO tb_user(name, age) VALUES(?, ?)";
        PreparedStatement pstm = conn.prepareStatement(sql);
        // 4. 设置参数
        pstm.setString(1, user.getName());
        pstm.setInt(2, user.getAge());
        // 5. 执行SQL
        int r = pstm.executeUpdate();
        // 输出
        System.out.println(r);
        // 6. 关闭资源,要返着关
        pstm.close();
        conn.close();
    }
}

3.1.4 编写测试

在 src 目录下的 test 目录中的 java 目录下,创建 com.xianopeng 包, 然后创建 UserDaoImplTest.java 类,代码如下:

package com.xianopeng;

import com.xianopeng.entity.User;
import com.xianopeng.entity.UserDao;
import com.xianopeng.entity.UserDaoImpl;
import org.junit.Test;

public class UserDaoImplTest {
    // 测试插入数据
    @Test
    public void testInsert() throws Exception {
        User user = new User()
                .setName("张飞")
                .setAge(19);

        UserDao userDao = new UserDaoImpl();
        userDao.insert(user);

    }
}

在上没的代码中,我们使用了 junit 来进行测试,因此我们需要把 junit 的依赖添加到 pom.xml 文件。

<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>

3.2 修改数据

3.2.1 修改接口

在 UserDao 接口中添加修改数据的方法。

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
    // 修改数据
    int update(User user) throws Exception;
}

3.2.2 修改实现

在 UserDaoImpl 类中添加 update 实现的方法,代码如下:

// 修改数据
    @Override
    public int update(User user) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        String sql = "UPDATE tb_user SET name=?,age=? WHERE id=?";
        PreparedStatement pstm = conn.prepareStatement(sql);
        pstm.setString(1, user.getName());
        pstm.setInt(2, user.getAge());
        pstm.setInt(3, user.getId());
        int r = pstm.executeUpdate();
        pstm.close();
        conn.close();
        return r;
    }

3.2.3 添加测试方法

编写测试修改数据的测试方式。

package com.xianopeng;

import com.xianopeng.entity.User;
import com.xianopeng.entity.UserDao;
import com.xianopeng.entity.UserDaoImpl;
import org.junit.Before;
import org.junit.Test;

/**
 * @Description TODO
 * @Author Jock
 * @Date 2022/2/23
 */
public class UserDaoImplTest {
    UserDao userDao;
    @Before
    public void init() {
        userDao = new UserDaoImpl();
    }

    // 测试修改
    @Test
    public void testUpdate() throws Exception {
        User user = new User()
                .setId(1)
                .setName("赵云")
                .setAge(21);
        userDao.update(user);
    }
    // 测试插入数据
    @Test
    public void testInsert() throws Exception {
        User user = new User()
                .setName("张飞")
                .setAge(19);

        //UserDao userDao = new UserDaoImpl();
        userDao.insert(user);

    }
}

3.3 删除数据

3.3.1 修改接口

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
    // 修改数据
    int update(User user) throws Exception;
    // 删除数据
    int delete(int id) throws Exception;
}

3.3.2 修改实现

// 删除数据
    @Override
    public int delete(int id) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        String sql = "DELETE FROM tb_user WHERE id=?";
        PreparedStatement pstm = conn.prepareStatement(sql);
        pstm.setInt(1, id);
        int r = pstm.executeUpdate();
        return r;
    }

3.3.3 编写测试方法

// 删除数据
    @Test
    public void testDelete() throws Exception {
        int id = 4;
        userDao.delete(id);
    }

3.4 封装通用方法

经过上面的插入数据、修改数据和删除数据的方法的实现,我们发现这三个方法的代码都基本相同,不同的地方在于:SQL语句不同,设置的参数个数不同。

3.4.1 封装方法

为了让方法具有通用性,我们对其进行封装,封装的代码如下:

/**
     * 封装的通用增删改方法
     * @param sql 需要操作的SQL语句
     * @param params 执行SQL语句时需要的参数
     */
    public void update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            // 1. 加载驱动器类
            Class.forName("com.mysql.jdbc.Driver");
            // 2. 创建连接对象
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            // 3. 创建执行SQL语句对象
            pstm = conn.prepareStatement(sql);
            // 4. 设置参数
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            // 5. 执行SQL语句
            pstm.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 释放资源
            if (pstm != null) {
                try {
                    pstm.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.4.2 修改Dao实现

UserDaoImpl.java

package com.xianopeng.entity;

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

/**
 * @Description TODO
 * @Author Jock
 * @Date 2022/2/23
 */
public class UserDaoImpl implements UserDao {
    private final String URL = "jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8";
    private final String USERNAME = "root";
    private final String PASSWORD = "123456";
    @Override
    public void insert(User user) throws Exception {
        update("INSERT INTO tb_user(name, age) VALUES(?, ?)", user.getName(), user.getAge());
    }

    // 修改数据
    @Override
    public int update(User user) throws Exception {
        return update("UPDATE tb_user SET name=?,age=? WHERE id=?",
                user.getName(),user.getAge(),user.getId());
    }

    // 删除数据
    @Override
    public int delete(int id) throws Exception {
        return update("DELETE FROM tb_user WHERE id=?", id);
    }

    /**
     * 封装的通用增删改方法
     * @param sql 需要操作的SQL语句
     * @param params 执行SQL语句时需要的参数
     */
    public int update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            // 1. 加载驱动器类
            Class.forName("com.mysql.jdbc.Driver");
            // 2. 创建连接对象
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            // 3. 创建执行SQL语句对象
            pstm = conn.prepareStatement(sql);
            // 4. 设置参数
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            // 5. 执行SQL语句
            return pstm.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            // 6. 释放资源
            if (pstm != null) {
                try {
                    pstm.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }


}

3.5 编写工具类

为了代码重用,我们可以编写一个连接数据库的工具类,这样的话,一方面它的职责更加的明确,另一方面它的代码重用性更好。

3.5.1 编写工具类

JdbcUtil01.java

package com.xianopeng.utils;

import lombok.extern.slf4j.Slf4j;

import java.sql.*;

/**
 * @Description 连接数据库的工具类
 * @Author Jock
 * @Date 2022/2/23
 */
@Slf4j
public final class JdbcUtil01 {
    // 定义连接数据库的几个参数
    private static final String URL = "jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "123456";

    private JdbcUtil01() {
    }

    // 加载驱动类,这个加载操作只在项目中执行一次即可,无须返回加载
    static {
        try {
            // 1. 加载驱动器类
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            log.info("加载驱动器类时出错,错误信息为:" + e.getMessage());
            throw new RuntimeException("加载驱动器类时出错,错误信息为:" + e.getMessage());
        }
    }

    /**
     * 封装的通用增删改方法
     * @param sql 需要操作的SQL语句
     * @param params 执行SQL语句时需要的参数
     */
    public static int update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            // 2. 创建连接对象
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            // 3. 创建执行SQL语句对象
            pstm = conn.prepareStatement(sql);
            // 4. 设置参数
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            // 5. 执行SQL语句
            return pstm.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            // 6. 释放资源
            destroy(conn, pstm, null);
        }
    }

    /**
     * 通用的释放资源的方法
     * @param conn 连接对象
     * @param pstm 执行语句对象
     * @param rs 结果集对象
     */
    public static void destroy(Connection conn, PreparedStatement pstm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("释放 ResultSet 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (pstm != null) {
            try {
                pstm.close();
            } catch (SQLException e) {
                log.info("释放 PreparedStatement 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                log.info("释放 Connection 对象时出错,错误信息为:" + e.getMessage());
            }
        }
    }

}

在这个工具类中,包括加载驱动器类,包含通知增删改方法,也包括释放资源的方法。

3.5.2 修改实现类

UserDaoImpl.java

package com.xianopeng.dao.impl;

import com.xianopeng.dao.UserDao;
import com.xianopeng.entity.User;
import com.xianopeng.utils.JdbcUtil01;

/**
 * @Description TODO
 * @Author Jock
 * @Date 2022/2/23
 */
public class UserDaoImpl implements UserDao {
    @Override
    public void insert(User user) throws Exception {
        JdbcUtil01.update("INSERT INTO tb_user(name, age) VALUES(?, ?)", user.getName(), user.getAge());
    }

    // 修改数据
    @Override
    public int update(User user) throws Exception {
        return JdbcUtil01.update("UPDATE tb_user SET name=?,age=? WHERE id=?",
                user.getName(),user.getAge(),user.getId());
    }

    // 删除数据
    @Override
    public int delete(int id) throws Exception {
        return JdbcUtil01.update("DELETE FROM tb_user WHERE id=?", id);
    }




}

4. 查询

4.1 查询多条数据

我们希望查询出 tb_user表中所有的数据

4.1.1 修改Dao接口

在 UserDao 接口类中添加如下的方法。

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
    // 修改数据
    int update(User user) throws Exception;
    // 删除数据
    int delete(int id) throws Exception;
    // 查询全部数据
    List<User> query();
}

4.1.2 修改Dao实现类

在 UserDaoImpl 类中实现查询全部数据的方法。

@Override
    public List<User> query() {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<User> users = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Properties info;
            conn = DriverManager.getConnection("jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8", "root", "123456");
            pstm = conn.prepareStatement("SELECT id, name, age FROM tb_user");
            rs = pstm.executeQuery();
            users = new ArrayList<>();
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                User user = new User()
                        .setId(id)
                        .setName(name)
                        .setAge(age);
                users.add(user);
            }
            return users;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil01.destroy(conn, pstm, rs);
        }
        return users;
    }

4.1.3 编写测试

@Test
    public void testQuery() {
        List<User> users = userDao.query();
        for (User user : users) {
            System.out.println(user);
        }
    }

4.2 分页查询

4.2.1 修改Dao接口

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
    // 修改数据
    int update(User user) throws Exception;
    // 删除数据
    int delete(int id) throws Exception;
    // 查询全部数据
    List<User> query();
    // 分页查询
    List<User> query(int offset, int pageSize);
}

4.2.2 修改Dao实现

/**
     * 分页查询
     * @param page 查询的页码
     * @param pageSize 每页显示记录数
     * @return 返回的结果
     */
    @Override
    public List<User> query(int page, int pageSize) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<User> users = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8", "root", "123456");
            pstm = conn.prepareStatement("SELECT id,name,age FROM tb_user LIMIT ?, ?");
            pstm.setInt(1, (page - 1) * pageSize);
            pstm.setInt(2, pageSize);
            rs = pstm.executeQuery();
            users = new ArrayList<>();
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                User user = new User()
                        .setId(id)
                        .setName(name)
                        .setAge(age);
                users.add(user);
            }
            return users;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil01.destroy(conn, pstm, rs);
        }
        return users;
    }

4.2.3 编写测试

// 分页查询
    @Test
    public void testQueryLimit() {
        List<User> users = userDao.query(2, 2);
        for (User user : users) {
            System.out.println(user);
        }
    }

4.3 单条数据查询

4.3.1 修改Dao接口

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
    // 修改数据
    int update(User user) throws Exception;
    // 删除数据
    int delete(int id) throws Exception;
    // 查询全部数据
    List<User> query();
    // 分页查询
    List<User> query(int page, int pageSize);
    // 查询单数据
    User select(int id);
}

4.3.2 修改Dao实现

/**
     * 查询单条数据
     * @param id 主键
     * @return 返回对象
     */
    @Override
    public User select(int id) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        User user = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8", "root", "123456");
            pstm = conn.prepareStatement("SELECT id,name,age FROM tb_user WHERE id=?");
            pstm.setInt(1, id);
            rs = pstm.executeQuery();
            if (rs.next()) {
                int _id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                
                user = new User()
                        .setId(id)
                        .setName(name)
                        .setAge(age);
            }
            return user;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil01.destroy(conn, pstm, rs);
        }
        return user;
    }

4.3.3 编写测试

// 单条数据查询
    @Test
    public void testSelect() {
        User user = userDao.select(2);
        System.out.println(user);
    }

4.4 查询总记录数

4.4.1 修改Dao接口

public interface UserDao {
    // 插入数据
    void insert(User user) throws Exception;
    // 修改数据
    int update(User user) throws Exception;
    // 删除数据
    int delete(int id) throws Exception;
    // 查询全部数据
    List<User> query();
    // 分页查询
    List<User> query(int page, int pageSize);
    // 查询单数据
    User select(int id);
    // 查询总记录数
    int count();
}

4.4.2 修改Dao实现

/**
     * 查询总记录数
     * @return
     */
    @Override
    public int count() {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        int count = 0;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8", "root", "123456");
            //pstm = conn.prepareStatement("SELECT count(*) as num FROM tb_user");
            pstm = conn.prepareStatement("SELECT count(*) FROM tb_user");
            rs = pstm.executeQuery();
            if (rs.next()) {
                //count = rs.getInt("num");
                count = rs.getInt(1);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil01.destroy(conn, pstm, rs);
        }
        return count;
    }

4.4.3 编写测试

// 查询总记录数
    @Test
    public void testCount() {
        int count = userDao.count();
        System.out.println(count);
    }

4.5 查询封装

4.5.1 封装方法

在 JdbcUtil01.java 类中封装查询多条数据的方法,代码如下:

/**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果集
     */
    public static <T> List<T> query(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<T> entities = null;

        try {
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            entities = new ArrayList<>();
            // 获取 ResultSet 对象来获取元数据信息
            ResultSetMetaData metaData = rs.getMetaData();
            // 获取SQL语名中字段的个数
            int columnCount = metaData.getColumnCount();
            // 处理结果集
            while (rs.next()) {
                // 通过反射来实例化对象
                //T entity = clazz.newInstance();
                T entity = clazz.getDeclaredConstructor().newInstance();

                for (int i = 1; i <= columnCount; i++) {
                    // getColumnName() 是获取表中的字段名称 SELECT id,name,age FROM xxx
                    //metaData.getColumnName();
                    // getColumnLabel() 是获取SQL中的字段名称 SELECT id,name as n,age FROM xxx
                    // 获取字段的名称
                    String columnLabel = metaData.getColumnLabel(i);
                    // 通过反射获取对象的字段
                    Field field = clazz.getDeclaredField(columnLabel);
                    // 给这个字段设置值
                    field.setAccessible(true);
                    field.set(entity, rs.getObject(columnLabel));
                }
                entities.add(entity);
            }
            return entities;
        } catch (Exception e) {
            throw new RuntimeException("查询多条数据时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

4.5.2 修改Dao实现类

// 查询全部
    @Override
    public List<User> query() {
        return JdbcUtil01.query(User.class, "SELECT * FROM tb_user");
    }
    /**
     * 分页查询
     * @param page 查询的页码
     * @param pageSize 每页显示记录数
     * @return 返回的结果
     */
    @Override
    public List<User> query(int page, int pageSize) {
        return JdbcUtil01.query(User.class, "SELECT * FROM tb_user LIMIT ?,?",(page - 1) * pageSize, pageSize);
    }
    /**
     * 查询单条数据
     * @param id 主键
     * @return 返回对象
     */
    @Override
    public User select(int id) {
        return JdbcUtil01.query(User.class, "SELECT id,name,age FROM tb_user WHERE id=?", id).get(0);
    }

4.5.2 封装查询统计的方法

/**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果
     */
    public static <T> T queryForObject(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        T entity = null;

        try {
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            while (rs.next()) {
                if (clazz == Integer.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(int.class);
                    entity = constructor.newInstance(rs.getInt(1));
                } else if (clazz == Long.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(long.class);
                    entity = constructor.newInstance(rs.getLong(1));
                } else {
                    throw new RuntimeException("参数中能传 int 类型或 long 类型。");
                }
            }

            return entity;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("统计查询时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

4.5.4 修改Dao实现类

/**
     * 查询总记录数
     * @return
     */
    @Override
    public int count() {
        return JdbcUtil01.queryForObject(Integer.class, "SELECT count(*) FROM tb_user");
    }

5. 调整工具类

在工具类中,我们把连接数据库的信息以硬编码的方式书写在了 Java 类中,这种方式是不推荐,在实际开发的项目中,我们通过都是把连接数据库的信息写在一个单独的文件中,例如 db.properties 文件中。

5.1 创建配置文件

在项目的 src 下的 main 下的 resources 目录中创建 db.properties 文件,内容如下:

jdbc.url=jdbc:mysql://192.168.72.71:3306/mydb?useSSL=false&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
jdbc.driver=com.mysql.jdbc.Driver

5.2. 使用这个文件

在连接数据库的工具类中(JdbcUtil.java)类中我们去使用这个配置文件。来读取文件中的内容。

package com.xianopeng.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @Description 连接数据库的工具类
 * @Author Jock
 * @Date 2022/2/23
 */
@Slf4j
public final class JdbcUtil {
    // 定义连接数据库的几个参数
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    private JdbcUtil() {
    }

    // 加载驱动类,这个加载操作只在项目中执行一次即可,无须返回加载
    static {
        try {
            // 读取db.properties配置文件
            InputStream is = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(is);
            DRIVER = prop.getProperty("jdbc.driver");
            URL = prop.getProperty("jdbc.url");
            USERNAME = prop.getProperty("jdbc.username");
            PASSWORD = prop.getProperty("jdbc.password");
        } catch (IOException e) {
            log.info("加载配置文件出错,错误信息为:" + e.getMessage());
            throw new RuntimeException("加载配置文件出错,错误信息为:" + e.getMessage());
        }

        try {
            // 1. 加载驱动器类
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            log.info("加载驱动器类时出错,错误信息为:" + e.getMessage());
            throw new RuntimeException("加载驱动器类时出错,错误信息为:" + e.getMessage());
        }
    }

    public static Connection getConnection() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            return conn;
        } catch (SQLException e) {
            throw new RuntimeException("创建连接对象时出错,错误信息为:" + e.getMessage());
        }
    }

    /**
     * 封装的通用增删改方法
     * @param sql 需要操作的SQL语句
     * @param params 执行SQL语句时需要的参数
     */
    public static int update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            // 2. 创建连接对象
            conn = getConnection();
            // 3. 创建执行SQL语句对象
            pstm = conn.prepareStatement(sql);
            // 4. 设置参数
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            // 5. 执行SQL语句
            return pstm.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            // 6. 释放资源
            destroy(conn, pstm, null);
        }
    }

    /**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果集
     */
    public static <T> List<T> query(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<T> entities = null;

        try {
            conn = getConnection();
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            entities = new ArrayList<>();
            // 获取 ResultSet 对象来获取元数据信息
            ResultSetMetaData metaData = rs.getMetaData();
            // 获取SQL语名中字段的个数
            int columnCount = metaData.getColumnCount();
            // 处理结果集
            while (rs.next()) {
                // 通过反射来实例化对象
                //T entity = clazz.newInstance();
                T entity = clazz.getDeclaredConstructor().newInstance();

                for (int i = 1; i <= columnCount; i++) {
                    // getColumnName() 是获取表中的字段名称 SELECT id,name,age FROM xxx
                    //metaData.getColumnName();
                    // getColumnLabel() 是获取SQL中的字段名称 SELECT id,name as n,age FROM xxx
                    // 获取字段的名称
                    String columnLabel = metaData.getColumnLabel(i);
                    // 通过反射获取对象的字段
                    Field field = clazz.getDeclaredField(columnLabel);
                    // 给这个字段设置值
                    field.setAccessible(true);
                    field.set(entity, rs.getObject(columnLabel));
                }
                entities.add(entity);
            }
            return entities;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("查询多条数据时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

    /**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果
     */
    public static <T> T queryForObject(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        T entity = null;

        try {
            conn = getConnection();
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            while (rs.next()) {
                if (clazz == Integer.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(int.class);
                    entity = constructor.newInstance(rs.getInt(1));
                } else if (clazz == Long.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(long.class);
                    entity = constructor.newInstance(rs.getLong(1));
                } else {
                    throw new RuntimeException("参数中能传 int 类型或 long 类型。");
                }
            }

            return entity;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("统计查询时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

    /**
     * 通用的释放资源的方法
     * @param conn 连接对象
     * @param pstm 执行语句对象
     * @param rs 结果集对象
     */
    public static void destroy(Connection conn, PreparedStatement pstm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("释放 ResultSet 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (pstm != null) {
            try {
                pstm.close();
            } catch (SQLException e) {
                log.info("释放 PreparedStatement 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                log.info("释放 Connection 对象时出错,错误信息为:" + e.getMessage());
            }
        }
    }

}

6. 使用连接池

在 Java 中常用的连接池有 C3P0、DBCP、Druid 等。它们都免费开源的产品。

我们在开发中,使用最多的就是 Druid ,因为它是阿里开源的产品。

好处:

  • 它的帮助文档是中文,便于查看
  • 它是经过阿里大量数据的使用后的产品,必能好

6.1 添加依赖

把 druid 依赖添加到项目的 pom.xml 文件中

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

6.2 修改连接数据库工具类

package com.xianopeng.utils;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @Description 连接数据库的工具类
 * @Author Jock
 * @Date 2022/2/23
 */
@Slf4j
public final class JdbcUtil {
    // 定义连接数据库的几个参数
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    private JdbcUtil() {
    }

    // 加载驱动类,这个加载操作只在项目中执行一次即可,无须返回加载
    static {
        try {
            // 读取db.properties配置文件
            InputStream is = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(is);
            DRIVER = prop.getProperty("jdbc.driver");
            URL = prop.getProperty("jdbc.url");
            USERNAME = prop.getProperty("jdbc.username");
            PASSWORD = prop.getProperty("jdbc.password");
        } catch (IOException e) {
            log.info("加载配置文件出错,错误信息为:" + e.getMessage());
            throw new RuntimeException("加载配置文件出错,错误信息为:" + e.getMessage());
        }
    }

    /**
     * 获取数据源
     * @return
     */
    public static DataSource getDataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(DRIVER);
        ds.setUrl(URL);
        ds.setUsername(USERNAME);
        ds.setPassword(PASSWORD);
        return ds;
    }

    public static Connection getConnection() {
        try {
            return getDataSource().getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("创建连接对象时出错,错误信息为:" + e.getMessage());
        }
    }

    /**
     * 封装的通用增删改方法
     * @param sql 需要操作的SQL语句
     * @param params 执行SQL语句时需要的参数
     */
    public static int update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            // 2. 创建连接对象
            conn = getConnection();
            // 3. 创建执行SQL语句对象
            pstm = conn.prepareStatement(sql);
            // 4. 设置参数
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            // 5. 执行SQL语句
            return pstm.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            // 6. 释放资源
            destroy(conn, pstm, null);
        }
    }

    /**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果集
     */
    public static <T> List<T> query(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<T> entities = null;

        try {
            conn = getConnection();
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            entities = new ArrayList<>();
            // 获取 ResultSet 对象来获取元数据信息
            ResultSetMetaData metaData = rs.getMetaData();
            // 获取SQL语名中字段的个数
            int columnCount = metaData.getColumnCount();
            // 处理结果集
            while (rs.next()) {
                // 通过反射来实例化对象
                //T entity = clazz.newInstance();
                T entity = clazz.getDeclaredConstructor().newInstance();

                for (int i = 1; i <= columnCount; i++) {
                    // getColumnName() 是获取表中的字段名称 SELECT id,name,age FROM xxx
                    //metaData.getColumnName();
                    // getColumnLabel() 是获取SQL中的字段名称 SELECT id,name as n,age FROM xxx
                    // 获取字段的名称
                    String columnLabel = metaData.getColumnLabel(i);
                    // 通过反射获取对象的字段
                    Field field = clazz.getDeclaredField(columnLabel);
                    // 给这个字段设置值
                    field.setAccessible(true);
                    field.set(entity, rs.getObject(columnLabel));
                }
                entities.add(entity);
            }
            return entities;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("查询多条数据时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

    /**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果
     */
    public static <T> T queryForObject(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        T entity = null;

        try {
            conn = getConnection();
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            while (rs.next()) {
                if (clazz == Integer.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(int.class);
                    entity = constructor.newInstance(rs.getInt(1));
                } else if (clazz == Long.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(long.class);
                    entity = constructor.newInstance(rs.getLong(1));
                } else {
                    throw new RuntimeException("参数中能传 int 类型或 long 类型。");
                }
            }

            return entity;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("统计查询时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

    /**
     * 通用的释放资源的方法
     * @param conn 连接对象
     * @param pstm 执行语句对象
     * @param rs 结果集对象
     */
    public static void destroy(Connection conn, PreparedStatement pstm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("释放 ResultSet 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (pstm != null) {
            try {
                pstm.close();
            } catch (SQLException e) {
                log.info("释放 PreparedStatement 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                log.info("释放 Connection 对象时出错,错误信息为:" + e.getMessage());
            }
        }
    }

}

在这个类中,我们增加了 getDataSource() 方法用于创建连接池对象。然后在 geConection() 方法中从连接池中获取连接对象。而静态代码块中的 加载驱动器类的代码就不再需要,因为这部分的功能已经放到的连接池对象中了。

其它地方的代码无须做任何的修改即可使用。

7. ThreadLocal

在 JDK1.2 的版本中已经提供了 ThreadLocal对象了,它是为了解决多线程程序并发问题而产生。使用这个类非常的方便。

这个类提供了以下几个静态方法:

  • get() :用于获取ThreadLocal 中当前线程共享变量的值。
  • set():设置ThreadLocal中当前线程共享的变量的值。
  • remove():移除 ThreadLocal中当前线程共享的变量的值。
  • initialValue:ThreadLocal没有被当前线程赋值时调用当前线程的remove方法后再调用 get 方法时就会返回这个值。

我们来修改 JdbcUtil 这个工具类,让它支持 ThreadLocal

package com.xianopeng.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @Description 连接数据库的工具类
 * @Author Jock
 * @Date 2022/2/23
 */
@Slf4j
public final class JdbcUtil {
    // 数据源连接池对象
    private static DataSource dataSource;
    // ThreadLocal 对象
    private static ThreadLocal<Connection> threadLocal;

    private JdbcUtil() {
    }

    // 加载驱动类,这个加载操作只在项目中执行一次即可,无须返回加载
    static {
        try {
            // 读取db.properties配置文件
            InputStream is = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(is);

            // 使用Druid连接池工厂方式
            dataSource = DruidDataSourceFactory.createDataSource(prop);
            threadLocal = new ThreadLocal<>();
        } catch (Exception e) {
            log.info("加载配置文件出错,错误信息为:" + e.getMessage());
            throw new RuntimeException("加载配置文件出错,错误信息为:" + e.getMessage());
        }
    }

    /**
     * 获取连接对象
     * @return 返回连接对象
     */
    public static Connection getConnection() {
        // 从当前线程中获取连接
        Connection conn = threadLocal.get();
        if (conn == null) {
            try {
                // 如果没有就从连接池中去获取
                conn = dataSource.getConnection();
                // 放到ThreadLocal中
                threadLocal.set(conn);
            } catch (SQLException e) {
                throw new RuntimeException("创建连接对象时出错,错误信息为:" + e.getMessage());
            }
        }
        return conn;
    }

    /**
     * 封装的通用增删改方法
     * @param sql 需要操作的SQL语句
     * @param params 执行SQL语句时需要的参数
     */
    public static int update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            // 2. 创建连接对象
            conn = getConnection();
            // 3. 创建执行SQL语句对象
            pstm = conn.prepareStatement(sql);
            // 4. 设置参数
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            // 5. 执行SQL语句
            return pstm.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            // 6. 释放资源
            destroy(conn, pstm, null);
        }
    }

    /**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果集
     */
    public static <T> List<T> query(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<T> entities = null;

        try {
            conn = getConnection();
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            entities = new ArrayList<>();
            // 获取 ResultSet 对象来获取元数据信息
            ResultSetMetaData metaData = rs.getMetaData();
            // 获取SQL语名中字段的个数
            int columnCount = metaData.getColumnCount();
            // 处理结果集
            while (rs.next()) {
                // 通过反射来实例化对象
                //T entity = clazz.newInstance();
                T entity = clazz.getDeclaredConstructor().newInstance();

                for (int i = 1; i <= columnCount; i++) {
                    // getColumnName() 是获取表中的字段名称 SELECT id,name,age FROM xxx
                    //metaData.getColumnName();
                    // getColumnLabel() 是获取SQL中的字段名称 SELECT id,name as n,age FROM xxx
                    // 获取字段的名称
                    String columnLabel = metaData.getColumnLabel(i);
                    // 通过反射获取对象的字段
                    Field field = clazz.getDeclaredField(columnLabel);
                    // 给这个字段设置值
                    field.setAccessible(true);
                    field.set(entity, rs.getObject(columnLabel));
                }
                entities.add(entity);
            }
            return entities;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("查询多条数据时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

    /**
     * 查询多条数据的封装方法
     * @param clazz 结果的封装对象
     * @param sql 要执行的SQL语句
     * @param params 执行SQL语句时需要的参数
     * @param <T> 消费金融泛型类型
     * @return 返回查询的结果
     */
    public static <T> T queryForObject(Class<T> clazz, String sql, Object...params) {
        Connection conn = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        T entity = null;

        try {
            conn = getConnection();
            pstm = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                pstm.setObject(i + 1, params[i]);
            }
            rs = pstm.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            while (rs.next()) {
                if (clazz == Integer.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(int.class);
                    entity = constructor.newInstance(rs.getInt(1));
                } else if (clazz == Long.class) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor(long.class);
                    entity = constructor.newInstance(rs.getLong(1));
                } else {
                    throw new RuntimeException("参数中能传 int 类型或 long 类型。");
                }
            }

            return entity;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("统计查询时出错,错误信息为:" + e.getMessage());
        } finally {
            destroy(conn, pstm, rs);
        }
    }

    /**
     * 通用的释放资源的方法
     * @param conn 连接对象
     * @param pstm 执行语句对象
     * @param rs 结果集对象
     */
    public static void destroy(Connection conn, PreparedStatement pstm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("释放 ResultSet 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (pstm != null) {
            try {
                pstm.close();
            } catch (SQLException e) {
                log.info("释放 PreparedStatement 对象时出错,错误信息为:" + e.getMessage());
            }
        }
        if (conn != null) {
            try {
                conn.close();
                // 从 ThreadLocal中删除当前连接对象
                threadLocal.remove();
           } catch (SQLException e) {
                log.info("释放 Connection 对象时出错,错误信息为:" + e.getMessage());
            }
        }
    }

}