简介

  • jdbc就是java标准库定义的一组接口,而不同数据库的jdbc驱动包就是接口的实现。

    • Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;(因此jdbc驱动包是运行时需要)
      • 改为空间为编译时虽然不报错,但是会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆

        第一个JDBC

        依赖

        1. <dependency>
        2. <groupId>mysql</groupId>
        3. <artifactId>mysql-connector-java</artifactId>
        4. <version>5.1.47</version>
        5. <scope>runtime</scope>
        6. </dependency>

        获取连接

        // JDBC连接的URL, 不同数据库有不同的格式。URL是由数据库厂商指定的格式
        String JDBC_URL = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8";
        String JDBC_USER = "root";
        String JDBC_PASSWORD = "password";
        //DriverManager会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。
        Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
        

        获取Statement执行sql并获取结果

  • Statement对象,该对象用于执行sql相关操作。

  • PreparedStatement可以防止sql注入,它执行一个不包含参数的sql,对sql进行预编译,再把参数传到编译好的sql中。被预编译的sql不会再变化
    • 注意不要把拼接参数得到的sql传给PreparedStatement,这样不能起到防止sql注入
    • 我们应当尽可能使用**PreparedStatement**而不是**Statement**
  • 于查询操作,调用**executeQuery**执行sql,返回的是类似**List<List<Object>>**的结果集**ResultSet**。对于增删改,调用**executeUpdate**,返回的是**int**

    try (Statement stmt = conn.createStatement()) {
      try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) {
    //rs.next()用于判断是否有下一行记录,一开始获得ResultSet时当前行不是第一行
          //循环行记录
          while (rs.next()) { 
          //循环列记录,注意输出的字段顺序跟获取的类型要对应。另外列索引从1开始
              long id = rs.getLong(1); 
              long grade = rs.getLong(2);
              String name = rs.getString(3);
              int gender = rs.getInt(4);
          }
      }
    }
    }
    
    PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")
    ps.setObject(1, "M"); // 注意:索引从1开始
    ps.setObject(2, 3);
    ResultSet rs = ps.executeQuery()
    

    关闭资源

  • Connection``Statment``ResultSet都是需要关闭的资源,可以使用try(resource)自动关闭

    JDBC事务

  • 我们首先需要关闭自动提交,执行多条命令后手动提交即是jdbc事务 ```java Connection conn =…; //该方法可以知道当前是否开启了自动提交 boolean isAutoCommit = conn.getAutoCommit(); //可以设置事务隔离级别,不设置则采用数据库默认的事务级别 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); conn.setAutoCommit(false); // 关闭自动提交事务 PreparedStatement ps =…; int n=ps.executeUpdate(); if(n>1){ conn.rollback(); //回滚 } else { conn.commit(); //提交 } conn.setAutoCommit(true); //恢复自动提交

<a name="OoS8o"></a>
## JDBC Batch

- 批量操作的本质在于:**SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。**
   - **即Batch本质基于是数据库提供的优化**
```java
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
    // 对同一个PreparedStatement反复设置参数并调用addBatch():
    for (Student s : students) {
        ps.setString(1, s.name);
        ps.setBoolean(2, s.gender);
        ps.setInt(3, s.grade);
        ps.setInt(4, s.score);
        ps.addBatch(); // 添加到batch
    }
    // 执行batch:
    int[] ns = ps.executeBatch();
    for (int n : ns) {
        System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
    }
}

连接池

创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池


类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。

  • JDBC连接池有一个标准的接口javax.sql.DataSource,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,我们必须选择一个JDBC连接池的实现。
    1. datasource可以看成是数据源,它封装了数据连接的各种信息和连接流。
  • 数据库连接池必须实现**getConnection**方法

常用的JDBC连接池有:

  • HikariCP
  • C3P0
  • BoneCP
  • Druid

    HikariCP

  • HikariCP是目前使用最广泛的数据库连接池

    导入依赖

    <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.19</version>
          <scope>runtime</scope>
      </dependency>
      <dependency>
          <groupId>com.zaxxer</groupId>
          <artifactId>HikariCP</artifactId>
          <version>2.7.1</version>
      </dependency>
    

    创建连接池并获取Connection

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

class BookMapper{ public static DataSource createds() throws SQLException { HikariConfig config = new HikariConfig(); config.setJdbcUrl(“jdbc:mysql://localhost:3306/bookManagement”); 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); return ds; }

public static void main(String[] args) {
    try (Connection conn = createds().getConnection()) { // 在此获取连接
    Statement stmt=conn.createStatement();
    }
    catch (SQLException throwables) {
        throwables.printStackTrace();
    }
}

} ```