Java JDBC 详解、使用、连接池

发布时间 2023-12-26 18:05:06作者: 进击的davis

JDBC介绍

Java数据库连接,JDBC(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。

简单说,jdbc 是Java语言为了屏蔽具体的具体的数据库操作的细节不同提供的一个框架。

在关系型数据库的处理中,大致流程都一样:

  • 连接数据库
  • 执行语句
  • 返回数据
  • 处理数据

所以,为了统一,Java官方就增加了对流程做了规范,增加了一步加载驱动,也就是加载不同的实现。

下图是 JDBC 作用图示[1]:

image.png

JDBC使用

JDBC 的使用比较简单,简单总结还是数据的大致流程。

这里看看 JDBC 连接数据的细致流程:

  • 读取配置
  • 加载驱动
  • 连接数据库

这里以 MySQL 为例:

// 1.读取配置
Properties properties = new Properties();
try {
    InputStream in = Util.class.getClassLoader().getResourceAsStream("app.properties");
    properties.load(in);
} catch (IOException e) {
    e.printStackTrace();
    System.out.println("未找到配置文件");
}

String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 2,加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 3.连接数据库
conn = DriverManager.getConnection(url, username, password);

数据操作

重要类、接口、方法

  • Statement

接口,主要声明执行语句和结果获取,在我们进行 DDL 操作的时候,可以通过 Connection.createStatement() 获取再操作。

  • preparestatement

是 Connection 类 的方法,通过调用此方法,传入提前定义好的 sql 语句,返回 PrepareStatement 接口的对象,同样也可以去调用 executeQuery/executeUpdate 去执行静态语句。

  • callablestatement

继承自PreparedStatement接口,用于调用存储过程。

  • boolean execute()
    在 PrepareStatement 语句中使用,效果等同于 executeQuery/executeUpdate,但有一点需要注意,如是是查询语句,执行后要返回 true/false,如果要获取查询的结果,应该再次调用 Statement.getResultSet,如果是 update 类的语句,则可以调用 Statement.getUpdateCount。

  • ResultSet executeQuery()

执行了 SQL DQL 查询语句后,返回查询的结果集。

  • int executeUpdate

执行 DML 后,返回整型值,可以是影响行数,或者0,主要针对 INSERT、UPDATE、DELETE 语句。

  • void addBatch()

在如执行批量插入操作时,将一组参数加入到 PrepareStatement 中。

  • int[] executeBatch()

提交批量操作的命令,正式执行批量操作。

  • setSomeType()

主要针对 PrepareStatement 对象,对占位符的参数进行设置,占位符的序号+字段值。

结果集-ResultSet

  • getSomeType()

类似于 ResultSet.getString(),传入参数主要2种,一种通过字段名,一种通过字段在表中的序号。

  • Boolean next()

通过游标的移动判断是否还有数据,如返回数据后,通过 next() 方法判断是否可以继续获取数据。

使用步骤

基本步骤:

  • 加载JDBC驱动程序
  • 建立数据库连接Connection
  • 创建执行SQL的语句Statement
  • 处理执行结果ResultSet/int

这里我们通过一个简短的示例看看 JDBC 的使用。

环境:

  • JDK:17
  • MySQL:5.7

新建一个 maven 项目,添加如下依赖:

<dependencies>
    <!--  mysql driver      -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.1.0</version>
    </dependency>

    <!--  druid连接池      -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.15</version>
    </dependency>

    <!-- junit     -->
    <!-- 基础测试 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.0</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.10.0</version>
    </dependency>

</dependencies>

在 resource 目录新建个配置文件,app.properties:

url=jdbc:mysql://{ip}:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username=root
password=123456

initialSize=5
maxActive=10
minIdle=5

这里新建个 工具类,主要功能就是获取连接和关闭连接:

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

public class Util {

    public static Connection getConn() {
        Connection conn = null;
        try {
            // 1.读取配置
            Properties properties = new Properties();
            try {
                InputStream in = Util.class.getClassLoader().getResourceAsStream("app.properties");
                properties.load(in);
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("未找到配置文件");
            }

            String url = properties.getProperty("url");
            String username = properties.getProperty("username");
            String password = properties.getProperty("password");
            // 2,加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 3.连接数据库
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("驱动类找不到");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("数据库连接失败");
        }

        return conn;
    }

    public static void close(Connection conn, Statement stm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (stm != null) {
            try {
                stm.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

我们操作的表结构:

image.png

接下耒,就可以测试 JDBC的CRUD了。

增-插入操作(insert)

这里主要演示单条插入和批量插入数据:

import org.junit.jupiter.api.Test;

import java.sql.*;
import java.util.Random;

public class TestSqlInsert {
    @Test
    public  void insert() throws SQLException {
        Connection conn = null;
        // 1.获取conn
        conn = Util.getConn();
        // 2.sql 语句,占位符
        String sql = "INSERT INTO example (`name`, `age`) VALUES (?, ?)";
        // 3.获取sql语句对象
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        // 4.填入字段内容
        // 第一个占位符字段,string类型
        preparedStatement.setString(1, "Gabriel");
        // 第二个占位符字段,int类型
        preparedStatement.setInt(2, 7);
        // 5.执行sql
        preparedStatement.executeUpdate();

        Util.close(conn, preparedStatement, null);
    }

    @Test
    public void insertBatch() throws SQLException {
        Connection conn = null;
        conn = Util.getConn();
        // 关闭自动提交
        conn.setAutoCommit(false);

        String sql = "INSERT INTO example (name, age) VALUES (?, ?)";
        PreparedStatement statement = conn.prepareStatement(sql);

        for (int i = 0; i < 100; i++) {
            statement.setString(1, "Galaxy" + i);
            statement.setInt(2, new Random().nextInt(18, 65));

            statement.addBatch();
            if (i % 10 == 0) {
                statement.executeBatch();
            }
        }
        statement.executeBatch();
        conn.commit();

        statement.close();
        conn.close();
    }
}

删-按条件删除记录行(delete)

import org.junit.jupiter.api.Test;

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

public class TestSqlDelete {
    @Test
    public void delete() throws SQLException {
        Connection conn = null;
        conn = Util.getConn();

        String sql = "DELETE FROM example WHERE age>?";

        PreparedStatement preparedStatement = conn.prepareStatement(sql);

        preparedStatement.setInt(1, 60);

        preparedStatement.executeUpdate();

        Util.close(conn, preparedStatement, null);
    }
}

改-更新数据(update)

按照条件更新记录行:

import org.junit.jupiter.api.Test;

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

public class TestSqlUpdate {

    @Test
    public void update() throws SQLException {
        Connection conn = null;
        // 1.获取conn
        conn = Util.getConn();
        // 2.sql语句
        String sql = "UPDATE example SET age=? WHERE id=?";
        // 3.获取prepareStatement
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        // 4.根据占位符填入值
        preparedStatement.setInt(1, 66);
        preparedStatement.setInt(2, 2);
        // 5.执行语句
        int result = preparedStatement.executeUpdate();
        System.out.println("影响行数:" + result);

        Util.close(conn, preparedStatement, null);
    }
}

查-查询数据(select)

由于只是示例,这里简单获取所有数据,或者按照条件查询数据:

import org.junit.jupiter.api.Test;

import java.sql.*;

public class TestSqlQuery {
    @Test
    public void queryAll() throws SQLException {
        Connection conn = null;
        // 1.获取连接
        conn = Util.getConn();
        // 2.sql语句
        String sql = "SELECT * FROM example";
        // 3.获取对象
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        // 4.执行语句
        ResultSet rs = preparedStatement.executeQuery();
        // 5.处理结果,多条结果用while,单条用if
        while (rs.next()) {
            // 通过字段名获取对象值
            int id = rs.getInt("id");
            // 通过字段列序号获取值
            String name = rs.getString(2);
            int age = rs.getInt("age");
            Date createTime = rs.getDate("create_time");
            System.out.println(String.format("id: %d, name: %s, age: %d, create_time: %s", id, name, age, createTime.toString()));
        }

        Util.close(conn, preparedStatement, rs);
    }

    @Test
    public void queryByName() throws SQLException{
        Connection conn = null;
        // 1.获取连接
        conn = Util.getConn();
        // 2.sql语句
        String sql = "SELECT * FROM example WHERE name=?";
        // 3.获取对象
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setString(1,"Greg");
        // 4.执行语句
        ResultSet rs = preparedStatement.executeQuery();
        // 5.处理结果,多条结果用while,单条用if
        if (rs.next()) {
            // 通过字段名获取对象值
            int id = rs.getInt("id");
            // 通过字段列序号获取值
            String name = rs.getString(2);
            int age = rs.getInt("age");
            Date createTime = rs.getDate("create_time");
            System.out.println(String.format("id: %d, name: %s, age: %d, create_time: %s", id, name, age, createTime.toString()));
        }

        Util.close(conn, preparedStatement, rs);
    }
}

事务处理

事务处理也比较简单,先开启事务,关闭自动提交,然后执行语句,提交,执行有问题则回滚事务:

import org.junit.jupiter.api.Test;

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

public class TestTransaction {

    @Test
    public void test() throws SQLException {
        Connection conn = Util.getConn();
        PreparedStatement smt = null;
        try {
            // 1.开启事务
            conn.setAutoCommit(false);

            String sql = "UPDATE example SET age=? WHERE name=?";
            smt = conn.prepareStatement(sql);

            smt.setInt(1, 55);
            smt.setString(2, "Greg");
            smt.execute();
            // 2.提交事务
            conn.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            // 3.回滚事务
            conn.rollback();
        }

        smt.close();
        conn.close();
    }
}

数据定义操作-DML

常用的如建库或者建表,或者设置索引等。

下面演示一个建表的操作:

import org.junit.jupiter.api.Test;

import java.sql.*;

public class TestDDL {
    @Test
    public void testDDL() throws SQLException {
        Connection conn = Util.getConn();

        Statement statement = conn.createStatement();

        // create table
        String tableSql = "CREATE TABLE test (id int auto_increment primary key, name varchar(20), age int)";

        // exec
        int res = statement.executeUpdate(tableSql);
        System.out.println("建表成功:" + res);
    }
}

连接池

如果是频繁的数据库操作,还是建议通过数据库连接池来代理,通过连接池,我们可以:

  • 不用频繁创建和销毁资源
  • 更加快速的获取连接

连接池选择很多,这里我们主要通过阿里巴巴的 Druid 为例。

在 连接池 的示例中,我们主要实现创建连接池和连接池的简单应用。

连接池工具类:

import com.alibaba.druid.pool.DruidDataSource;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class PoolUtil {

    public static Connection getPoolConn() {
        Connection conn = null;
        // 读取配置
        try {
            Properties properties = new Properties();
            try {
                InputStream in = PoolUtil.class.getClassLoader().getResourceAsStream("app.properties");
                properties.load(in);
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("未找到连接池的配置文件");
            }
            // 创建连接池源
            DruidDataSource dataSource = new DruidDataSource();
            // 设置数据库连接信息
            dataSource.setUrl(properties.getProperty("url"));
            dataSource.setUsername(properties.getProperty("username"));
            dataSource.setPassword(properties.getProperty("password"));
            // 设置连接池信息
            dataSource.setInitialSize((int)Integer.parseInt((String)properties.get("initialSize")));
            dataSource.setMaxActive((int)Integer.parseInt((String)properties.get("maxActive")));
            dataSource.setMinIdle((int)Integer.parseInt((String)properties.get("minIdle")));

            conn =  dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static void close(Connection conn, Statement stm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (stm != null) {
            try {
                stm.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

连接池的应用测试

import org.junit.jupiter.api.Test;

import java.sql.*;

public class TestPool {

    @Test
    public void test() {
        System.out.println("Preparing test>>>");
        Connection conn = null;
        PreparedStatement preparedStatement = null;
        ResultSet rs = null;

        try {
            conn = PoolUtil.getPoolConn();
            // create sql
            String sql = "SELECT * FROM example";
            preparedStatement = conn.prepareStatement(sql);
            rs = preparedStatement.executeQuery();

            while (rs.next()) {
                int id = rs.getInt(1);
                String name = rs.getString(2);
                int age = rs.getInt(3);
                Date createTime = rs.getDate(4);
                System.out.println(String.format("id: %d, name: %s, age: %d, create_time: %s", id, name, age, createTime.toString()));
            }

        } catch (SQLException e) {
            System.out.println("数据库操作有问题");
            e.printStackTrace();
        } finally {
            PoolUtil.close(conn, preparedStatement, rs);
        }
    }
}

通过上面的示例,我们可以发现,其实连接池的使用上和直连差别不大,但还是建议使用 连接池 访问数据库。

总结

上面的学习中,我们了解了 JDBC 是 Java 中用于数据库连接的接口,我们通过 JDBC 简单实现了对数据库的增删改查,最后学习了连接池的创建与使用,实际的项目应用中,只要项目代码稍微复杂点,我们更多的会选择 ORM框架 辅助编码实现。

参考: