JDBC深入

前言

  1. 大二上学期跟着学校里面的老师学JavaSE的时候写了一篇非常垃圾的JDBC的文章JDBC,那时候自认为自己对JDBC的认识已经很高了,认为它很简单,现在再回头看,发现自己那时候完全是井底之蛙,所以我觉得得再写一篇文章深入谈谈JDBC,算是给自己的黑历史抹白吧。
  2. 我之前写的那篇jdbc文章,使用statement操作sql语句会存在SQL注入问题,之后会再写一篇文件谈谈这个问题
  3. 鉴于之前写了关于MySQL事务机制的文章,之后,我也会谈谈JDBC事务机制的使用

JDBC本质

JDBC,本质上就是一套接口,是SUN公司制定的一套接口(interface),包名为:java.sql.*(这个软件包下有很多接口)

为什么面向接口编程

我们先来看看什么是面向接口编程:

接口都有调用者和实现者,面向接口调用、面向接口写实现类,这都属于面向接口编程。

为什么要面向接口编程?

  • 解耦合:
    • 降低程序的耦合度
    • 提高程序的扩展力。

多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)

思考

现在思考:为什么SUN公司制定一套JDBC接口呢?

  1. 因为每一个数据库的底层实现原理都不一样。
  2. Oracle数据库有自己的原理。
  3. MySQL数据库也有自己的原理。
  4. MS SqlServer数据库也有自己的原理。

每一个数据库产品都有自己独特的实现原理,如果我们不写一套接口,那么我们这些程序员对同一套Java程序,需要准备几套面向不同数据库的Java代码,程序员会累死在重复且无意义的工作上。

而面向接口编程可以实现一个规范,从而让我们java程序员写一套java代码,适配不同的数据库,达到解放程序员的效果

三方模拟JDBC本质

我们扮演SUN公司,Java程序员,数据库厂商这三方来模拟一下JDBC

SUN公司

/*
SUN公司负责制定这套JDBC接口。
*/
public interface JDBC{
    /*
        连接数据库的方法。
    */
    void getConnection();
}

数据库厂商

数据库厂商对SUN公司制定出的接口的实现类,我们称之为驱动(Driver)

MySQL

/*
    MySQL的数据库厂家负责编写JDBC接口的实现类
*/
public class MySQL implements JDBC{

    public void getConnection(){
        // 具体这里的代码怎么写,对于我们Java程序员来说没关系
        // 这段代码涉及到mysql底层数据库的实现原理。
        System.out.println("连接MYSQL数据库成功!");
    }
}

Oracle数据库厂商

/*
    Oracle的数据库厂家负责编写JDBC接口的实现类
*/
public class Oracle implements JDBC{

    public void getConnection(){
        System.out.println("连接Oracle数据库成功!");
    }
}

Java程序员

显然,java程序员在操作MySQL数据库时,需要先把MySQL数据库厂商的对JDBC的实现类的jar包导入工程,也就是需要先把mysql驱动导入工程,不然接口的方法没有实现类,程序自然无法执行。

同理,java程序员在操作Oracle数据库时,也需要先导入Oracle驱动

/*
    Java程序员角色。
    不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码。
    面向接口编程,面向抽象编程,不要面向具体编程。
*/
import java.util.*;

public class JavaProgrammer
{
    public static void main(String[] args) throws Exception{
        // JDBC jdbc = new MySQL();
        // JDBC jdbc = new SqlServer();

        // 创建对象可以通过反射机制。
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String className = bundle.getString("className");
        Class c = Class.forName(className);
        JDBC jdbc = (JDBC)c.newInstance();

        // 以下代码都是面向接口调用方法,不需要修改
        jdbc.getConnection();
    }
}

三方之间的关系图

JDBC本质.jpg

JDBC编程六步法

第一步:注册驱动

  • 作用:告诉Java程序,即将要连接的是哪个品牌的数据库
Driver driver = new com.mysql.cj.jdbc.Driver(); // 使用多态,父类型引用指向子类型对象
DriverManager.registerDriver(driver);

上面代码可以合并为下面一行

DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());

注册驱动这里还可以进行代码优化,我们就在下面JDBC工具类,DBUtils里面进行

第二步:获取连接

  • 表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的操作,使用完之后一定要关闭通道

获取连接的url需要遵循通信协议

  • 通信协议是通信之前就提前定好的数据传送格式
  • 它包括数据包具体怎样传数据,其格式是提前定好的

url案例

jdbc:mysql://127.0.0.1:3306/swu

swu是西南大学的英文简写,我自己设置的数据库的名字

  • jdbc:mysql:// -------->协议
  • 127.0.0.1 ------------->IP地址
  • 3306/mysql----------->数据库端口号
  • swu--------------------->具体的数据库名
String url = "jdbc:mysql://192.168.151.9:3306/bjpowernode";
String user = "root";
String password = "981127";
String connection = DriverManager.getConnection(url,user,password);

注意

不同数据库的通信的协议不同,例如Oracle数据库的通信协议如下:

jdbc:oracle:thin:@localhost:1521:数据库名

第三步:获取数据库操作对象

  • 专门执行sql语句的对象
Statement stmt = connection.createStatement();

一般较少使用statement执行SQL语句,因为有SQL注入问题。

我们一般使用PrepareStatement对象,之后会谈到

第四步:执行SQL语句

JDBC中的sql语句不需要提供分号结尾

String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
// 专门执行DML语句的(insert delete update)
// 返回值是“影响数据库中的记录条数”
int count = stmt.executeUpdate(sql); 
System.out.println(count == 1 ? "保存成功" : "保存失败");

第五步:处理查询结果集

  • 只有当第四步执行的是select语句的时候,才要处理结果集

我们用ResultSet对象来接收执行sql语句的返回值后,这个数据集是什么样子呢?

如下图:

遍历结果集.jpg

这里详细谈谈ResultSetnextLine()方法,getString()方法

nextLine()方法判断下一行是否有数据,它类似于一个浮标的移动,如果下一行有数据,则返回true,否则返回false

getString()方法:不管数据库中的数据类型是什么,都以String的形式取出,同理:还有getInt,getDouble等

当然,如果在()内输入字段名,则会以列的名字获得该数据,输入单纯的数字n,则取出第n列的数据

注意

  1. JDBC中所有下标从1开始,不是从0开始。
  2. 列名称不是表中的列名称,是查询结果集的列名称

第六步:释放资源

  • 使用完资源之后一定要关闭资源。
  • Java和数据库属于进程间的通信,开启之后一定要关闭。
  • 不关闭通道,时间一长可能出现问题

为了保证资源一定释放,在finally语句块中关闭资源,并且要遵循从小到大依次关闭

try{
    if(stmt != null){
    stmt.close();
    }
}catch(SQLException e){
    e.printStackTrace();
}
try{
    if(conn != null){
        conn.close();
    }
}catch(SQLException e){
    e.printStackTrace();
}

JDBC工具类的写法

我们通常创建一个类名为DBUtils的工具类来封装JDBC中注册驱动,获取连接,释放资源部分的代码,提高代码复用性

import java.sql.*;
/**
 * JDBC工具类,简化JDBC编程。
 */
public class DBUtil {
    /**
     * 工具类中的构造方法都是私有的。
     * 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用。
     */
    private DBUtil() {
    }
    // 静态代码块在类加载时执行,并且只执行一次。
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取数据库连接对象
     *
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/swu", "root", "password");
    }

    /**
     * 关闭资源
     * @param conn 连接对象
     * @param ps 数据库操作对象
     * @param rs 结果集
     */
    public static void close(Connection conn, Statement ps, ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(ps != null){
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

ResourceBundle绑定属性配置文件

资源配置文件准备

文件名:jdbc.properties

内容:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://主机IP/数据库名
user=数据库用户名
password=数据库用户密码

绑定

使用ResourceBundle的getBundle方法,传递参数为资源配置文件(.properties后缀文件)名字,不包括后缀

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

推荐阅读更多精彩内容