专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

JDBC基础总结

概述

JDBC是Java数据库编程的核心基础。是Java提供的一个核心类库。它通过提供一套接口规范,确保Java通过JDBC,正确地访问数据库。本文在介绍JDBC基础的同时,还将引入数据库连接池相关的概念,以及关于DBCP连接池的使用。(点此获取示例代码)

核心类和接口

  • Driver 接口,定义了各个驱动程序都必须要实现的功能,是驱动程序的抽象。
  • DriverManager 是Driver的管理类。通过Class.forname(DriverName)的方式,就可以注册一个驱动程序。
  • Connection 通过DriverManager.getCoonection(DB_URL,USER,PASS)的方式,获取建立到数据库的物理链接。
  • Statement sql的容器。可以进行数据的增删查改。
  • ResultSet sql查询的结果。内部有指针,默认指向第一行记录。

JDBC URL

JDBC URL是后端数据库的唯一标识符。它是由“协议:子协议://子名称”这样格式的字符串组成的。而子名称中又包含了主机、端口和数据库,如下:

69_1.png

构建步骤

1、 装载驱动程序
2、 建立数据库连接
3、 执行sql语句
4、 获取执行结果
5、 清理环境

案例演示

在开始案例演示之前,我们先建立一个表,内容如下图:

69_2.png接着,我们在pom文件中引入mysql的驱动依赖,只有引入了驱动依赖,才能连接mysql数据库。

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

代码如下:

public class HelloJDBC {
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";

    public static void databaseOperation() {

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            // 1. 装载驱动程序
            Class.forName(JDBC_DRIVER);

            // 2. 建立数据库连接
            connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);

            // 3. 执行sql语句
            statement = connection.createStatement();
            resultSet = statement.executeQuery("select name from user");

            // 4. 获取执行结果
            while (resultSet.next()) {
                System.out.println("Hello " + resultSet.getString("name"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 清理环境
            try {
                if (connection != null) {
                    connection.close();
                }
                if (statement != null) {
                    statement.close();
                }
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    public static void main(String[] args) {
        databaseOperation();
    }

}

运行结果如下:

69_3.png

JDBC进阶

通过游标按批次读取数据

我们通常有这种应用场景,比如我们想要读取数据库表中的所有记录,对所有记录进行分析统计。但是如果一次性读取全部数据,在企业开发过程中,数据量可能达到千万级,如果全部读入到内存中,很容易内存溢出。因此我们可以每次读取一部分数据进行处理,处理完之后再读取下一部分的数据。JDBC提供对游标的支持,支持批次读取数据。

JDBC使用游标的方式

  • DB_URL后缀增加“?useCursorFetch=true”,开启对游标的支持。
  • 使用PreparedStatement接口替换Statement接口,通过PreparedStatement对象.setFetchSize()方法,设置每次查询的数量。

代码修改如下:

public class HelloJDBC {
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    //useCursorFetch=true表示使用游标
    private static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study?useCursorFetch=true";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";

    public static void databaseOperation() {

        Connection connection = null;
        //Statement statement = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 1. 装载驱动程序
            Class.forName(JDBC_DRIVER);

            // 2. 建立数据库连接
            connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);

            // 3. 执行sql语句
            //statement = connection.createStatement();
            //resultSet = statement.executeQuery("select name from user");
            preparedStatement = connection.prepareStatement("select name from user");
            preparedStatement.setFetchSize(1);
            resultSet = preparedStatement.executeQuery();

            // 4. 获取执行结果
            while (resultSet.next()) {
                System.out.println("Hello " + resultSet.getString("name"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 清理环境
            try {
                if (connection != null) {
                    connection.close();
                }
//                if (statement != null) {
//                    statement.close();
//                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    public static void main(String[] args) {
        databaseOperation();
    }

}

JDBC使用流方式读取大字段数据

实际开发过程中,数据库可能需要存储大字段,比如存储博客数据。但是如果直接从数据库中取出大字段数据,会占据太多的内存,太大的话,同样也会造成内存溢出。因此,我们可以使用流方式来读取大字段数据。

JDBC流方式读取大字段数据的思路

将大字段的数据,按照二进制的方式,按照区间加以划分,划分为多个区间。每次读取一个区间的内容,处理完毕之后,再处理下一个区间的内容,直到所有去区间的数据都处理完毕为止。 在这里我们先定义一个表:

69_4.png其中info字段存储的是当前博客的一些数据。 Java代码如下:

public class StreamJDBC {
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    private static final String FILE_PREFIX = "D:\\info";
    private static final String FILE_SUFFIX = ".txt";

    public static void databaseOperation() {

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        InputStream in = null;
        OutputStream out = null;

        try {
            // 1. 装载驱动程序
            Class.forName(JDBC_DRIVER);

            // 2. 建立数据库连接
            connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);

            // 3. 执行sql语句
            preparedStatement = connection.prepareStatement("select info from info_message");
            resultSet = preparedStatement.executeQuery();

            int i = 1;
            // 4. 获取执行结果
            while (resultSet.next()) {

                // 5.获取流对象
                in = resultSet.getBinaryStream("info");

                // 6.将对象流写入文件
                File f = new File(FILE_PREFIX + i + FILE_SUFFIX);
                out = new FileOutputStream(f);
                int temp = 0;

                //边读编写
                while ((temp = in.read()) != -1) {
                    out.write(temp);
                }
                i++;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 清理环境
            closeResource(connection, preparedStatement, resultSet,in,out);
        }

    }

    public static void closeResource(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet,
                              InputStream in, OutputStream out) {
        try {
            if (out != null) {
                out.close();
            }
            if (in != null) {
                in.close();
            }
            if (connection != null) {
                connection.close();
            }
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (resultSet != null) {
                resultSet.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        databaseOperation();
    }

}

JDBC批处理插入数据

批量插入数据能够减少连接的个数,从而减少系统开销,提高效率。

  • Statement (preparedStatement继承了Statement,所以也自动拥有了下面的方法)
    • addBatch() 将多条sql组成一个处理单元
    • executeBatch() 进行批处理操作
    • clearBatch() 清空sql语句,准备下次执行

接着我们批量插入三个用户,代码如下:

public class BatchJDBC {
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";

    public static void insertUser(List<String> userNames) {

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            // 1. 装载驱动程序
            Class.forName(JDBC_DRIVER);

            // 2. 建立数据库连接
            connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);

            // 3. 执行sql语句
            statement = connection.createStatement();
            for (String userName : userNames) {
                statement.addBatch("insert into user(name) values('"+userName+"')");
            }
            statement.executeBatch();
            statement.clearBatch();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 清理环境
            StreamJDBC.closeResource(connection, statement, resultSet, null, null);
        }
    }

    public static void main(String[] args) {
        insertUser(Arrays.asList("xiaobai","xiaohei","xiaohuang"));
    }

}

这时我们查看数据库,应该就能看到三条新增的用户了。(这里需要注意的是,需要设置id为自增id,否则会报主键不存在的错误)

数据库连接池

由于JDBC获取的一次数据库连接,就需要客户端与服务端进行4次的网络传输,而且一般来说,客户端和服务端不在同一台机器上,所以建立连接时间开销较大。同时,又由于数据库的最大连接数往往是有限的,如果不限制地频繁地建立数据库连接,一旦连接个数超过限值,就容易造成数据库的崩溃,从而影响正常业务的开发。在企业级的java web 开发中,数据库查询是最频繁的操作,因此,有必要对数据库连接做必要的管理。

连接池的作用

  • 连接复用:从“创建连接”改变为“租借”,避免数据库连接的频繁创建。
  • 限制最大并发连接数:在客户端实现最大并发连接数的限制,以及线程排队等功能。

DBCP连接池

DBCP是Apache开源的一个连接池项目,也是tomcat使用的连接池组件,在java开发中被广泛使用。其他用途广泛的连接池有hikari(日本开发的一个连接池,Sprintboot2.x默认支持的连接池)、druid(阿里开发的一个开源连接池)等。我们这里只介绍DBCP连接池。DBCP连接池的核心类库如下:

  • commons-dbcp.jar
  • commons-pool.jar
  • commons-logging.jar

接下来,我们来演示如何使用DBCP连接池管理连接。首先,我们先引入DBCP依赖:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.7.0</version>
</dependency>

创建DBCP连接池需要使用BasicDataSource类,它有以下几个核心方法:

69_5.png代码如下:

public class DbcpJDBC {

    private static BasicDataSource dbPool = null;
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";

    public static void dbPoolInit(){
        // 1.初始化Dbcp连接池
        dbPool = new BasicDataSource();
        dbPool.setUrl(DB_URL);
        dbPool.setDriverClassName(JDBC_DRIVER);
        dbPool.setUsername(USER);
        dbPool.setPassword(PASSWORD);
    }

    public static void databaseOperation() {

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            // 2. 建立数据库连接
            connection = dbPool.getConnection();

            // 3. 执行sql语句
            statement = connection.createStatement();
            resultSet = statement.executeQuery("select name from user");

            // 4. 获取执行结果
            while (resultSet.next()) {
                System.out.println("Hello " + resultSet.getString("name"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 清理环境
            StreamJDBC.closeResource(connection, statement, resultSet,null,null);
        }

    }

    public static void main(String[] args)
    {
        dbPoolInit();
        databaseOperation();
    }

}

DBCP的高级配置与定期检查

DBCP有相关的方法,用于配置连接池的相关参数,比如最大连接数,最大空闲连接数等;DBCP还有几个关于定期检查的方法,用于定期检查服务端连接是否失效,从而防止租借到失效的连接。具体的方法使用参见如下的代码:

    public static void dbPoolInit(){
        // 1.初始化Dbcp连接池
        dbPool = new BasicDataSource();
        dbPool.setUrl(DB_URL);
        dbPool.setDriverClassName(JDBC_DRIVER);
        dbPool.setUsername(USER);
        dbPool.setPassword(PASSWORD);

        //高级设置
        //设置初始连接数为1
        dbPool.setInitialSize(1);
        //设置最大连接数为10
        dbPool.setMaxTotal(10);
        //设置队列的最大等待时间为10秒
        dbPool.setMaxWaitMillis(10000);
        //设置最大空闲连接数为1,超过部分的连接,则自动被回收
        dbPool.setMaxIdle(1);
        //设置最小空闲连接数为1,小于它,则自动创建连接,一般来说,为了避免频繁地创建或者释放连接,建议将maxIdle和minIdle都设置为1
        dbPool.setMinIdle(1);

        //定期检查
        //如果没有设置,mysql服务器会自动关闭空闲时间超过8小时的连接,这时候客户端却并不清楚连接已经被服务端关闭了
        //当应用程序向连接池租借连接时,连接池可能会将失效的连接直接租借给客户端,客户端使用这个连接操作时,就会报相应的sql异常
        //通过定期对连接池的空闲连接进行检查,在服务端关闭连接之前,我们保证将这些连接销毁掉,重新补充新的连接
        //当连接空闲时,进行检查
        dbPool.setTestWhileIdle(true);
        //最小的空闲时间,当空闲时间超过该值时,自动销毁连接
        dbPool.setMinEvictableIdleTimeMillis(10000);
        //检查空闲时间的时间间隔,建议设置为小于服务器自动关闭连接的阈值时间,也就是说,如果没有设置空闲时间,mysql的默认空闲时间是8个小时,所以设置需要小于8个小时
        dbPool.setTimeBetweenEvictionRunsMillis(1000);

    }

我们可以测试我们配置地连接池参数有没有生效。为了演示方便,排除干扰参数,这里我们只设置了DBCP连接池地最大连接数为2。在代码里面,我们定义了两个方法,一个是jdbcTest()方法,通过普通地jdbc的方式查询数据;另外一个是dbcpTest()方法,通过dbcp连接池获取连接的方式查询数据库。两者都是在10秒内不断地进行查询。然后在main方法中,我们启动10个线程,去调用jdbcTest()方法。代码如下图:

public class ThreadJDBC {

    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    private static BasicDataSource dbPool = null;

    public static void dbPoolInit() {
        // 1.初始化Dbcp连接池
        dbPool = new BasicDataSource();
        dbPool.setUrl(DB_URL);
        dbPool.setDriverClassName(JDBC_DRIVER);
        dbPool.setUsername(USER);
        dbPool.setPassword(PASSWORD);

        //设置最大连接数为10
        dbPool.setMaxTotal(2);

    }

    /**
     * 纯粹JDBC查询
     */
    public static void jdbcTest() {
        //在10秒内不断地进行查询
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < 10000){
            try {
                 Class.forName(JDBC_DRIVER);
                databaseOperation(DriverManager.getConnection(DB_URL, USER, PASSWORD));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * dbcp查询
     */
    public static void dbcpTest() {
        //在10秒内不断地进行查询
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < 10000){
            try {
                databaseOperation(dbPool.getConnection());
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void databaseOperation(Connection connection) {

        Statement statement = null;
        ResultSet resultSet = null;

        try {

            // 3. 执行sql语句
            statement = connection.createStatement();
            resultSet = statement.executeQuery("select name from user");

            // 4. 获取执行结果
            while (resultSet.next()) {
                System.out.println("Hello " + resultSet.getString("name"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 清理环境
            StreamJDBC.closeResource(connection, statement, resultSet, null, null);
        }

    }

    public static void main(String[] args) {
        dbPoolInit();
        for(int i=0;i<10;i++){
            Thread thread = new Thread(() -> {
                jdbcTest();
            });
            thread.start();
        }
    }

}

运行main方法,我们立即用show processlist命令查询创建的连接数。

69_6.png可以看出,创建了10个连接。 接着我们以同样的方式,调用dbcpTest()方法,通过show processlist命令,我们可以看出,这时候创建出来的连接只有两个,所以说明我们的连接池配置成功了。

69_7.png

文章永久链接:https://tech.souyunku.com/26744

未经允许不得转载:搜云库技术团队 » JDBC基础总结

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们