Java JDBC 数据库连接

前言

1、JDBC

  • JDBC(Java Data Base Connection)是通过 Java 访问数据库。

  • 访问 MySQL 数据库需要用到第三方的类,这些第三方的类,都被压缩在一个叫做 Jar 的文件里。

  • 为了代码能够使用第三方的类,需要为项目导入 MySQL 的专用 Jar 包。

1.1 使用 JDBC

  • 初始化驱动

    • 通过 Class.forName("com.mysql.jdbc.Driver"); 初始化驱动类 com.mysql.jdbc.Driver
    • Class.forName 是把这个类加载到 JVM 中,加载的时候,就会执行其中的静态初始化块,完成驱动的初始化的相关工作。

      1
      2
      3
      4
      5
      try {
      Class.forName("com.mysql.jdbc.Driver"); // 初始化驱动
      } catch (ClassNotFoundException e) {
      e.printStackTrace();
      }
  • 建立与数据库的连接

    • 建立与数据库的 Connection 连接。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;

      String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8";

      try {
      Class.forName("com.mysql.jdbc.Driver");

      Connection connection = DriverManager // 建立与数据库的 Connection 连接
      .getConnection(
      url, // 数据库 ip,端口,名称,编码方式
      "root", // 账号
      "admin"); // 密码
      } catch (ClassNotFoundException e) {
      e.printStackTrace();
      } catch (SQLException e) {
      e.printStackTrace();
      }
  • 创建 Statement

    • Statement 是用于执行 SQL 语句的,比如增加,删除。

      1
      2
      3
      import java.sql.Statement;

      Statement statement = connection.createStatement(); // 获取 Statement 对象
  • 执行 SQL 语句

    • execute 执行 sql 语句。

      1
      2
      3
      String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";

      statement.execute(sql); // 执行 SQL 语句
  • 关闭连接

    • 数据库的连接是有限资源,相关操作结束后,养成关闭数据库的好习惯。
    • 先关闭 Statement,后关闭 Connection。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      finally {
      if (statement != null)
      try {
      statement.close(); // 先关闭 Statement
      } catch (SQLException e) {
      e.printStackTrace();
      }

      if (connection != null)
      try {
      connection.close(); // 后关闭 Connection
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }
    • 使用 try-with-resource 的方式自动关闭连接

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.sql.Statement;

      try {
      Class.forName("com.mysql.jdbc.Driver"); // 初始化驱动
      } catch (ClassNotFoundException e) {
      e.printStackTrace();
      }

      String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8";

      try (
      Connection connection = DriverManager.getConnection(url, "root", "admin"); // 建立与数据库的 Connection 连接
      Statement statement = connection.createStatement(); // 获取 Statement 对象
      ) {
      String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";
      statement.execute(sql); // 执行 SQL 语句
      } catch (SQLException e) {
      e.printStackTrace();
      }

1.2 数据操作 CRUD

  • CRUD 是最常见的数据库操作,即增删改查。

    • C 增加(Create)
    • R 查询(Retrieve)
    • U 更新(Update)
    • D 删除(Delete)
  • 在 JDBC 中增加,删除,修改的操作都很类似,只是传递不同的 SQL 语句就行了。

    • execute 执行 SQL 增加,删除,修改语句。
  • 在 JDBC 中执行查询的 SQL 语句,有以下两种。查询结果 ResultSet 是 基 1 的,即 2 就代表第二个。

    • executeQuery 执行 SQL 查询语句。
    • execute 也可以执行查询语句。
  • 增加

    1
    2
    String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";
    statement.execute(sql);
  • 删除

    1
    2
    String sql = "delete from hero where id = 5";
    statement.execute(sql);
  • 修改

    1
    2
    String sql = "update hero set name = 'name 5' where id = 3";
    statement.execute(sql);
  • 查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    String sql = "select * from hero";
    ResultSet rs = statement.executeQuery(sql); // 执行查询语句,并把结果集返回给 ResultSet

    while (rs.next()) { // 如果有数据
    int id = rs.getInt("id"); // 可以使用字段名
    String name = rs.getString(2); // 也可以使用字段的顺序,ResultSet 是基 1 的
    float hp = rs.getFloat("hp");
    int damage = rs.getInt(4);
    }
  • execute 与 executeUpdate 的区别

    • 相同点

      • 都可以执行增加,删除,修改
    • 不同 1:

      • execute 可以执行查询语句,然后通过 getResultSet,把结果集取出来。
      • executeUpdate 不能执行查询语句。
    • 不同 2:

      • execute 返回 boolean 类型,true 表示执行的是 select 语句,false表示执行的是 insert, delete, update 等等。
      • executeUpdate 返回的是 int,表示有多少条数据受到了影响。

2、预编译

  • 和 Statement 一样,PreparedStatement 也是用来执行 sql 语句的。

    • PreparedStatement 使用参数设置,可读性好,不易犯错。
    • PreparedStatement 有预编译机制,性能比 Statement 更快。
    • PreparedStatement 使用的是参数设置,能够防止 SQL 注入式攻击
  • PreparedStatement 参数设置位置是 基 1 的

    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
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;

    try {
    Class.forName("com.mysql.jdbc.Driver"); // 初始化驱动
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }

    String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8";
    String sql = "insert into hero values(null, ?, ? , ?)";

    try (
    Connection connection = DriverManager.getConnection(url, "root", "admin"); // 建立与数据库的 Connection 连接
    PreparedStatement ps = connection.prepareStatement(sql); // 根据 sql 语句创建 PreparedStatement
    ) {
    ps.setString(1, "提莫"); // 设置参数,PreparedStatement 是基 1 的
    ps.setFloat(2, 313.0f);
    ps.setInt(3, 50);

    ps.execute(); // 执行 SQL 语句

    } catch (SQLException e) {
    e.printStackTrace();
    }

3、特殊操作

  • 获取自增长 id

    • 在 Statement 通过 execute 或者 executeUpdate 执行完插入语句后,MySQL 会为新插入的数据分配一个自增长 id。
    • 但是无论是 execute 还是 executeUpdate 都不会返回这个自增长 id 是多少。需要通过 Statement 的 getGeneratedKeys 获取该 id。
    • 代码后面加了个 Statement.RETURN_GENERATED_KEYS 参数,以确保会返回自增长 id。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      String sql = "insert into hero values(null, ?, ?, ?)";

      PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // Statement.RETURN_GENERATED_KEYS 确保会返回自增长 id

      ps.setString(1, "盖伦");
      ps.setFloat(2, 616);
      ps.setInt(3, 100);

      ps.execute();

      // 在执行完插入语句后,MySQL 会为新插入的数据分配一个自增长 id,JDBC 通过 getGeneratedKeys 获取该 id
      ResultSet rs = ps.getGeneratedKeys();
      if (rs.next()) {
      int id = rs.getInt(1);
      }
  • 获取表的元数据

    • 和数据库服务器相关的数据,比如数据库版本,有哪些表,表有哪些字段,字段类型是什么等等。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      DatabaseMetaData dbmd = connection.getMetaData();          // 查看数据库层面的元数据

      String pName = dbmd.getDatabaseProductName()); // 获取数据库服务器 产品名称
      String pVersion = dbmd.getDatabaseProductVersion()); // 获取数据库服务器 产品版本号
      String separator = dbmd.getCatalogSeparator()); // 获取数据库服务器 用作类别和表名之间的分隔符 如 test.user

      String dVersion = dbmd.getDriverVersion()); // 获取 驱动版本

      ResultSet rs = dbmd.getCatalogs(); // 获取 数据库名称
      while (rs.next()) {
      String dbName = rs.getString(1));
      }

4、事务

  • 在 Mysql 中,只有当表的类型是 INNODB 的时候,才支持事务

    1
    2
    3
    4
    5
    # 修改表的类型为 INNODB
    > alter table hero ENGINE = innodb;

    # 查看表的类型
    > show table status from how2java;
  • 有事务的前提下,在事务中的多个操作,要么都成功,要么都失败。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8";

    try (Connection connection = DriverManager.getConnection(url, "root", "admin");
    Statement statement = connection.createStatement();) {

    connection.setAutoCommit(false); // 关闭自动提交,在之间的数据库操作,就处于同一个事务当中

    String sql1 = "update hero set hp = hp + 1 where id = 22";
    statement.execute(sql1);

    connection.commit(); // 手动提交,在之间的数据库操作,就处于同一个事务当中

    } catch (SQLException e) {
    e.printStackTrace();
    }

5、ORM

  • ORM(Object Relationship Database Mapping)对象和关系数据库的映射。

  • 简单说,一个对象,对应数据库里的一条记录。

    1
    2
    3
    4
    5
    6
    public class Hero {
    public int id;
    public String name;
    public float hp;
    public int damage;
    }
    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
    public static Hero get(int id) {
    Hero hero = null;
    try {
    Class.forName("com.mysql.jdbc.Driver");
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }

    String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8";
    try (Connection connection = DriverManager.getConnection(url, "root", "admin");
    Statement statement = connection.createStatement();) {
    String sql = "select * from hero where id = " + id;
    ResultSet rs = statement.executeQuery(sql);
    if (rs.next()) { // 因为 id 是唯一的,ResultSet 最多只能有一条记录,所以使用 if 代替 while
    String name = rs.getString(2);
    float hp = rs.getFloat("hp");
    int damage = rs.getInt(4);

    hero = new Hero();
    hero.id = id;
    hero.name = name;
    hero.hp = hp;
    hero.damage = damage;
    }
    } catch (SQLException e) {
    e.printStackTrace();
    }
    return hero;
    }

    public static void main(String[] args) {

    Hero h = get(22); // 提供方法 get(int id) 返回一个 Hero 对象
    System.out.println(h.name);
    }
  • 常见的 ORM 方法

    1
    2
    3
    4
    5
    6
    7
    public static void add(Hero h)                       // 把一个 Hero 对象插入到数据库中

    public static Hero get(int id) // 返回一个 Hero 对象

    public static void delete(Hero h) // 删除掉一个 Hero 对象

    public static void update(Hero h) // 更新一个 Hero 对象
  • Hibernate 是 对 JDBC 的轻量级封装,使得开发人员可以像操作对象操作数据库。

6、DAO

  • DAO(Data Access Object)数据访问对象。

  • 把数据库相关的操作都封装在类里面,其他地方看不到 JDBC 的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import java.util.List;
    import charactor.Hero;

    public interface heroDAO {

    public void add(Hero hero);
    public void update(Hero hero);
    public void delete(int id);
    public Hero get(int id);

    public List<Hero> list();
    public List<Hero> list(int start, int count);
    }
  • MyBatis 是一款优秀的持久层框架,前身是(ibatis),它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

7、数据库连接池

  • 与线程池类似的,数据库也有一个数据库连接池。不过他们的实现思路是不一样的。

    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
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;

    public class ConnectionPool {

    List<Connection> cs = new ArrayList<Connection>();

    int size;

    public ConnectionPool(int size) {
    this.size = size;
    init();
    }

    public void init() {

    // 这里恰恰不能使用 try-with-resource 的方式,因为这些连接都需要是"活"的,不要被自动关闭了
    try {
    Class.forName("com.mysql.jdbc.Driver");
    for (int i = 0; i < size; i++) {
    Connection c = DriverManager
    .getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "admin");

    cs.add(c);
    }
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }

    public synchronized Connection getConnection() {
    while (cs.isEmpty()) {
    try {
    this.wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    Connection c = cs.remove(0);
    return c;
    }

    public synchronized void returnConnection(Connection c) {
    cs.add(c);
    this.notifyAll();
    }
    }
  • 数据库连接池的工作机制(原理)

    • 因为创建连接和关闭连接的行为是非常耗时的,会显著降低软件的性能表现。
    • 解决办法就是先创建 n 条数据库连接 Connection,循环使用,但是不进行关闭。
    • 这样再执行 SQL 语句,就不需要额外创建连接了,直接使用现成的连接就可以了,从而节约了创建连接和关闭连接的时间开销。
文章目录
  1. 1. 前言
  2. 2. 1、JDBC
    1. 2.1. 1.1 使用 JDBC
    2. 2.2. 1.2 数据操作 CRUD
  3. 3. 2、预编译
  4. 4. 3、特殊操作
  5. 5. 4、事务
  6. 6. 5、ORM
  7. 7. 6、DAO
  8. 8. 7、数据库连接池
隐藏目录