我所了解的JDBC

本文内容

  1. 首先介绍了JDBC的意义:Java提供的一套操作数据库的接口规范。
  2. 然后分三部分介绍了JDBC使用的方法:注册驱动、获得连接、执行SQL语句并获得结果。
  3. 注册驱动部分,介绍了Driver、DriverManager接口,并介绍了JDBC 4之后利用 Java SPI 自动注册驱动的原理。
  4. 获得连接部分,介绍了Connection、DataSource接口,并阐述了连接的一些注意点,以及为什么要使用连接池。
  5. 执行SQL语句部分,介绍了Statement、PreparedStatement、ResultSet接口提供给操作数据库的能力。

一张图

image-20220115135526467.png

JDBC是什么?

我们都用过数据库,你也可能自己使用命令行连接过某个数据库,以MySQL为例:

  1. 首先需要有一个MySQL客户端,使用MySQL的服务器地址、端口、用户名、密码去进行连接
  2. 连接上之后,执行需要执行的SQL语句,MySQL会返回对应的数据

为了使用Java语言进行上述操作,java.sql 包定义了一套标准接口,即 JDBC 规范。各个数据库方(不限于MySQL)均实现这套规范,用户引入自己想连接的数据库的规范实现,通过 Java 的这一套JDBC接口,实现连接数据库,执行SQL语句操作数据库的需求,而不用关心不同的数据库厂商对应的不同连接细节。

JDBC使用的步骤包括三个部分,注册驱动获得连接执行SQL语句并获得结果;我们将从这三个部分介绍。

注册驱动

注册驱动的目的

驱动(Driver)指的是java.sql包下的Driver接口,定义了两个比较重要的方法:

// 用于获取数据库连接
Connection connect(String url, java.util.Properties info) throws SQLException;
// 用于判断所配置的要连接的数据库url是否被该驱动的实现支持
boolean acceptsURL(String url) throws SQLException;
复制代码

我们在使用JDBC连接数据库时,通常会配置一个JDBC连接的Properties,中间必然包含一个url的参数:

url=jdbc:mysql://localhost:3306/db_name
复制代码

Driver的acceptsURL方法会判断配置跌url是否被该驱动支持,connect方法会使用该url去连接数据库。

JDBC接口中有一个关键的DriverManager类,后面我们会直接使用它来获取数据库连接Connection,顾名思义,DriverManager用于管理驱动Driver,而注册驱动的目的就是将我们要使用的JDBC实现(数据库厂商提供的包,如mysql-connector-java jar包)的Driver实现注册到DriverManager中,使用户(Java应用代码编写者)在获取数据库连接时,直接使用JDBC中的接口,而不用关心不同数据库的不同实现。

SPI自动注册驱动

很多老旧的代码或博客还会使用如下方式进行驱动的注册:

Class.forName("com.mysql.cj.jdbc.Driver")  // 加载Dirver类,Driver类的初始化时static块会将自己注册到DriverManager
复制代码

其实JDBC4.0(对应JDK 1.6)之后,DriverManager已经支持使用SPI实现自动注册驱动。

SPI是Java提供的一套被第三方实现或者扩展的接口(调用方根据需求选择第三方实现),用户可以在项目META-INF/services目录下指定要加载的接口实现

可以看到mysql-connector-java jar包的META-INF/services目录下指定了Driver的实现类

image-20220113224711702.png

很容易在DriverManager类中找到,DriverManager在类的初始化过程中,static块里,进行了Driver的获取

DriverManager 将加载数据库连接包的 jdbc 的 Driver 的实现;注,jdbc4 后不用显式注册,在使用 DriverManager 获得连接时将自动检测加载驱动;

// Driver.loadInitialDrivers
AccessController.doPrivileged(new PrivilegedAction<Void>() {
  // 省略了一些代码
  public Void run() {
    // SPI加载Driver类
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  }
});
复制代码

所以,注册驱动的事情,Java SPI和mysql-connector-java驱动包已经帮我们自动做了,不需要应用代码注册。

获得连接

前面提到,Driver的一个重要方法connect,调用后返回Connection数据库连接,拿到连接,相当于我们在命令行模式下使用mysql -u -p 连接MySQL服务器成功后的效果。

关于获取连接,需要注意的是:

  1. 获得数据库连接是一个比较重的操作,需要进行应用与数据库服务器建立TCP连接的的三次握手、数据库校验用户名密码数据库服务器分配资源给这个连接等等操作;
  2. 数据库连接Connection对象不管对于应用(如MySQL客户端),还是数据库服务端(如MySQL服务端),都是一种资源占用,需要显式地进行释放,且不能大量创建
  3. 同一个数据库连接可以用于执行多条SQL语句,且可以连续执行多个事务。

基于以上几点,可以得出,数据库连接可以并且应该被复用,现在的实际应用中,对于数据库连接的管理,通常会使用连接池,如常用的HikariDataSource,实现了java.sql.DataSource,应用定义好关于数据库的连接信息、用户名、密码、连接池大小、连接超时时间等侯,可以用HikariDataSource直接获取连接,它会负责数据库连接的创建、释放等生命周期管理。

DataSource是JDBC的一个接口,意思是数据源,简单定义了两个获取Connection的方法,可以用于替代DriverManager获取连接的写法;被大家实现用来做连接池等

执行SQL语句并获得结果

获取到数据库连接之后,下一步我们要使用Connection去执行SQL语句完成数据库操作了,所以看看JDBC定义的Connection接口里面定义的有什么方法:

// java.sql.Connection

// 在这个数据库连接上创建一个Statement
Statement createStatement() throws SQLException;

// 预编译一条SQL语句,PreparedStatement接口继承Statement
PreparedStatement prepareStatement(String sql) throws SQLException;
复制代码

引出了另一个执行SQL语句并获得结果比较核心的两个JDBC接口:Statement、PreparedStatement,后者继承了前者。

Statement用于执行SQL语句的直接字符串。

PreparedStatement相比Statement增加了两个重要点:

  1. 支持预编译SQL
  2. 支持参数占位符,避免了直接拼接SQL语句可能会导致的SQL注入问题

预编译是一种数据库支持的语法,MySQL即支持,好处是对于重复执行一条SQL语句但参数不同时,使用预编译可以做到语法、词法解析只执行一次,性能会有所提高

先看下Statement接口提供的方法:

// java.sql.Statement

// 用于执行SELECT类型的SQL语句,返回结果当然在ResultSet里啦
ResultSet executeQuery(String sql) throws SQLException;
// 用于执行INSERT、UPDATE、DELETED类型的SQL语句
int executeUpdate(String sql) throws SQLException;
// 用于获取语句执行自动生成的东西,如自增主键,插入返回的主键
ResultSet getGeneratedKeys() throws SQLException;
复制代码

PreparedStatement接口因为需要支持预编译,增加了设置参数相关的方法:

// java.sql.PreparedStatement

// 执行查询,因为SQL语句已经预编译,不需要传入SQL
ResultSet executeQuery() throws SQLException;
// 设置参数,根据预编译时在SQL语句中的位置索引,省略其它类型的setXXX()
void setInt(int parameterIndex, int x) throws SQLException;
void setString(int parameterIndex, String x) throws SQLException;
复制代码

获得结果

执行SQL语句后,或者返回或者通过Statement.getResultSet,都可以拿到一个ResultSet,从ResultSet中我们可以获取到语句返回的内容:

// 例,执行查询语句返回结果的遍历
ResultSet resultSet = statement.executeQuery("select * from user limit 10");

while (resultSet.next()){
 formatter.format(format,resultSet.getInt("id"),resultSet.getString("name"),resultSet.getInt("age"),simpleDateFormat.format(resultSet.getDate("birth")));
  System.out.println();
}

// insert语句返回结果处理
try(
  PreparedStatement preparedStatement = connection.prepareStatement("insert into user(name,age,birth) values(?,?,?)", Statement.RETURN_GENERATED_KEYS)
){
  preparedStatement.setString(1, name);
  preparedStatement.setInt(2, age);
  preparedStatement.setDate(3, new Date(birth.getTime()));
  preparedStatement.execute();
  ResultSet resultSet = preparedStatement.getGeneratedKeys();
  if(resultSet.next()){
    // 这里获取到mysql自增主键
    return resultSet.getInt(1);
  }else{
    throw new RuntimeException("插入失败");
  }
}
复制代码

后续

后面会再写一篇更接近我们平常使用的Mybatis相关的博客,看看它是怎么使用JDBC的,为我们的日常开发带来的便利。

猜你喜欢

转载自juejin.im/post/7053308720473702431