• Java基础JDBC


    前言

    📢📢📢本篇博文主要学习廖雪峰博文JDBC编程,博文地址:https://www.liaoxuefeng.com/wiki/1252599548343744/1255943820274272

    1.JDBC

    JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。

    例如,我们在Java代码中如果要访问MySQL,那么必须编写代码操作JDBC接口。注意到JDBC接口是Java标准库自带的,所以可以直接编译。而具体的JDBC驱动是由数据库厂商提供的,例如,MySQL的JDBC驱动由Oracle提供。因此,访问某个具体的数据库,我们只需要引入该厂商提供的JDBC驱动,就可以通过JDBC接口来访问,这样保证了Java程序编写的是一套数据库访问代码,却可以访问各种不同的数据库,因为他们都提供了标准的JDBC驱动:

    在这里插入图片描述
    从代码来看,Java标准库自带的JDBC接口其实就是定义了一组接口,而某个具体的JDBC驱动其实就是实现了这些接口的类:

    在这里插入图片描述
    实际上,一个MySQL的JDBC的驱动就是一个jar包,它本身也是纯Java编写的。我们自己编写的代码只需要引用Java标准库提供的java.sql包下面的相关接口,由此再间接地通过MySQL驱动的jar包通过网络访问MySQL服务器,所有复杂的网络通讯都被封装到JDBC驱动中,因此,Java程序本身只需要引入一个MySQL驱动的jar包就可以正常访问MySQL服务器:

    在这里插入图片描述

    2.JDBC的操作

    (1) 准备

    创建数据库learjdbc并在该数据库下创建students:

    在DOS环境下运行( 默认你已经安装MySQL数据库 )

    -- 创建数据库learjdbc:
    DROP DATABASE IF EXISTS learnjdbc;
    CREATE DATABASE learnjdbc;
    
    -- 创建表students:
    USE learnjdbc;
    CREATE TABLE students (
      id BIGINT AUTO_INCREMENT NOT NULL,
      name VARCHAR(50) NOT NULL,
      gender TINYINT(1) NOT NULL,
      grade INT NOT NULL,
      score INT NOT NULL,
      PRIMARY KEY(id)
    ) Engine=INNODB DEFAULT CHARSET=UTF8;
    
    -- 插入初始数据:
    INSERT INTO students (name, gender, grade, score) VALUES ('小明', 1, 1, 88);
    INSERT INTO students (name, gender, grade, score) VALUES ('小红', 1, 1, 95);
    INSERT INTO students (name, gender, grade, score) VALUES ('小军', 0, 1, 93);
    INSERT INTO students (name, gender, grade, score) VALUES ('小白', 0, 1, 100);
    INSERT INTO students (name, gender, grade, score) VALUES ('小牛', 1, 2, 96);
    INSERT INTO students (name, gender, grade, score) VALUES ('小兵', 1, 2, 99);
    INSERT INTO students (name, gender, grade, score) VALUES ('小强', 0, 2, 86);
    INSERT INTO students (name, gender, grade, score) VALUES ('小乔', 0, 2, 79);
    INSERT INTO students (name, gender, grade, score) VALUES ('小青', 1, 3, 85);
    INSERT INTO students (name, gender, grade, score) VALUES ('小王', 1, 3, 90);
    INSERT INTO students (name, gender, grade, score) VALUES ('小林', 0, 3, 91);
    INSERT INTO students (name, gender, grade, score) VALUES ('小贝', 0, 3, 77);
    
    • 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

    运行效果:

    在这里插入图片描述

    准备相应驱动的jar包:

    • 官方驱动下载

    你可以参照这篇博文:怎么在官网上下载java连接mysql的驱动jar包?

    • Maven项目导入MySQL的驱动

    注意到这里添加依赖的scope是runtime,因为编译Java程序并不需要MySQL的这个jar包,只有在运行期才需要使用。如果把runtime改成compile,虽然也能正常编译,但是在IDE里写程序的时候,会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆,所以坚决不要设置为compile。

    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
        <scope>runtimescope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    项目中只需要在pom.xml中导入依赖即可:

    在这里插入图片描述

    (2) 数据库的连接

    数据库的操作过程如下:

    • 获取连接数据库的参数(推荐从配置文件中获取)
    • 通过DriverManager.getConnection()获取连接对象Connection
    • 通过Connection对象对数据库进行相应的操作【CRUD】
    • 通过Connection.close()关闭连接:

    在这里插入图片描述
    备注:获取数据的参数一般从配置文件中获取。

    关闭数据库避免遗忘,建议使用try()进行自动关闭:

    实例代码:

    在这里插入图片描述

    (3) 数据库的查询操作

    数据库的查询操作过程如下:

    • 第一步,通过Connection提供的createStatement()方法创建一个Statement对象,用于执行一个查询;
    • 第二步,执行Statement对象提供的executeQuery(“SELECT * FROM
      students”)并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集;
    • 第三步,反复调用ResultSet的next()方法并读取每一行结果。

    实例代码:

    在这里插入图片描述
    运行效果:

    在这里插入图片描述

    相关细节:

    Statment和ResultSet都是需要关闭的资源,因此嵌套使用try (resource)确保及时关闭:

    在这里插入图片描述

    rs.next()用于判断是否有下一行记录,如果有,将自动把当前行移动到下一行(一开始获得ResultSet时当前行不是第一行) 类似于MySQL里的光标 ,并且ResultSet获取列时,索引从1开始而不是0;

    在这里插入图片描述

    如果获取索引0会报如下异常:

    在这里插入图片描述

    索引对应数据库中对应列的顺序:

    数据库里列的顺序如下:

    在这里插入图片描述
    查看一下ReslutSet的fields变量( 顺序和数据库中的顺序保持一致 ):

    在这里插入图片描述

    Java和数据库中的数据类型的映射关系,如下表所示:

    SQL数据类型Java数据类型
    BIT, BOOLboolean
    INTEGERint
    BIGINTlong
    REALfloat
    FLOAT, DOUBLEdouble
    CHAR, VARCHARString
    DECIMALBigDecimal
    DATEjava.sql.Date, LocalDate
    TIMEjava.sql.Time, LocalTime

    除了通过索引获取数据库对应列的值外,还可以通过列名获取( 推荐 )

    在这里插入图片描述

    运行效果:

    在这里插入图片描述

    SQL注入:

    当我们进行条件查询的时候,如果直接通过拼接的方法拼接SQL语句可能出现SQL注入的危险,如下:

    实例例子 (通过用户输入相应用户的名字查询数据库的数据 ):

    在这里插入图片描述

    运行效果:

    在这里插入图片描述

    虽然上面的方式成功执行了我们的需求,但是这种方式非常危险,如果用户在传入的参数里面加入了相应SQL语句的话就可能执行一些非常危险的操作:

    在这里插入图片描述
    运行效果:

    上面的操作违背了我们本来的意图,我们只是想让用户通过相应的姓名查询该姓名的信息,但是这里查询了数据库中所有用户信息,在一些登入业务场景下,这是非常危险的一件事情。

    在这里插入图片描述

    使用Java对数据库进行操作时,必须使用PreparedStatement,严禁任何通过参数拼字符串的代码,如下:

    实例代码:

    在这里插入图片描述
    运行效果:

    在这里插入图片描述

    再次传入相关SQL语句试试:

    实例代码:

    在这里插入图片描述

    运行效果:

    在这里插入图片描述

    我们看看通过PreparedStatement后的SQL语句:

    在这里插入图片描述

    (4) 数据库的更新操作
    • 插入操作

    插入操作是INSERT,即插入一条新记录。通过JDBC进行插入,本质上也是用PreparedStatement执行一条SQL语句,不过最后执行的不是executeQuery(),而是executeUpdate()。示例代码如下:

    在这里插入图片描述

    运行效果:

    在这里插入图片描述

    • 插入并获取主键

    如果数据库的表设置了自增主键,那么在执行INSERT语句时,并不需要指定主键,数据库会自动分配主键。对于使用自增主键的程序,有个额外的步骤,就是如何获取插入后的自增主键的值。要获取自增主键,不能先插入,再查询。因为两条SQL执行期间可能有别的程序也插入了同一个表。获取自增主键的正确写法是在创建PreparedStatement的时候,指定一个RETURN_GENERATED_KEYS标志位,表示JDBC驱动必须返回插入的自增主键。示例代码如下:

    在这里插入图片描述

    运行效果:

    在这里插入图片描述

    在students表中id为自增主键:

    在这里插入图片描述

    • 更新

    更新操作是UPDATE语句,它可以一次更新若干列的记录。更新操作和插入操作在JDBC代码的层面上实际上没有区别,除了SQL语句不同:

    在这里插入图片描述

    执行前:

    在这里插入图片描述

    执行后:

    在这里插入图片描述

    • 删除

    删除操作是DELETE语句,它可以一次删除若干列。和更新一样,除了SQL语句不同外,JDBC代码都是相同的:

    在这里插入图片描述

    执行前:

    在这里插入图片描述

    执行后:

    在这里插入图片描述

    3.JDBC的事务

    数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列,有点类似于Java的synchronized同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:

    • Atomicity:原子性
    • Consistency:一致性
    • Isolation:隔离性
    • Durability:持久性

    数据库事务可以并发执行,而数据库系统从效率考虑,对事务定义了不同的隔离级别。SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:

    Isolation Level脏读(Dirty Read)不可重复读(Non Repeatable Read)幻读(Phantom Read)
    Read UncommittedYesYesYes
    Read Committed-YesYes
    Repeatable Read--Yes
    Serializable---

    备注:如果用户没有修改事务的隔离级别的话,MySQL的默认隔离级别是REPEATABLE_READ,修改事务隔离级别使用conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);进行修改conn为数据库的连接对象。

    实例:

    用户A和B账户余额都为300元,用户A向用户B转账300,然后用户B向用户A转账600元,实例代码如下:

    新增数据表:

    -- 创建accounts 表
    USE learnjdbc;
    CREATE TABLE accounts (
      id BIGINT AUTO_INCREMENT NOT NULL,
      name VARCHAR(50) NOT NULL,
      balance INT ,
      PRIMARY KEY(id)
    ) Engine=INNODB DEFAULT CHARSET=UTF8;
    
    -- 插入数据
    INSERT INTO accounts (name,balance) VALUES ('A',300);
    INSERT INTO accounts (name,balance) VALUES ('B',300);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实例代码:

    
        @Test
        public void  transation() throws SQLException {
            // 用户A向用户B转账300
            String fromA = "UPDATE accounts SET balance = balance - 300 WHERE name = ? AND balance >= 300";
            String toB = "UPDATE accounts SET balance = balance + 300 WHERE name = ?";
            boolean a1 = updateUser(fromA, new Object[]{"A"});
            boolean a2 = updateUser(toB, new Object[]{"B"});
            System.out.println("a1执行情况:"+a1+" a2执行情况:"+a2);
            // 当前余额情况
            ShowInfo();
    
            // 用户B向用户A转账300元
            String fromB =  "UPDATE accounts SET balance = balance - 300 WHERE name = ? AND balance >= 300";
            String toA =  "UPDATE accounts SET balance = balance + 300 WHERE name = ?";
            boolean b1 = updateUser(fromB, new Object[]{"B"});
            boolean b2 = updateUser(toA, new Object[]{"A"});
            System.out.println("b1执行情况:"+b1+" b2执行情况:"+b2);
            ShowInfo();
    
        }
    
        public boolean updateUser(String sql,Object[] parameters) throws SQLException {
            try( Connection conn = getMySQLConnect();) {
                try (PreparedStatement ps = conn.prepareStatement(sql);) {
                    for (int i = 0; i < parameters.length; i++) {
                        ps.setObject(i+1,parameters[i]);
                    }
                    int n = ps.executeUpdate(); // 返回更新的行数
                    System.out.println("执行了"+n+"条Update语句。");
                    return n > 0 ? true : false;
                }
            }
        }
    
        public void ShowInfo() throws SQLException {
            // 【1】获取数据库的连接
            try( Connection conn = getMySQLConnect();) {
                // 【2】 获取Statement对象,用于执行查询
                String sql = "select * from accounts ";
                try (Statement statement = conn.createStatement();) {
                    // 【3】 执行Statement对象提供的executeQuery("SELECT * FROM students")并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集
                    try (ResultSet resultSet = statement.executeQuery(sql);) {
                        // 【4】反复调用ResultSet的next()方法并读取每一行结果【相当于MySQL里面的光标】
                        while (resultSet.next()) {
                            int id = resultSet.getInt(1);
                            String name = resultSet.getString(2);
                            Long balance = resultSet.getLong(3);
                            System.out.println("id=" + id + " name=" + name + " 余额=" + balance);
                        }
                    }
                }
            }
        }
    
    • 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

    运行效果:

    在这里插入图片描述

    有的时候可能在某一步出现问题,但是数据库里的数据还是发生了变化,如下:

    在这里插入图片描述
    运行效果:

    在这里插入图片描述

    数据库里的数据还是发生了变化( 数据库中本金少了300元! ):

    在这里插入图片描述

    造成这个的原因是因为默认情况下,我们获取到Connection连接后,总是处于“自动提交”模式,也就是每执行一条SQL都是作为事务自动执行的,你可以通过conn.setAutoCommit(false);关闭自动提交模式,当你传入true的时候表示开启,下面我们用事务的方式完成上面的操作,实例代码如下:

    数据恢复:

    使用 update set balance = 300 from accounts where id = 1; 语句使A和B账号的余额都为300元。

    在这里插入图片描述
    实例代码:

     @Test
        public void  transation() throws SQLException {
            Connection mySQLConnect = null;
            try {
                // 获取数据的连接
                 mySQLConnect = getMySQLConnect();
                // 【1】关闭自动提交
                mySQLConnect.setAutoCommit(false);
                // 用户A向用户B转账300
                String fromA = "UPDATE accounts SET balance = balance - 300 WHERE name = ? AND balance >= 300";
                String toB = "UPDATE accounts SET balance = balance + 300  name = ?";
                boolean a1 = updateUser(mySQLConnect,fromA, new Object[]{"A"});
                boolean a2 = updateUser(mySQLConnect,toB, new Object[]{"B"});
                System.out.println("a1执行情况:"+a1+" a2执行情况:"+a2);
                // 当前余额情况
                ShowInfo(mySQLConnect);
    
                // 用户B向用户A转账300元
                String fromB =  "UPDATE accounts SET balance = balance - 300 WHERE name = ? AND balance >= 300";
                String toA =  "UPDATE accounts SET balance = balance + 300 WHERE name = ?";
                boolean b1 = updateUser(mySQLConnect,fromB, new Object[]{"B"});
                boolean b2 = updateUser(mySQLConnect,toA, new Object[]{"A"});
                System.out.println("b1执行情况:"+b1+" b2执行情况:"+b2);
                ShowInfo(mySQLConnect);
                // 【2】提交事务
                mySQLConnect.commit();
    
            } catch (Exception e) {
                // 【3】回滚事务
                if (mySQLConnect != null) {
                    mySQLConnect.rollback();
                }
                e.printStackTrace();
            }
    
            finally {
                if (mySQLConnect != null) {
                    // 【4】开启自动提交
                    mySQLConnect.setAutoCommit(true);
                    // 关闭连接
                    mySQLConnect.close();
                }
            }
    
    
        }
    
        private Connection getMySQLConnect() throws SQLException {
            // 【1】获取连接数据库的参数
            String JDBC_URL = "jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8";
            String JDBC_USER = "root";
            String JDBC_PASSWORD = "root";
            Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            return conn;
        }
        
        public boolean updateUser(Connection connection,String sql,Object[] parameters) throws SQLException {
                Connection conn = connection;
                try (PreparedStatement ps = conn.prepareStatement(sql);) {
                    for (int i = 0; i < parameters.length; i++) {
                        ps.setObject(i+1,parameters[i]);
                    }
                    int n = ps.executeUpdate(); // 返回更新的行数
                    System.out.println("执行了"+n+"条Update语句。");
                    return n > 0 ? true : false;
                }
        }
    
        public void ShowInfo(Connection connection) throws SQLException {
            // 【1】获取数据库的连接
                Connection conn = connection;
                // 【2】 获取Statement对象,用于执行查询
                String sql = "select * from accounts ";
                try (Statement statement = conn.createStatement();) {
                    // 【3】 执行Statement对象提供的executeQuery("SELECT * FROM students")并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集
                    try (ResultSet resultSet = statement.executeQuery(sql);) {
                        // 【4】反复调用ResultSet的next()方法并读取每一行结果【相当于MySQL里面的光标】
                        while (resultSet.next()) {
                            int id = resultSet.getInt(1);
                            String name = resultSet.getString(2);
                            Long balance = resultSet.getLong(3);
                            System.out.println("id=" + id + " name=" + name + " 余额=" + balance);
                        }
                    }
                }
        }
    
    • 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

    运行效果:

    在这里插入图片描述
    观察数据库里的数据( 虽然发生了异常,但是数据库里的数据没有发生变化 ):

    在这里插入图片描述

    4.JDBC的Batch

    在前面的例子中(账号转钱例子),执行的4条修改语句中,不难发现存在二组SQL语句相同,只是参数不相同,对于上面的写法我们可以进行优化,减少执行updateUser()的次数,如下:

    修改updateUser()方法里的代码如下:

    在这里插入图片描述
    修改transation()方法里的代码如下:

    在这里插入图片描述
    运行效果:

    在这里插入图片描述

    通过一个循环来执行每个PreparedStatement虽然可行,但是性能很低。SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。在JDBC代码中,我们可以利用SQL数据库的这一特性,把同一个SQL但参数不同的若干次操作合并为一个batch执行,实例代码如下:

    在这里插入图片描述

    运行效果:

    在这里插入图片描述

    上面的例子可能不是很恰当,下面给出一个批量插入用户的例子代码:

    插入数据前:

    在这里插入图片描述

    实例代码:

     @Test
        public void addUserByBatch() throws SQLException {
            // 批量插入数据
            List<student> list = new ArrayList<>();
            Random random = new Random();
            for (int i = 14; i <= 24; i++) {
                student temStu = new student("User"+i,i %2 ==0?1:0,i %2 ==0?1:2,random.nextInt(100));
                list.add(temStu);
            }
            // 批量插入数据
            String sql = "INSERT INTO students (name, gender, grade, score) VALUES (?,?,?,?)";
            insertUserByBatch(list,sql);
        }
    
        private void insertUserByBatch(List<student> list,String sql) throws SQLException {
            // SQL连接
            try (Connection connection = getMySQLConnect();)
            {
                // 关闭自动提交
                connection.setAutoCommit(false);
                try (PreparedStatement statement = connection.prepareStatement(sql)) {
                    for (student stu : list) {
                        statement.setObject(1, stu.getName());
                        statement.setObject(2, stu.getGender());
                        statement.setObject(3, stu.getGrade());
                        statement.setObject(4, stu.getScore());
                        statement.addBatch();
                    }
                    // 执行批量操作
                    int[] ints = statement.executeBatch();
                    for (int i = 0; i < ints.length; i++) {
                        System.out.println("执行了"+ints[i]+"条INSERT语句。");
                    }
                    // 提交事务
                    connection.commit();
                } catch (Exception e) {
                    // 回滚事务
                    connection.rollback();
                }
                finally {
                    connection.setAutoCommit(true);
                }
            }
        }
    
    
        class student{
            private String name = "";
            private int gender = -1;
            private long grade = -1;
            private long score = -1;
    
            public student(String name, int gender, long grade, long score) {
                this.name = name;
                this.gender = gender;
                this.grade = grade;
                this.score = score;
            }
    
            @Override
            public String toString() {
                return "student{" +
                        ", name='" + name + '\'' +
                        ", gender=" + gender +
                        ", grade=" + grade +
                        ", score=" + score +
                        '}';
            }
    
            public String getName() {
                return name;
            }
    
            public int getGender() {
                return gender;
            }
    
            public long getGrade() {
                return grade;
            }
    
            public long getScore() {
                return score;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public void setGender(int gender) {
                this.gender = gender;
            }
    
            public void setGrade(long grade) {
                this.grade = grade;
            }
    
            public void setScore(long score) {
                this.score = score;
            }
        }
    
    • 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
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    运行效果:

    在这里插入图片描述

    执行后:

    在这里插入图片描述

    5.JDBC的连接池

    在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。JDBC连接池有一个标准的接口javax.sql.DataSource,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,我们必须选择一个JDBC连接池的实现。常用的JDBC连接池有:

    • HikariCP(SpringBoot官方默认支持的数据库连接池)
    • C3P0
    • BoneCP
    • Druid
    • DBCP
    • Tomcat jdbc Pool
    连接池最新发布版本最新发布时间
    HikariCP5.0.12022年10月1日
    C3P00.9.5.52019年12月12日
    BoneCP0.8.x2013年10月23日
    Druid1.2.x2022年9月11日
    DBCP2.9.x2021年8月4日
    tomcat-jdbc10.1.x2022年7月20日

    主流连接池各项功能对比如下:
    在这里插入图片描述

    备注:来源于这篇博文:https://blog.csdn.net/wyouwd1/article/details/122525759

    目前使用最广泛的是HikariCP。我们以HikariCP为例,要使用JDBC连接池,先添加HikariCP的依赖如下:
    依赖:

    <dependency>
        <groupId>com.zaxxergroupId>
        <artifactId>HikariCPartifactId>
        <version>2.7.1version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在pom.xml中导入依赖:

    在这里插入图片描述

    紧接着,我们需要创建一个DataSource实例,在创建这个实例之前我们需要创建一个HikariConfig 的实例,该实例为设置数据库连接池的配置信息,常常设置参数有维护的最小、最大活动连接数,指定一个连接在空闲一段时间后自动关闭等,,整个实例代码如下:

            HikariConfig config = new HikariConfig();
            config.setJdbcUrl("jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8");
            config.setUsername("root");
            config.setPassword("root");
            config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
            config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
            config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
            DataSource ds = new HikariDataSource(config);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意创建DataSource也是一个非常昂贵的操作,所以通常DataSource实例总是作为一个全局变量存储,并贯穿整个应用程序的生命周期。

    有了连接池以后,我们如何使用它呢?和前面的代码类似,只是获取Connection时,把DriverManage.getConnection()改为ds.getConnection():

      // 获取Connect对象
            try( Connection connection = ds.getConnection();) {
    }
    
    • 1
    • 2
    • 3

    完整实例代码如下:

     @Test
        public void ConnectPool() throws SQLException {
            // 【1】 创建DataSource实例
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl("jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8");
            config.setUsername("root");
            config.setPassword("root");
            config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
            config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
            config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
            DataSource ds = new HikariDataSource(config);
    
    
            // 获取Connect对象
            try( Connection connection = ds.getConnection();) {
    
                // 获取Statement对象
                try (Statement statement = connection.createStatement()) {
                    String sql = "SELECT * FROM students";
                    try (ResultSet rs = statement.executeQuery(sql)){
                        while (rs.next()) {
                            long id = rs.getLong("id");
                            long grade = rs.getLong("grade");
                            String name = rs.getString("name");
                            String gender = rs.getString("gender");
                            long score = rs.getInt("score");
                            System.out.println("id="+id+" name="+name+" gender="+gender+" grade="+grade+" score="+score);
                        }
                    }
                }
            }
    
        }
    
    • 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

    运行效果:

    一开始,连接池内部并没有连接,所以,第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。当我们调用conn.close()方法时(在try(resource){…}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回。因此,连接池内部维护了若干个Connection实例,如果调用ds.getConnection(),就选择一个空闲连接,并标记它为“正在使用”然后返回,如果对Connection调用close(),那么就把连接再次标记为“空闲”从而等待下次调用。这样一来,我们就通过连接池维护了少量连接,但可以频繁地执行大量的SQL语句。

    在这里插入图片描述

  • 相关阅读:
    Azure的AI使用-(语言检测、图像分析、图像文本识别)
    ArcGIS || ENVI:如何将彩色影像拆分为R、G、B以及H、S、I(B/V)影像?
    fdisk 命令实现磁盘分区详细教程
    电子科技大学链时代工作室招新题C语言部分---题号E
    GIN框架路由的实现原理
    Adobe研究人员研发新AI模型LRM:实现从2D样本瞬时生成3D图像
    SPARK中关于HighlyCompressedMapStatus的说明(会造成运行时的数据不精确)
    网络安全(黑客)自学
    ORACLE设置快照回滚点
    Python pywin32实现word和Excel的处理
  • 原文地址:https://blog.csdn.net/weixin_42753193/article/details/126865188