真的了解JDBC吗?- JDBC详解

前言

前面将JavaWeb的三个组件:Servlet、Listener、Filter学习了
仔细复习了XML、Session、Cookie
接下来是JDBC
这篇文章将按照:JDBC概述 - 》JDBC实现案例 - 》JDBC核心对象 -》总结
顺序学习

JDBC

程序的运行需要数据,不能都靠我们内置数据,需要将数据与程序分开,数据持久化存放在数据库,程序运行时调用数据库数据,这就需要连接数据库

如何在一个程序中连接数据库呢?

  • 可以自己基于TCP/IP协议写一个应用层程序连接数据库,但是又麻烦,可移植性又差(只能连接当前数据库)
  • 基于JDBC API连接,需要连接什么数据库,就可以连接该厂商的Connect驱动,即简单又可移植

一个程序访问数据库的过程:
在这里插入图片描述

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,JDBC是面向关系型数据库的。

注意:数据库驱动是具体的数据库厂商提供的
如Mysql数据库提供的mysql-connector-java-x.jar
oracle提供的ojdbc-x.jar

驱动类的差别:
Mysql:com.mysql.jdbc.Driver
Oracle:oracle.jdbc.OracleDriver

语句也有一些差别

Mysql连接数据库的url:jdbc:mysql://localhost:3306/DatabaseName
Oracle:jdbc:oracle:thin@localhost:1521:ORCL

JDBC连接数据库的可移植性体现出了,JDBC中内置了与不同数据库驱动的连接方式,更换不同的数据库,仅需更换驱动即可(sql语言也要符合不同数据库)

实例,连接Mysql

首先,在Mysql准备一个test数据库和student表
在这里插入图片描述

TestMysql:

package com.company.JDBC;

import java.sql.*;

public class TestMysql {
	//连接数据库的url
    private static String url = "jdbc:mysql://localhost:3306/test";
	//用户名、密码
    static String name = "root";
    static String password = "root";

    public static void main(String[] args) {

        Connection connection = null;
        Statement statement = null;

        try {
            //JVM创建DriverManager
            Class.forName("com.mysql.jdbc.Driver");
            //创建连接
            connection = DriverManager.getConnection(url,name,password);
            //sql语句操作数据库
            statement = connection.createStatement();
            String sql = "select * from Student where id>1";
            //返回结果集ResultSet
            ResultSet resultSet = statement.executeQuery(sql);
            //遍历结果集,打印
            while (resultSet.next()){
                System.out.println("id="+resultSet.getInt("id"));
                System.out.println("name="+resultSet.getString("name"));
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            try {
                //关闭连接
                statement.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

得到了sql语句查询的数据
在这里插入图片描述

上面就是一个简单的访问数据库并返回数据的过程
在这里插入图片描述

这几个对象需要深入了解:
DriverManager、Connection 、Statement、ResultSet

DriverManager驱动管理器

首先,我们应该知道:
DriverManager是java.sql包的,是属于JDBC
Driver是com.mysql驱动包,是外部加入的包

创建连接:connection = DriverManager.getConnection(url,name,password);

这个DriverManager都不知道从哪冒出来的?为什么它可以返回一个驱动?

学过Java反射,应该有一点猜想了
Class.forName("com.mysql.jdbc.Driver")
这是一个反射语句,获得驱动com.mysql.jdbc.Driver类的class对象

我们进入这个驱动类的源码看看:
在这里插入图片描述

这个类很简单,只有一个空构造方法和一个静态代码块

当我们通过反射创建Driver的Class对象,就会运行这个静态代码块
这个代码块:DriverManager.registerDriver(new Driver())实例化了一个Driver对象,并将Driver对象注册到DriverManager里

就这?那些实现方法呢?这个DriverManager又是哪冒出来的?

看继承类,MySQL的Driver类还继承了NonRegisteringDriver,它实现了Driver接口的全部方法
在这里插入图片描述

这个类中的连接方法:
在这里插入图片描述
看到这里,结合前面说的JDBC负责连接各种数据库驱动,驱动才是负责连接数据库的,应该差不多明白了,真正连接数据库的是Driver类(NonRegisteringDriver)
其实不需要使用DriverManager,仅使用Driver的connect方法也可以创建连接,也可以通过url和info(usename和password)连接数据库
我们可以试试:上面的类中使用

			Driver driver = new com.mysql.jdbc.Driver();
            Properties properties = new Properties();
            properties.put("user",name);
            properties.put("password",password);
            connection = driver.connect(url, properties);

代替

Class.forName("com.mysql.jdbc.Driver");
//创建连接
connection = DriverManager.getConnection(url,name,password);

也可以连接到数据库
在这里插入图片描述

那么DriverManager是什么呢?

它是一个驱动管理器,管理所有的驱动

内部有一个list保存注册在它上面的驱动
在这里插入图片描述DriverManager的getConnection()方法能够获得连接,是因为底层会循环遍历所有驱动,找到当前注册的驱动后调用driver.connect()获得Connection

这是它的getConnection方法,当调用这个方法时,DriverManager会试着从初始化时加载的那些驱动程序以及使用与当前程序相同的类加载器显式加载的那些驱动程序中查找合适的驱动程序
在这里插入图片描述

回到最初的问题:DriverManager从哪来?
在这里插入图片描述
看DriverManager的构造方法,是private,也就是防止被实例化,这是一个单例模式,DriverManager会有相应的方法创建并在系统中是唯一的,不需要我们去创建,而那些方法也都是静态方法,直接调用即可

DriverManager的初始化在一个静态代码块里
在这里插入图片描述

其中loadInitialDrivers方法加载系统变量的System.getProperty(“jdbc.drivers”),将其读取到驱动初始化
通过ServiceLoadr可以获取到Driver的实现类

为什么需要DriverManager?

我们前面在源码中看到了DriverManager中有一个列表存放所有注册在其上的Driver驱动,DriverManager会查找这些驱动,找到我们需要的驱动,设计的初衷就是为了支持注册多个(多种)驱动,通过DriverManager可以管理多个驱动程序

大致的加载流程:

Class.forName(“com.mysql.jdbc.Driver”)是反射,将Driver加载到JVM,并且会运行Driver中的静态代码块DriverManager.registerDriver(new Driver()),实例化一个Driver并注册到DriverManager中(DriverManager早已加载在JVM中了),通过DriverManager可以管理上面驱动,例如最常用的获得连接getConnection实质上是DriverManager通过输入的参数,调用相匹配的Driver驱动,然后调用该驱动的connect方法连接数据库

DriverManager的其他方法要深究的话也挺复杂的,就不去了解了

Connection 连接对象

前面知道,通过Driver驱动connect方法连接到数据库,会返回一个对象Connection,这是将Java程序与数据库连接的抽象,抽象成一个对象

通过这个对象可以执行对象的获取进一步执行SQL操作

Connection提供了数据库事务相关信息的设置以及其他信息的设置与获取

在这里插入图片描述
它可以获得三个执行对象:Statement、PreparedStatement、CallableStatement
这个我们最常用,等下再学习

事务相关的:commit提交、rollback事务回滚等等
事务就是那些SQL事务,Mysql中默认自动事务提交
如果想要验证一下这些方法,可以先connect.setAutoCommit(false)设置手动事务提交,然后验证

package com.company.JDBC;

import java.sql.*;

public class TestMysql {
    private static String url = "jdbc:mysql://localhost:3306/test";

    static String name = "root";
    static String password = "root";

    public static void main(String[] args) {

        Connection connection = null;
        Statement statement = null;

        try {
            //JVM创建DriverManager
            Class.forName("com.mysql.jdbc.Driver");
            //创建连接
            connection = DriverManager.getConnection(url,name,password);
            //sql语句操作数据库
            statement = connection.createStatement();
            
           
            String sql = "select * from Student where id>1";

            //返回结果集ResultSet
            ResultSet resultSet = statement.executeQuery(sql);
            //遍历结果集,打印
            while (resultSet.next()){
                System.out.println("id="+resultSet.getInt("id"));
                System.out.println("name="+resultSet.getString("name"));
            }
            System.out.println("--------------------------");

            connection.setAutoCommit(false);
            //往数据库添加数据5
            String insetSql = "insert into Student values(5,'xiaoming')" ;
            statement.executeUpdate(insetSql);
            //回滚,不保存
            connection.rollback();

            ResultSet resultSet1 = statement.executeQuery(sql);
            //遍历结果集,打印
            while (resultSet1.next()){
                System.out.println("id="+resultSet1.getInt("id"));
                System.out.println("name="+resultSet1.getString("name"));
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            try {
                //关闭连接
                statement.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

事务回滚,数据(5,‘xiaoming’)并没有保存到数据库

自身属性信息:关闭方法(数据库连接是有限的,记得手动关闭)、查询关闭、设置只读等

Connection有很多方法,对应数据库事务的相关信息和一些自身的信息,可以选择的了解

Statement执行对象

Connection连接对象可以获得三种执行对象:

Statement:

  • 作用:用于执行不带参数的简单SQL语句
  • 特点:每次执行SQL语句,数据库都要执行SQL语句的编译,仅执行一次查询并返回结果的情形建议使用这个,此时效率高于PreparedStatement

PreparedStatement

  • 作用:用于执行带 或 不带参数的预编译SQL语句
  • 特点:是预编译的,在执行可变参数的一条SQL语句时,比Statement的效率高,安全性好,有效防止SQL注入等问题,对于多次重复执行的语句,效率会更高

CallableStatement

  • 作用:用于调用数据库的存储过程、存储函数

我们前面使用statement = connection.createStatement();获得的是Statement,执行的sql内部是不可变动的,即当execute执行时,这个sql语句是确定的

我们可以看看Statement的结构
在这里插入图片描述
Statement、PreparedStatement、CallableStatement都是接口,具体的实现靠的是其子类

Statement

通过connection.createStatement();获得Statement

对于数据库的操作,Statement主要是两个方法:
数据库增删改:executeUpdate
数据库查询:executeQuery

具体的实现太复杂了,暂时没这能力解读:
在这里插入图片描述
execute有很多方法
在这里插入图片描述
主要是3种方法:execute,executeQuery,executeUpdate,其他都是一些扩展

execute执行任意的SQL查询,但是返回的是boolean类型,也就是只能知道操作是否成功,如果要得到结果集还需要getResultSet方法来获取ResultSet,或者通过getUpdateCount()方法来获取更新的记录条数

executeQuery执行查询操作(select),返回ResultSet,即使查询不到记录返回的ResultSet也不会为null,如果sql语句的增删改会报错

executeUpdate执行增删改操作,返回int,返回值是更新的记录数量

PreparedStatement

首先PreparedStatement是Statement的子类
在这里插入图片描述
那么Statement的方法,PreparedStatement也有
所以增删改查还是那三个方法execute,executeQuery,executeUpdate

PreparedStatement创建有点不同:

PreparedStatement statement1 =  connection.prepareStatement(sql);

它需要传递sql语句,那下面的execute语句就不需要sql参数了

String sql = "select * from Student where id>1";
statement = connection.prepareStatement(sql);
//返回结果集ResultSet
ResultSet resultSet = statement.executeQuery();

也就是说在 connection.prepareStatement(sql)时,先被预编译了

它可以使用占位符?先占着sql语句的属性的位置,到后面在通过赋值方法set

String sql = "select * from Student where id > ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,1);

一样可以得到结果
在这里插入图片描述

使用?占着sql语句的一个位置
到后面再用statement.setInt(1,1)给第一个?设置值
根据数据类型选择set方法,setInt 、setString 、setArray等

PreparedStatement和Statement的使用

推荐使用PreparedStatement:

  • 编码更加简便(避免了字符串的拼接)
    一个带参数的插入语句:
    Statement中的sql这么写,很复杂:先单引号,再双引号,在+id+
String sql = "insert into Student (id,name) values ('"+id+"','"+name+"')";

而使用PreparedStatement仅需放两个占位符,在到后面添加

String sql1 = "insert into Student (id,name) values (?,?)";
statement.setInt(1,id);
statement.setString(2,name);
  • 功能更强大:PreparedStatement是Statement子类,扩展了很多功能

  • 提高性能:对于多次重复的语句,Statement是将statement.executeQuery(sql);重复执行,需要多次编译sql语句,而PreparedStatement是将statement.executeQuery();重复执行,不需要编译sql,即提高了性能

  • 安全性:Statement存在sql注入的风险,而PreparedStatement没有,在executeQuery内不用传递sql语句

什么是sql注入?

sql注入就是将客户输入的内容与开发人员输入的sql语句混为一体
具体的可以看这篇文章
JDBC中碰到的SQL安全问题 - Sql注入

通过用户名输入abc' or 1=1 --,将客户输入的内容与开发人员输入的sql语句混为一体,or 1=1 使得name验证成功, – 注释掉password验证,就可以随意登录数据库

为什么PreparedStatement可以防备Sql注入

我们可以稍微的可靠PreparedStatement源码(com.mysql.jdbc包下的PreparedStatement类是实现类)

setString方法的实现中:

在这里插入图片描述

boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);

判断是否需要转义处理(比如包含引号,换行等字符)

if (!needsHexEscape) {
       parameterAsBytes = null;
       buf = new StringBuilder(x.length() + 2);
       buf.append('\'');
       buf.append(x);
       buf.append('\'');

如果不需要转义,则在两边加上单引号

这样一操作,客户端输入的单引号就没用了,自然无法sql注入

最终可以得出结论:推荐使用PreparedStatement

CallableStatement

CallableStatement负责调用存储过程 ,这种调用是用一种换码语法来写的,有两种形式:一种形式带结果参,另一种形式不带结果参数

用的太少,稍作了解即可:百度百科:CallableStatement

ResultSet

Java程序访问数据库,一般都是需要返回数据

如果Sql是增删改语句,返回是int值,返回值是更新的记录数量
如果Sql是查询语句,返回的ResultSet对象

重点了解ResultSet对象

ResultSet用于代表Sql语句的执行结果,ResultSet封装执行结果时,采用的类似于表格的方式,并且Resultset会维护一个指针,这个指针会指向表格头部,类似与迭代器Iterator
它的继承结构是这样的:
在这里插入图片描述

ResultSet是一个接口,实现类是ResultSetImpl类

源码就别看了,太多太复杂,知道ResultSet对象的操作就好

ResultSet封装了执行结果集合,那么肯定能够获取数据

它提供了诸多get方法:

getInt:获得int型数据
在这里插入图片描述

getString:获得String型数据
在这里插入图片描述

getDouble:获得Double型数据

在这里插入图片描述

等等获取不同类型的数据的方法,从这也可以看出获取数据是比较严格的,它会根据数据库表中数据类型分配,只能通过同类型的get方法得到

有没有注意到,这些数据类型都有两种get方法,一个传入int型参数,一个传入String型

这是两种获得方法
在这里插入图片描述

我们前面获得id,name是传入数据库表的列名得到相应的数据,也可以传入序号

System.out.println("id="+resultSet.getInt(1));
System.out.println("name="+resultSet.getString(2));

也能得到相应的结果:
在这里插入图片描述

但是要注意:序号是从1开始,且要知道数据库表的列的顺序
所以不推荐使用序号获得数据

类似迭代器的ResultSet对象:
在这里插入图片描述

ResultSet提供了对结果集进行滚动的方法(移动指针):

  • next():移动到下一行
  • previous():移动到前一行
  • absolute(int row):移动到指定行
  • beforeFirst():移动resultSet的最前面。
  • afterLast() :移动到resultSet的最后面。

并且这些方法返回的是boolean值
在这里插入图片描述

也就是判断是否移动成功

总结

JDBC大致就是这些知识点,当然,如果要深究底层的实现原理还可以继续研究下去,但是难度太大,暂时处理不了

  • JDBC的规范Java程序访问数据库的应用程序接口,JDBC可移植性强,因为它内部实现了对多种数据库驱动的管理,面对不同的数据库驱动都封装了连接管理方法
  • Java程序访问数据库:Java - > JDBC -> 不同数据库Driver驱动 -> 数据库;不同的数据库的厂商都有自己的驱动,驱动类、语法都有些不同,需要注意
  • JDBC的实现关键是这四个对象:DriverManager、Connection、Statement、ResultSet
  • DriverManager是驱动管理器,它管理着注册在它上面的驱动,真正创建一个连接的过程:class.forName反射在JVM中加载驱动class,会运行其中的静态代码块,实例化一个Driver类并注册到DriverManager上,DriverManager.getConnect方法会根据传入的参数选择合适的Driver(当有多个驱动注册时),并调用Driver类中的Connect方法,连接数据库,返回一个Connection对象
  • Connetion对象是对Java程序与数据库连接的抽象,提供了数据库事务的相关信息和其他信息的设置与或者,例如Statement执行对象、commit提交事务、rollback事务回滚、连接自身属性相关等
  • Statement执行对象有3种,Statement、PreparedStatement、CallableStatement;CallableStatement负责调用存储过程,使用较少
  • Statement执行对象执行sql语句,有3种方法:execute、executeQuery、executeUpdate;execute可以执行增删改查操作,但是只能返回boolean类型结果,即只知道执行成功与否,获得结果集需要getResultSet方法获得;executeQuery执行查找操作,返回ResultSet结果集,类似与表格存储查询到的数据;executeUpdate执行增删改操作,返回int型结果,返回值是更新的记录数量
  • PreparedStatement是Statement的子类,在它的基础上添加了许多方法,且使用方法也不同,connect.preparedStatement(sql)创建时需要带上sql语句预编译,sql语句可以使用占位符,且防备了sql注入的安全问题
  • ResultSet是一个结果集,封装了sql语句返回的结果,如果是增删改,返回的是int型数据,是更新的记录数量,如果是查询,返回的是ResultSet结果集;ResultSet类似与迭代器,内部维护了一个指针,可以获得下一条数据(next)、上一条(previous),get数据需要明确数据类型如getInt,getString,可以通过列名或者序号获得
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值