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
  • 乱码问题。如果需要插入中文文字,记得在配置文件中加入编码声明语句characterEncoding=utf8.否则会导致存储到数据库中乱码。

总结

Statement产生注入的演示和PreparedStatement的防护

PreparedStatement的预编译性能测试

Statement和PreparedStatement的区别

Class.forName为何能加载驱动

JDBC操作口诀

JDBC释放资源的异常处理顺序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容