手记

JDBC基础(一)

1. JDBC

1.1 什么是JDBC

今天我来介绍下JDBC. JDBC英文全程Java Database Connectivity.是一个独立于特定DBMS(数据库管理系统), 通用的SQL数据库存储和操作的公共接口集合。该接口定义在java.sqljavax.sql下。
说白了JDBC是一套接口, 规定了访问数据库的规范和标准

1.2 JDBC的好处

  • 统一数据库访问途径, 方便开发和维护.JDBC为访问不同数据库提供了统一的途径.屏蔽了不同数据库系统之间的差异.

  • 解耦数据库访问, 方便扩展支持不同数据库。使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统, 具有更好的扩展性, 兼容性。这些驱动由数据库厂商根据JDBC接口约定的规范进行开发.这就像java中的子类实现接口, 这其实是一种面向接口编程的思想.我们使用的时候, 不去关心子类的实现, 屏蔽掉了子类实现细节, 直接new一个子类赋值个接口引用即可引用.

JDBC

1.3 JDBC体系结构

JDBC接口包括两个层次

  • 面向应用的API
    Java API, 抽象接口,供应用程序开发人员使用。

  • 面向数据库的API
    Java Driver API, 供开发商开发数据库驱动用。

1.4 JDBC驱动程序的分类

  • 第一类: JDBC-ODBC, Windows实现的为方便

  • 第二类: 部分本地API部分Java的驱动程序

第二类

  • 第三类: JDBC网络纯Java驱动程序

第三类

  • 这种驱动采用中间件的应用服务器来访问数据库。应用服务器作为一个到多个数据库的网关,客户端通过它间接联接数据库。

  • 应用服务器通常有自己的网络协议.Java程序通过JDBC驱动程序将JDBC调用发送给应用服务器, 应用服务器使用本地程序驱动访问数据, 从而完成请求。
    例子:举个例子, 比如阿里云提供了访问数据库服务器的接口, 这就是阿里云厂商提供的本地API, 而应用服务器者是买了阿里云的第三方自己去订制开发, 根据需求可以自己定义一套通信协议,并实现JDBC驱动来间接的调用阿里云

  • 第四类: 本地协议的存Java驱动协议

  • 数据库厂商已经提供了网络协议, 用来约定客户端程序通过网络直接与数据库通信

  • 这类驱动完全使用Java编写, 通过与数据库建立的Socket连接, 采用具体的网络协议, 把JDBC调用转换为直接连接的网络调用

    第四类

第三和第四种都是走的是网络.
不同的是前者是通过数据库网关间接和数据库通信, 而不同的数据库网关可能有不同的协议, 后者是直接和数据库建立网络连接, 进行远程接口调用。
第二种走的是数据库本地API, 相当于提供头文件和库直接给你调用.扩展性不好,对于不同的语言就需要提供另外一套的接口,没有走网络协议的兼容性好
第一种的Windows提供的已经淘汰忽略.

1.5 JDBC的API构架

JDBC API是一套接口集合.约定了应用程序能够进行数据库联接、执行SQL语句、并且得到返回结果。

JDBC的API构架

1.5(0x00) Driver 接口

  • java.sql.Driver接口是所有JDBC驱动程序要实现的接口。这个接口提供给数据库厂商使用,让数据库厂商根据自己的技术实现这些接口的规范。

  • 在程序中不需要直接访问实现了Driver接口的类。而是由驱动管理器去调用这些Driver实现

常用的数据库驱动

  • Oracle驱动: oracle.jdbc.driver.OracleDriver

  • Mysql驱动: com.mysql.jdbc.Driver

1.6 JDBC操作

1.6(0x00) JDBC操作步骤

  • 加载驱动并注册

  • 获取连接对象

  • 获取预编译对象(代码模板更易读懂更高效(mysql不支持)、防止SQL注入)

  • 执行SQL语句

  • 释放资源(连接对象、预编译对象、结果集)(有顺序要求,注意异常处理的正确顺序)

1.6(0x01) 具体步骤

引入驱动包,这里我使用Maven引入

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

加载和注册驱动

一、通过Class.forName

通过反射直接使用Class.forName方式进行加载和注册.

Class.forName("com.mysql.jdbc.Driver");
原理

通过Class.forName直所以能够加载注册驱动, 是因为Mysql驱动的实现类,使用了静态代码块, 并在静态代码块中使用驱动管理器(DriverMangaer)进行驱动的注册。如下是Mysql驱动实现的代码

package com.mysql.jdbc;import java.sql.DriverManager;import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {    public Driver() throws SQLException {
    }    static {        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {            throw new RuntimeException("Can't register driver!");
        }
    }
}
二、通过DriverManager自动进行加载和注册.

在JDK6.0, JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了, 我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载.

  • 只要一调用这句代码, 就会加载DriverManager,其会去classpath下面扫描数据库驱动, 并自动进行加载和注册.

    public static Connection getConn() throws ClassNotFoundException, SQLException {
        Connection conn = null;
        String url = config.getProperty(JDBCUtil.URL_KEY);
        conn = DriverManager.getConnection(
                    url,
                    config);        return conn;
    }
原理
注意
  • 虽然可以这样做, 但是在Web开发中这种方式隐式加载驱动方式不被认可, 我们还是要手动的使用Class.forName来加载驱动

  • 主要原因在于Web开发过程中, 我们需要将数据库做成配置文件, 方便部署和维护, 而这时候需要我们在配置文件中显示的指定需要加载的数据库驱动全限定类名.所以不用这种自动加载方式。

Web开发中我们还需要加入Class.forName.

    public static Connection getConn() throws ClassNotFoundException, SQLException {
        Connection conn = null;
        String url = config.getProperty(JDBCUtil.URL_KEY);
        Class.forName(config.getProperty(JDBCUtil.DRIVE_KEY));
        conn = DriverManager.getConnection(
                    url,
                    config);        return conn;
    }

建立连接

一旦加载完驱动, 我们就可以通过驱动和数据库服务器建立远程通信联接, 并获取到该联接对象, 使用如下代码.也可以使用Class.forName的方式,通过反射, 创建Driver对象,并进行注册

    public static Connection getConn() throws ClassNotFoundException, SQLException {
        Connection conn = null;
        String url = config.getProperty(JDBCUtil.URL_KEY);
        Class.forName(config.getProperty(JDBCUtil.DRIVE_KEY));
        conn = DriverManager.getConnection(
                    url,
                    config);        return conn;
    }

获取执行体对象或其子对象

我们可以通过联接对象,来获取执行体对象.通过执行体对象来执行sql语句.一般我们都用预加载执行体对象PrepareStatement, 而不不用其父类Statement对象.主要原因有以下两个。

  • 安全问题。Statement对象对于存在sql注入的,没有对sql语句进行严格的过滤.

  • 效率问题。PreparedStatement是预编译,对于支持预编译的数据库来说可以大大提高执行效率.

PreparedStatement创建步骤
  • 创建执行提对象,绑定好预编译sql。

  • 设置预编译sql中占位符所对应数据。

    @Test
    public void exer1() {        // 创建Customer表, 包含id, name, age, gender,birthday。插入数据

        Connection conn = null;
        PreparedStatement ps = null;
        StringBuilder sb = new StringBuilder();        try {
            conn = JDBCUtil.getConn();

            sb.append("create table if not exists Customer (id int auto_increment, name varchar(20),").
                    append("age int, gender varchar(3) not null,").
                    append("birthdate date,").
                    append("primary key(id)").append(") engine innodb charset utf8");

            System.out.println(sb);
            PreparedStatement preparedStatement = conn.prepareStatement(sb.toString());
            preparedStatement.execute();

            sb = new StringBuilder();
            sb.append(" insert into Customer (name, age, gender, birthdate) ");
            sb.append(" values (?,?,?,?) ");
            preparedStatement = conn.prepareStatement(sb.toString());

            preparedStatement.setObject(1, "SweetCS");
            preparedStatement.setObject(2, 26);
            preparedStatement.setObject(3, "男");
            preparedStatement.setObject(4,  LocalDate.of(1992,1,1).toString());
            System.out.println(preparedStatement);
            preparedStatement.execute();



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

执行查询

调用执行体对象的executeQuery()方法或者executeUpdate()执行sql。

释放资源

  • 由于我们是通过网络联接和数据库进行通信,这是属于系统调用, 系统调用JVM并没有权限回收资源, 所以我们需要自己关闭网络资源.

  • 既然要关闭资源,在finally语句块中进行关闭是最合适

        Connection conn = null;
        String url = "jdbc:mysql://127.0.0.1:3306/jdbc";        try {
            conn = DriverManager.getConnection(url, "root", "admin");
            System.out.println(conn);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {            if (null != conn) {                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }

JDBCUtil封装(版本一)

只要思路在于,方便后续进行数据库联接。如果我们使用JDBC除了每次肯定要进行的操作主要有四步.

  • 数据库驱动的加载

  • 数据库连接的建立

  • 数据库查询语句的执行

  • 资源的释放

除了第三点之外, 其他三个可以说是完全一样的操作,对于重复代码我们要进行封装.

import java.io.IOException;import java.sql.*;import java.util.Properties;public class JDBCUtil {    static Properties config = null;    private static final String URL_KEY = "url";    private static final String DRIVER_CLASS_NAME_KEY = "driverClassName";    private static String configFileName = "config.properties";    public static String getConfigFileName() {        return configFileName;
    }    public static void setConfigFileName(String configFileName) {
        JDBCUtil.configFileName = configFileName;
    }    static {
        config = new Properties();        try {
            config.load(JDBCTest.class.getResourceAsStream(configFileName));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }    public static Connection getConn() throws ClassNotFoundException, SQLException {
        Class.forName(config.getProperty(DRIVER_CLASS_NAME_KEY));
        String url = config.getProperty(JDBCUtil.URL_KEY);
        Connection conn = DriverManager.getConnection(
                    url,
                    config);        return conn;
    }    public static void close(Connection conn) {        if (null != conn) {            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }    public static void close(Connection conn, Statement stmt) {        try {            if (null != stmt) {
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(conn);
        }
    }    public static void close(Connection conn, Statement stmt, ResultSet rs) {        try {            if (null != rs) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(conn, stmt);
        }

    }

}
  • 释放顺序问题。需要注意的是执行体对象需要在连接对象之前先释放。

  • 字符串复用问题。字符串最好提取出来方便复用。

  • 配置文件字段名规范问题。该工具类对应config.properties的数据库配置文件

config.properties



作者:sixleaves
链接:https://www.jianshu.com/p/a509cb1ffafc
x


1人推荐
随时随地看视频
慕课网APP