Java学习之==>JDBC

一、概述

官方解释:

  JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的 Java API,可以为多种关系型数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序,同时,JDBC也是个商标名。

简单地说,JDBC 可做三件事:与数据库建立连接、发送 操作数据库的语句并处理结果。

理解:

  JDBC就是一套访问数据库的规范(或者说是一组接口),提供数据库的协议标准,各个数据库厂商根据这套规范提供一套访问自己数据库的驱动。

二、JDBC工作原理

从图中可以看到 JDBC 的几个重要组成要素。最顶层是我们自己编写的Java应用程序(Java Application),Java 应用程序可以使用集成在JDK中的 java.sql 和 javax.sql 包中的JDBC API来连接和操作数据库。下面我们依次介绍JDBC的组成要素:

1、JDBC API

  JDBC API 由Sun公司提供,提供了Java 应用程序与各种不同数据库交互的标准接口,如:Connection(连接)接口,Statement 接口,ResultSet(结果集)接口,PreparedStatement 接口等。开发者使用这些JDBC接口进行各种数据库操作。

  • DriverManager:此类管理数据库驱动程序列表。使用通信子协议将来自 Java 应用程序的连接请求与适当的数据库驱动程序匹配;
  • Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象;
  • Connection:该接口具有用于联系数据库的所有方法。连接对象通信上下文,即,与数据库的所有通信仅通过连接对象;
  • Statement:使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数;
  • ResultSet: 在使用 Statement 对象执行 SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据;
  • PreparedStatement:继承自 Statement 接口,主要用于执行预编译 SQL,除了具有 Statement 接口的所有功能,还另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数占位符的值;

2、JDBC Driver Manager

  JDBC Driver Manager(驱动程序管理器)由Sun公司提供,它是 JDBC 体系结构的支柱,负责管理各种不同的 JDBC 驱动,把 Java 应用程序连接到相应的 JDBC 驱动程序上,位于JDK的 java.sql 包中。

3、JDBC Driver

  JDBC Driver 由各个数据库厂商或第三方中间件厂商提供,负责连接各种不同的数据库。例如,上图中,访问 MySQL 和 Oracle 时需要不同的JDBC驱动,这些 JDBC 驱动都实现了JDBC API 中定义的各种接口。在开发Java应用程序时,我们只需正确加载JDBC驱动,正确调用 JDBC API,就可以进行数据库访问了。

三、JDBC 访问数据库的步骤

我们先来看一下下面这段代码:

public class App {
  private static final String URL = "jdbc:mysql://192.168.182.131:3306/cakes";
  private static final String USER_NAME = "root";
  private static final String PASSWD = "123456";

  public static void main(String[] args) throws Exception {

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

    // 2.建立连接
    Connection connection = DriverManager.getConnection(URL, USER_NAME, PASSWD);

    // 3.建立Statement
    Statement statement = connection.createStatement();

    // 4.执行sql
    boolean execResult = statement.execute("insert into user(`name`,`passwd`) values('木木','123456')");
    // statement.executeUpdate返回的是影响的行数
    int effectRows = statement.executeUpdate("insert into user(`name`,`passwd`) values('大叔','888888')");

    System.out.println("execResult = " + execResult);
    System.out.println("effectRows:" + effectRows);

    // 5.资源关闭
    statement.close();
    connection.close();
  }
}
JDBC 示例

执行结果如下:

以上红色字体为警告信息,意思是:com.mysql.jdbc.Driver 这个驱动已经被弃用了,建议使用 com.mysql.cj.jdbc.Driver 这个驱动,更改后再次运行则没有这个警告信息了。

所以标准代码如下:

public class App {
  private static final String URL = "jdbc:mysql://192.168.182.131:3306/cakes";
  private static final String USER_NAME = "root";
  private static final String PASSWD = "123456";

  public static void main(String[] args) throws Exception {

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

    // 2.建立连接
    Connection connection = DriverManager.getConnection(URL, USER_NAME, PASSWD);

    // 3.建立Statement
    Statement statement = connection.createStatement();

    // 4.执行sql
    boolean execResult = statement.execute("insert into user(`name`,`passwd`) values('木木','123456')");
    // statement.executeUpdate返回的是影响的行数
    int effectRows = statement.executeUpdate("insert into user(`name`,`passwd`) values('大叔','888888')");

    System.out.println("execResult = " + execResult);
    System.out.println("effectRows:" + effectRows);

    // 5.资源关闭
    statement.close();
    connection.close();
  }
}

同时,因为我本地的 Gradle 工程引入的 mysql-connector-java 包的版本是 8.0.16,所以加载驱动那一步可以省略不写(5.x.x版本之后)。

JDBC访问数据库基本步骤:

1、加载驱动程序

2、建立连接

3、创建 Statement 对象

4、执行 SQL 语句

5、处理返回结果

6、关闭资源,包括:ResultSet,Statement 和 Connection

四、Statement 和 PreparedStatement

我们先来看两段代码:

public static void test01() {

  Connection connection = null;
  Statement statement = null;

  try {
    connection = DriverManager.getConnection(URL, USER_NAME, PASSWD);

    statement = connection.createStatement();

    String sql = "insert into user(`name`,`passwd`) values('木木','123456')";

    int executeNum = statement.executeUpdate(sql);
    System.out.println(String.format("成功插入%d条数据",executeNum));

  } catch (SQLException se) {
    se.printStackTrace();
  }finally {
    try {
      if (null != statement) {
        statement.close();
      }
      if (null != connection) {
        connection.close();
      }
    } catch (SQLException se) {
      se.printStackTrace();
    }
  }
}
Statement
public static void test02() {

  Connection connection = null;
  PreparedStatement preparedStatement = null;

  try {
    connection = DriverManager.getConnection(URL, USER_NAME, PASSWD);

    String sql = "insert into user(`name`,`passwd`) values(?, ?)";

    preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setString(1,"jack");
    preparedStatement.setString(2,"112233");

    int executeNum = preparedStatement.executeUpdate();
    System.out.println(String.format("成功插入%d条数据",executeNum));

  } catch (SQLException se) {
    se.printStackTrace();
  }finally {
    try {
      if (null != preparedStatement) {
        preparedStatement.close();
      }
      if (null != connection) {
        connection.close();
      }
    } catch (SQLException se) {
      se.printStackTrace();
    }
  }
}
PreparedStatement

PreparedStatment 接口继承自 Statement 接口,PreparedStatment 执行的是预编译 SQL,使用占位符,创建 PreparedStatment 对象时就会把 SQL语句作为参数参入。

通常情况下,我们更建议使用 PreparedStatment,原因如下:

1、提高了代码的可读性和可维护性

虽然使用 PreparedStatement 来代替 Statement 会多几行代码,但避免了烦琐麻烦又容易出错的SQL语句拼接,提高了代码的可读性和可维护性。

2、提高了SQL语句执行的性能

创建 Statement 对象时不使用SQL语句做参数,不会解析和编译SQL语句,每次调用方法执行SQL语句时都要进行SQL语句解析和编译操作,即操作相同仅仅是数据不同。

创建 PreparedStatement 对象时使用SQL语句做参数,会解析和编译该SQL语句,也可以使用带占位符的SQL语句做参数,在通过 setXxx ()方法给占位符赋值后执行SQL语句时无须再解析和编译SQL语句,直接执行即可。多次执行相同操作可以大大提高性能。

3、提高了安全性

PreparedStatement 使用预编译语句,传入的任何数据都不会和已经预编译的SQL语句进拼接,避免了SQL注入攻击。

五、ResultSet

使用 Statement 或 PreparedStatement 对象执行 SQL 查询后,会返回 ResultSet 结果集,作为一个迭代器,我们一般使用 next() 方法来判断把光标挪向下一行。

public class UserDemo3 {

  private static final String URL = "jdbc:mysql://192.168.182.131:3306/cakes";
  private static final String USER_NAME = "root";
  private static final String PASSWD = "123456";


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

    try {
      connection = DriverManager.getConnection(URL, USER_NAME, PASSWD);
      String sql = "select * from user where name=?";

      preparedStatement = connection.prepareStatement(sql);
      preparedStatement.setString(1, "大叔");

      resultSet = preparedStatement.executeQuery();

      ArrayList<User> users = new ArrayList<>();

      while (resultSet.next()) {
        User user = User.of();
        user.setId(resultSet.getInt("id"));
        user.setName(resultSet.getString("name"));
        user.setPasswd(resultSet.getString("passwd"));

        users.add(user);
      }
      for (User user : users) {
        System.out.println(user);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        if (null != resultSet) {
          resultSet.close();
        }
        if (null != preparedStatement) {
          preparedStatement.close();
        }
        if (null != connection) {
          connection.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
}
ResultSet

六、事物操作

七、SQL 执行工具设计

下面我们使用 JDBC 简单设计一款 SQL 执行工具

public interface JdbcConsts {

  String URL = "jdbc:mysql://192.168.182.131:3306/cakes";

  String USER_NAME = "root";

  String PASSWD = "123456";

}
JdbcConsts 接口

JdbcConsts 接口中存放的是数据库连接配置。

public final class ConnectionHandler {

  private ConnectionHandler() {
  }

  public static ConnectionHandler of() {
    return new ConnectionHandler();
  }

  public Connection getConnection() {
    try {
      return DriverManager.getConnection(URL, USER_NAME, PASSWD);
    } catch (SQLException e) {
      throw new IllegalStateException(e);
    }
  }
}
ConnectionHandler 类

ConnectionHandler 类封装了数据库连接这个操作。

public abstract class AbstractDBHandler<T> {

  /**
   * @param sql SQL语句, update user set name=?,age=? where id=?
   * @param params [zhangsan,23,12]
   */
  public int modify(String sql, List<Object> params) {
    Connection connection = null;
    PreparedStatement statement = null;
    try {
      connection = ConnectionHandler.of().getConnection();

      // statement = connection.prepareStatement(sql);
      //
      // for (int i = 0; i < params.size(); i++) {
      //   statement.setObject(i + 1, params.get(i));
      // }
      statement = preparedStatement(sql,params,connection);

      int effectRows = statement.executeUpdate();

      System.out.println("effectRows = " + effectRows);

      return effectRows;
    } catch (SQLException e) {
      throw new IllegalStateException(e);
    } finally {
      release(null, statement, connection);
    }
  }

  public List<T> select(String sql, List<Object> params) {
    Connection connection = null;
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
      connection = ConnectionHandler.of().getConnection();

      // statement = connection.prepareStatement(sql);
      //
      // for (int i = 0; i < params.size(); i++) {
      //   statement.setObject(i + 1, params.get(i));
      // }
      statement = preparedStatement(sql,params,connection);

      resultSet = statement.executeQuery();

      List<T> result = new ArrayList<>();
      while (resultSet.next()) {
        // 抽象点,通过抽象方法来实现
        T t = mapping(resultSet);
        result.add(t);
      }
      return result;
    } catch (SQLException e) {
      throw new IllegalStateException(e);
    } finally {
      release(resultSet, statement, connection);
    }
  }

  private PreparedStatement preparedStatement(String sql,List<Object> params,
                                              Connection connection) throws SQLException {
    PreparedStatement statement = connection.prepareStatement(sql);
    for (int i = 0; i < params.size(); i++) {
      statement.setObject(i + 1, params.get(i));
    }
    return statement;
  }

  protected abstract T mapping(ResultSet resultSet) throws SQLException;

  private void release(ResultSet resultSet, Statement statement, Connection connection) {
    try {
      if (null != resultSet) {
        resultSet.close();
      }
      if (null != statement) {
        statement.close();
      }
      if (null != connection) {
        connection.close();
      }
    } catch (SQLException e) {
      throw new IllegalStateException(e);
    }
  }
}
AbstractDBHandler 抽象类

AbstractDBHandler 类中定了了 select() 和 modify() 两个方法,modify()用于执行增、删、改,select()用于执行查询。modify() 方法传入两个参数 sql 和 List<Object> params,具体执行的什么 SQL 和 该 SQL 应该传入什么值,全部在 Dao 中定义。select() 方法查询出的结果对于不同的表返回的是不一样的,所以,我们使用了一个抽象方法 mapping() 来抽象返回结果,具体我们查询的是哪张表,返回的是什么样的结果,全部在 Dao中定义,如下:

public class UserDao extends AbstractDBHandler<User> {

  @Override
  protected User mapping(ResultSet resultSet) throws SQLException {

    User user = User.of();
    user.setId(resultSet.getInt("id"));
    user.setName(resultSet.getString("name"));
    user.setPasswd(resultSet.getString("passwd"));

    return user;
  }

  public int addUser(User user) {
    String sql = "insert into user(`name`,`passwd`) values(?,?)";
    List<Object> params = new ArrayList<>();
    params.add(user.getName());
    params.add(user.getPasswd());
    return modify(sql, params);
  }

  public int delUser(User user) {
    String sql = "delete from user where name=? and passwd=?";
    List<Object> params = new ArrayList<>();
    params.add(user.getName());
    params.add(user.getPasswd());
    return modify(sql, params);
  }

  public List<User> queryByName(User user) {
    String sql = "select * from user where name=? and passwd=?";
    List<Object> params = new ArrayList<>();
    params.add(user.getName());
    params.add(user.getPasswd());
    return select(sql, params);
  }
}
UserDao

UserDao 继承自 AbstractDBHandler 类,当中定义了关于 User 表的 增、删、改、查等操作。其中,增、删、改调用的是 modify() 方法而查询调用的是 select() 方法。同时也定义了 User 表查询结果由三个字段组成:id,name,passwd。

public class App {

  @Test
  public void testInsert() {
    UserDao userDao = new UserDao();

    User user = User.of("小芹", "s123456");

    userDao.addUser(user);
  }

  @Test
  public void testDelete() {
    UserDao userDao = new UserDao();

    User user = User.of("jack", "123456");

    userDao.delUser(user);
  }

  @Test
  public void testSelect() {
    UserDao userDao = new UserDao();

    User user = User.of("大叔", "111222");

    List<User> users = userDao.queryByName(user);

    System.out.println(users);
  }
}

测试类中只需要传入执行 增、删、改、查具体的数据就可以。

以上,只是对针对 JDBC 的操作进行了简单的设计和封装,还有很多地方可以优化,这里就不再深入讲解。

posted on 2019-09-23 22:53  破解孤独  阅读(320)  评论(0编辑  收藏  举报

导航