前言

本笔记参考

  • 哔哩哔哩黑马程序员
  • 哔哩哔哩遇见狂神说

JDBC简介

什么是JDBC

之前我们在讲SQL的时候曾经讲过,SQL中有很多实现分类,比如MySQL,Oracle,SQL Server等等。

这些SQL语言相当于同一种语言的不同方言,我们如果想在Java代码中实现对数据库的操作,必须实现这种方言。

然而语言太多,实现起来必然非常麻烦,所以有了JDBC。

那么JDBC是什么?其实是一套标准,一套规范。我们只要遵循这套规范。

而具体的规范的实现不是我们进行实现,而是各个语言进行实现。

这就是我们经常使用的,面向接口的编程思想

使用JDBC的好处

程序员只需要关注具体的方法使用,而不用去关心是怎么样实现的,我们如果使用代码,只需要修改一小部分就可以访问其他JDBC支持的数据库。

比如我们使用JDBC规范中的MySQL,然后我们想要切换到Oracle也是非常容易的。

也叫依赖,是我们使用JDBC所必须的依赖

  • java.sql:所有和JDBC访问数据库需要的接口和类,在Java中存在
  • javax.sql:其他的依赖实现,比如连接池,在Java中存在
  • 数据库驱动:对JDBC实现的类,由各个厂商提供,需要我们手动下载

    我们基于MySQL实现,所以我们的驱动是mysql-connector-java.jar 其中有几个对应版本 JDBC - 图1 官方推荐,MySQL5.6以上使用8.0 下面是jar包的下载地址 https://repo1.maven.org/maven2/mysql/mysql-connector-java/ 我所选用的是mysql-connector-java-8.0.11.jar 这里有一个比较坑的点 JDBC - 图2 不要用xxx-sources.jar,应该用xxx.jar

JDBC的实现

JDBC核心API

接口或类 作用
DriverManager 管理和注册数据库驱动
得到数据库连接对象
Connection 数据库连接对象,可以创建Statement和PreStatement
Statement 发送SQL语句到数据库
PreStatement Statement的子接口
ResultSet 封装结构,返回到Java中

起步

先跟着流程跑一遍,大部分的事情都会懂

  1. 打开IDEA,新建一个Java项目工程

    JDBC - 图3

  2. 在项目下新建一个lib目录,将jar包放到lib下面

    JDBC - 图4

  3. 在Jar包上右键–>添加到库,如果是英文应该是Add as Library

    JDBC - 图5

  4. 准备好数据库

    现在我的student/users有如下几个数据: JDBC - 图6

  5. 编写Java代码 ```java package com.howling;

import java.sql.*;

public class Step { public static void main(String[] args) throws ClassNotFoundException, SQLException {

  1. //指定连接对象 jdbc:mysql://地址/数据库?参数,注意这里参数在高版本的MySQL中必须要指定时区
  2. String url = "jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT";
  3. //指定用户名和密码,作为必须的连接参数
  4. String username = "root";
  5. String password = "root";
  6. //1. 加载JDBC驱动
  7. Class.forName("com.mysql.cj.jdbc.Driver");
  8. //2. 根据数据库驱动管理获得连接对象
  9. Connection conn = DriverManager.getConnection(url, username, password);
  10. //3. 创建SQL语句对象
  11. Statement statement = conn.createStatement();
  12. //4. 定义SQL语句
  13. String sql = "select * from users";
  14. //5. 发送SQL,并获取返回的结果集
  15. ResultSet resultSet = statement.executeQuery(sql);
  16. while (resultSet.next()) {
  17. System.out.println("id=" + resultSet.getObject("id"));
  18. System.out.println("name=" + resultSet.getObject("name"));
  19. System.out.println("password=" + resultSet.getObject("password"));
  20. System.out.println("email=" + resultSet.getObject("email"));
  21. System.out.println("birthday=" + resultSet.getObject("birthday"));
  22. }
  23. //关闭连接
  24. resultSet.close();
  25. statement.close();
  26. conn.close();
  27. }

}

> - Oracle写法:jdbc:oracle:thin:@localhost:1521:database
> - SqlServer写法:jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=database
> - MySql写法:jdbc:mysql://localhost:3306/database
> 
其实MySQL假如地址是localhost:3306的话,可以简写为`/`,那就是`jdbc:mysql:///database`


<a name="a1632591"></a>
## 参数理解

> 跑完了流程,基本概念应该是心中有数了,接下来进行


- **DriverManager**
| 方法 | 作用 |
| --- | --- |
| `getConnection (String url, String user, String password)` | 通过url和username,password进行连接 |
| `getConnection (String url, Properties info)` | 通过url和properties进行连接,properties中存放username和password |


```java
String url = "jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT";

String username = "root";
String password = "root";

Properties properties = new Properties();
properties.setProperty("user",username);
properties.setProperty("password",password);

Connection conn = DriverManager.getConnection(url,properties);
  • Statement | 方法 | 作用 | | —- | —- | | executeUpdate(String sql) | 发送DML语句,返回值是对数据库影响的行数 | | executeQuery(String sql) | 发送DQL语句,返回值是查询出来的结果集 | | execute(String sql) | 发送任意SQL | | addBatch(String sql) | 把多条语句放到一起批处理 | | executeBatch() | 向数据库发送一批sql语句执行 |
  • 建议

之前的异常都是抛出的,但是现在我们建议使用try-catch-finally语句,将关闭操作放到finally代码块中

  • 例子

    为了理解方便,现在使用DML进行一次语句的插入

//指定连接对象   jdbc:mysql://地址/数据库?参数,注意这里参数在高版本的MySQL中必须要指定时区
String url = "jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT";
//指定用户名和密码,作为必须的连接参数
String username = "root";
String password = "root";

Properties properties = new Properties();
properties.setProperty("user", username);
properties.setProperty("password", password);

//1. 加载JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");

//2. 根据数据库驱动管理获得连接对象
Connection conn = DriverManager.getConnection(url, properties);

//3. 创建SQL语句对象
Statement statement = conn.createStatement();

//4. 定义SQL语句
String sql = "INSERT INTO users(id,NAME,PASSWORD,email,birthday) VALUES(4,'赵六','098765','zl@qq.com','1999-12-04')";


//5. 发送SQL,并获取返回的结果集
statement.executeUpdate(sql);

//关闭连接
statement.close();
conn.close();

有没有发现什么,我们只需要改变SQL语句,其他的东西什么也不用改变,但是每一次都这么写就非常麻烦 JDBC - 图7

  • connect | 方法 | 说明 | | —- | —- | | createStatement() | 创建SQL语句对象 | | prepareStatement(sql) | 创建预编译的SQL语句对象 | | setAutoCommit(boolean) | 设置事务是否自动提交 | | commit() | 提交事务 | | rollback() | 事务回滚 |
  • Statement | 方法 | 说明 | | —- | —- | | getObject(int index) | 获取任意类型的数据 | | getObject(String name) | 获取任意类型的数据 | | getString(int index) | 返回值使用String获取 | | getString(String name) | 返回值使用String获取 |

除了这个之外,还有getBooleangetBytegetShrot,….. JDBC - 图8

  • ResultSet | 方法 | 说明 | | —- | —- | | next() | 移动到下一行 | | previous() | 移动到上一行 | | absolute(int row) | 移到指定行 | | beforeFirst() | 移到最前面 | | afterLast() | 移到最后面 |

JDBC工具类

在之前我们就已经讲过了,我们发现执行DQL和DML的时候只需要改动几行代码,而基本的框架是不用改变的,所以我们应该写一个工具类,更加进一步的封装 这个JDBC的工具类十分的简单,但是总体来说JDBC就这么几个固定的步骤 1、加载驱动 2、连接数据库 3、向数据库发送SQL对象Statement 4、编写SQL 5、执行SQL,获取结果 6、关闭连接

package com.howling.utils;

import java.sql.*;

public class JDBCUtil {

    private static final String USERNAME = "root";
    private static final String PASSWORD = "root";
    private static final String URL = "jdbc:mysql://localhost:3306/student?allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT";
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";

    /**
     * 1、注册驱动 
     */
    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 2、得到连接
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }

    /**
     * 6、关闭打开资源
     */
    public static void close(Connection connection, Statement statement) {

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    public static void close(Connection connection, Statement statement, ResultSet resultSet) {

        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

测试

package com.howling;

import com.howling.utils.JDBCUtil;

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

public class Step {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtil.getConnection();

            statement = connection.createStatement();

            String sql = "select * from emp";

            resultSet = statement.executeQuery(sql);

            while (resultSet.next()) {
                System.out.print("id=" + resultSet.getObject("id")+"\t");
                System.out.print("name=" + resultSet.getObject("name")+"\t");
                System.out.print("gender=" + resultSet.getObject("gender")+"\t");
                System.out.print("salary=" + resultSet.getObject("salary")+"\t");
                System.out.print("join_date=" + resultSet.getObject("join_date")+"\t");
                System.out.print("dept_id=" + resultSet.getObject("dept_id")+"\t\n");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JDBCUtil.close(connection, statement, resultSet);
        }
    }
}

JDBC - 图9

预编译SQL

SQL注入

Statement有一个非常致命的错误,那就是你的SQL语句全部都是编译好的,非常容易遭到攻击。

在实际的开发过程中,用户的用户名和密码肯定是自己手动输入进去的

我们发现,只要用户名和密码是正确的就会登陆进去,那么假如密码是:1=1,这样永远正确的数字,那么密码就会永远正确

因为我们的密码是手动拼接地,所以SQL注入是非常有可能存在的

为了防止SQL注入,我们就不能将用户输入的密码和我们的SQl语句进行拼接

PreparedStatement

PreparedStatement介绍

PreparedStatement这个接口可以防止SQL注入,它是Statement的子接口,所以这就意味着我们的Statement中的方法在PreparedStatement都可以使用

PreparedStatement相对于Statement有几个好处

1、效率高

Statemnt每执行一条语句都会首先发送给数据库,数据库先编译然后再执行。假如有一万条语句,数据库就要编译一万次,执行一万次、 PrepareStatement会首先将SQL发送给数据库预编译,然后PrepareStatement会引入预编译之后的结果 可以多次传入不同的参数给PrepareStatement对象执行,假如有一万条数据,那么数据库只需要预编译一次

2、防止SQl注入

3、提高了程序的可读性


PreparedStatement起步

1、改造JDBCUtil

package com.howling.utils;

import java.sql.*;

public class JDBCUtil {

    private static final String USERNAME = "root";
    private static final String PASSWORD = "root";
    private static final String URL = "jdbc:mysql://localhost:3306/student?allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT";
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";


    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }


    public static void close(Connection connection, Statement statement) {

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {

        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

2、编写SQL语句,执行

package com.howling;

import com.howling.utils.JDBCUtil;

import java.sql.*;

public class Step {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {

            String sql = "select * from emp where gender = ? and dept_id = ?";

            connection = JDBCUtil.getConnection();

            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, "男");
            preparedStatement.setInt(2, 1);

            resultSet = preparedStatement.executeQuery();

            while (resultSet.next()) {
                System.out.print("id=" + resultSet.getObject("id") + "\t");
                System.out.print("name=" + resultSet.getObject("name") + "\t");
                System.out.print("gender=" + resultSet.getObject("gender") + "\t");
                System.out.print("salary=" + resultSet.getObject("salary") + "\t");
                System.out.print("join_date=" + resultSet.getObject("join_date") + "\t");
                System.out.print("dept_id=" + resultSet.getObject("dept_id") + "\t\n");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JDBCUtil.close(connection, preparedStatement, resultSet);
        }
    }
}

JDBC - 图10


PreparedStatement详解

我们从刚才的起步可以看到

1、我们首先是得到了一个PreparedStatement对象,用于替代Statement

2、在PreparedStatemnt中就直接传入了参数

3、使用preparedStatement设置了具体的参数

方法
setDouble
setFloat
setInt
setLong
setObject
setString

注意,这个参数值是从1开始的,这些方法都有两个参数:(问号的位置,值) 比如我设置了preparedStatement.setString(1, “男”);指的是第一个问号的值为男


JDBC连接池

连接池介绍

其实JDBC连接池就是一个集合容器,这个容器存放的是一些数据库的连接

当系统初始化时,容器会被创建,容器中有一些连接的对象

当用户来访问数据库的时候,拿一个没有使用的连接去连接数据库

当用户访问数据库完成之后,连接不会关闭,而是再放回连接池,等待下一次用户的访问

当一切都完成之后,连接统一关闭


这样做有几个好处

1、节约资源,不用每次访问都创建连接和关闭连接

2、用户访问更加高效


DataSource我们使用的是javax.sql.Datasouce

1、获取连接:getConnection()

2、归还连接:close()

这里注意了,虽然我们这里调用的的是close方法,但其实连接并没有进行关闭,而是放回到了数据库连接池中,等待下一次调用


各大厂商的连接池

介绍

一般来说,数据库连接池我们不用去实现它,有的厂商会去实现,我们只需要使用即可

目前数据库连接池有很多,但是经过市场检验最终站稳脚跟的有

  • DBCP:由Apache提供
  • C3P0:老牌开源
  • Druid:由阿里巴巴提供,支付宝和淘宝专用的数据库连接池,支持所有JDBC兼容的数据库
  • HikariCP:后起之秀

    为什么说HikariCP是后起之秀呢,听说他的速度比C3P0快200倍,吹牛逼说秒杀Druid。 这是从博文里找到的几张图:https://www.cnblogs.com/xingzc/p/6073730.html JDBC - 图11 看这个数据貌似确实是非常牛逼的,官网解释了Hikari的一些优化 1、代码精简 2、优化代理和拦截器 3、自定义数组类型代替ArrayList 4、自定义集合类型,提高并发读写效率 5、其他 稳定性的数据 JDBC - 图12 对现在的连接池来说,貌似Hikari和Druid都是一个不错的选择,C3P0太古老,但是在学习的时候可以使用。 并且,现在的SprinBoot貌似默认的连接池就是Hikari 但是在这里只介绍C3P0和Druid

C3P0

1、导入依赖,如果不是maven直接下载这几个jar包放到项目里

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.13</version>
    </dependency>
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>mchange-commons-java</artifactId>
        <version>0.2.12</version>
    </dependency>
</dependencies>

MySQL的驱动和C3P0的包

2、在resources下面创建一个名叫c3p0-config的xml文件,如果不是maven项目直接放到src下,会自动扫描

3、编写c3p0的配置

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 默认配置,如果没有指定则使用这个配置 -->
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/student?allowPublicKeyRetrieval=true&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT</property>
        <property name="user">root</property>
        <property name="password">root</property>
    </default-config>


    <!-- 命名的配置,可以通过方法调用实现 -->
    <named-config name="test">
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/student?allowPublicKeyRetrieval=true&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <!-- 如果池中数据连接不够时一次增长多少个 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化数据库连接池时连接的数量 -->
        <property name="initialPoolSize">20</property>
        <!-- 数据库连接池中的最大的数据库连接数 -->
        <property name="maxPoolSize">25</property>
        <!-- 数据库连接池中的最小的数据库连接数 -->
        <property name="minPoolSize">5</property>
    </named-config>
</c3p0-config>

4、编写工具

package datasource.c3p0;

import com.mchange.v2.c3p0.ComboPooledDataSource;

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

public class C3P0Util {
    /**
     * 加载配置,假如不写就是默认的
     */
    private static DataSource dataSource = new ComboPooledDataSource();

    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }

    /**
     * 释放连接回连接池
     */
    public static void close(Connection conn, PreparedStatement pst, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pst != null) {
            try {
                pst.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

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

5、测试连接

package datasource.c3p0;

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

public class HelloC3P0 {
    public static void main(String[] args) {

        Connection connection = C3P0Util.getConnection();
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            preparedStatement = connection.prepareStatement("select * from emp");
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getString("name"));
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            C3P0Util.close(connection, preparedStatement, resultSet);
        }
    }
}

JDBC - 图13


Druid

Druid其实和C3P0没有太大的区别,但是区别还是有的

1、导入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.22</version>
</dependency>

2、编写配置文件

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student?allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username=root
password=root
# 初始化连接数
initialSize=5
# 最大连接数
maxActive=10
# 超时时间
maxWait=3000

3、编写工具类

package datasource.druid;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class DruidUtil {

    private static DataSource dataSource = null;

    static {
        try {
            Properties properties = new Properties();


            ClassLoader classLoader = DruidUtil.class.getClassLoader();

            InputStream resourceAsStream = classLoader.getResourceAsStream("druid.properties");

            properties.load(resourceAsStream);

            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {


        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

        return connection;
    }

    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

4、实现

package datasource.druid;

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

public class HelloDruid {
    public static void main(String[] args) {
        Connection connection = DruidUtil.getConnection();

        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            preparedStatement = connection.prepareStatement("select * from emp");
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getString("name"));
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DruidUtil.close(connection, preparedStatement, resultSet);
        }
    }
}

5、测试

JDBC - 图14