JDBC :带它再爱你一次

(一) JDBC 入门

(1) 概述

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。JDBC是面向关系型数据库的。

简单解释: 通过Java语言执行sql语句,从而操作数据库

(2) 来由

想要通过Java操作不同的数据库,应该根据数据库的不同而执行特定的API,而出于简化的想法,Sun公司,定义了一套面向所有关系型数据库的 API 即 JDBC ,其只提供接口,而具体实现去交给数据库厂商实现,而我们作为开发者,我们针对数据数据库的操作,只需要基于JDBC即可

(二) 简单使用 JDBC

我们简单的使用JDBC去查询数据库中的数据,并且输出到控制台中

为了快速演示,我们新建一张非常简单的表

CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
score DOUBLE(4,1)
); INSERT student(id,NAME,score) VALUES (1,'张三',98); INSERT student(id,NAME,score) VALUES (2,'李四',96); INSERT student(id,NAME,score) VALUES (3,'王五',100);

我们根据数据库中的信息写一个对应的学生类

public class Student {
private int id;
private String name;
private double score;
//省略构造、Get、Set、toString方法
......
}

下面是对 JDBC 查询功能的简单使用

package cn.ideal.jdbc;

import cn.ideal.domain.Student;

import java.sql.*;

public class JdbcDemo {
public static void main(String[] args) {
//导入数据库驱动包 Connection connection = null;
Statement statement = null;
ResultSet resultSet = null; try {
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取与数据库的连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "root99");
//定义sql语句
String sql = "SELECT * FROM student";
//获取执行sql语句的对象statement
statement = connection.createStatement();
//执行sql语句,获取结果集
resultSet = statement.executeQuery(sql); //遍历获取到的结果集
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
Double score = resultSet.getDouble(3); Student student = new Student();
student.setId(id);
student.setName(name);
student.setScore(score); System.out.println(student.toString());
} } catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源,后调用的先释放
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
} //运行结果
Student{id=1, name='张三', score=98.0}
Student{id=2, name='李四', score=96.0}
Student{id=3, name='王五', score=100.0}

下面我们开始详细的解释一下上面所用到的各个对象

(三) JDBC 对象详解

(1) DriverManager

A:加载驱动 --> 注册驱动

首先我们要知道加载驱动和注册驱动这两个词是什么意思,刚刚接触的时候,会有人总有朋友将Class.forName(com.mysql.jdbc.Driver) 当做注册数据库驱动的语句,但实际不然,它的作用是将参数表示的类加载到内存中,并且初始化,同时其中的静态变量也会被初始化,静态代码块也会被执行

  • 疑惑:能否使用ClassLoader 类中的loadClass()方法呢?

    • 答案是否定的,这个方法的特点是加载但不对该类初始化
//Class类源码节选 -jdk8
* A call to {@code forName("X")} causes the class named
* {@code X} to be initialized.

关于初始化问题这里简单提及一下,我们还是先回到我们主线来

为什么不对类进行初始化,就不能选择了呢?

这是因为真正实现注册驱动(告诉程序使用哪一个数据库驱动jar)的是:

static void registerDriver(Driver driver)

我们在jar包中找到Driver这个类,查看其源码

//com.mysql.jdbc.Driver类中的静态代码块
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}

类被加载后,执行了类中的静态方法DriverManager进行了注册驱动

我们也可能有见过下面2中的代码,但是实际上驱动会被加载两次,因为执行

new com.mysql.jdbc.Driver() 已经加载了一次驱动

//1.推荐
Class.forName("com.mysql.jdbc.Driver");
//2.不推荐
DriverManager.registerDriver(new com.mysql.jdbc.Driver())

那么何必这么麻烦呢?new com.mysql.jdbc.Driver() 直接这样写不就挺好了吗?

但我们还是选择 拒绝!为什么呢?

如果我们这样写,对于jar包的依赖就比较重了,我们如果面临多个项目,或者需要修改数据库,就需要修改代码,重新编译,但是如果使用Class类加载的方式,既保证了静态代码块中所包含的注册驱动方法会被执行 ,而又将参数变成了字符串形式,我们之后便可以通过修改配置文件 “ ” 内的内容 + 添加jar包 的方式更灵活的处理问题,并且不需要重新编译!

注意:mysql5之后的驱动jar包可以省略注册驱动这一步,原因查看jar包中META-INF/services/java.sql.Driver文件

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

B:获取数据库连接

static Connection getConnection(String url, String user, String password)
/*
jdbc:mysql://ip地址(域名):端口号/数据库名称
Eg:jdbc:mysql://localhost:3306/db1
本地mysql,且端口为默认3306,则可简写:jdbc:mysql:///数据库名称
*/

(2) Connection (数据库连接对象)

A:获取执行sql的对象

//创建向数据库发送sql语句的statement对象
Statement createStatement() //创建向数据库发送预编译sql语句的PrepareStement对象
PreparedStatement prepareStatement(String sql)

B:管理事务

//开启事务:设置参数为false,即开启事务
setAutoCommit(boolean autoCommit) //提交事务
commit() //回滚事务
rollback()

(3) Statement (执行sql语句的对象)

//执行DQL(查询数据库中表的记录(数据))
ResultSet executeQuery(String sql) //执行DML(对数据库中表的数据进行增删改)
int executeUpdate(String sql) //执行任意sql语句,但是目标不够明确,较少使用
boolean execute(String sql) //把多条sql的语句放到同一个批处理中
addBatch(String sql) //向数据库总发送一批sql语句执行
executeBatch()

代码演示(以增加一条数据为例)

package cn.ideal.jdbc;

import java.sql.*;

public class StatementDemo {
public static void main(String[] args) { Connection connection = null;
Statement statement = null;
try {
//加载驱动
Class.forName("com.mysql.jdbc.Driver"); //获取数据库连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "root99"); //定义sql语句
String sql = "INSERT student(id,NAME,score) VALUES (NULL,'马六',88);"; //获取执行sql语句的对象
statement = connection.createStatement(); //执行sql语句
int count = statement.executeUpdate(sql);
System.out.println(count);
if (count > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

(4) ResultSet(结果集对象,封装查询结果)

ResultSet所代表的的是sql语句的结果集——执行结果,当Statement对象执行excuteQuery()后,会返回一个ResultSet对象

//游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据)
//如果是,则返回false,如果不是则返回true
boolean next() //获取数据,Xxx代表数据类型
getXxx(参数) Eg:int getInt() , String getString() 1. int:代表列的编号,从1开始 如: getString(1)
2. String:代表列名称。 如: getDouble("name")

案例可参考开头快速使用部分,自行尝试读取数据库中数据后用集合框架装载

(四) 事半功倍——工具类

通过封装一些方法,使得出现一个更加通用的工具类,我们可以通过properties配置文件 ,使得信息更加直观且容易维护

package cn.ideal.jdbc;

import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties; public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver; /**
* 文件读取
*/
static { try {
//创建Properties集合类
Properties pro = new Properties();
//获取src路径下的文件
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath(); //加载文件
pro.load(new FileReader(path));
//获取数据
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver"); //注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} } /**
* 获取连接
*
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
} /**
* 释放资源
*
* @param statement
* @param connection
*/
public static void close(Statement statement, Connection connection) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} /**
* 释放资源
*
* @param resultSet
* @param statement
* @param connection
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} }

工具类测试类

package cn.ideal.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement; public class JDBCUtilsTest {
public static void main(String[] args) { Connection connection = null;
Statement statement = null;
try { connection = JDBCUtils.getConnection(); //定义sql语句
String sql = "INSERT student(id,NAME,score) VALUES (NULL,'马六',88)"; //获取执行sql语句的对象
statement = connection.createStatement(); //执行sql语句
int count = statement.executeUpdate(sql);
System.out.println(count);
if (count > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(statement,connection);
}
}
}

之前的文章中分别通过集合实现、IO实现、而学习数据库后,我们可以试着通过数据库存储数据,写一个简单的登录注册小案例!在第五大点中有提到吼

(五) 补充:PreparedStatment

//创建向数据库发送预编译sql语句的prepareStatement
PreparedStatement prepareStatement(String sql)

prepareStatement继承自Statement,总而言之,它相较于其父类,更强更简单!

(1) 优点

A:效率

Statement 直接编译 SQL 语句,直接送到数据库去执行,而且其多次重复执行sql语句,PreparedStatement 会对SQL进行预编译,再填充参数,这样效率会比较高(预编译的SQL存储在PreparedStatement中

B:可读性

定义 SQL 语句的时候,常常需要使用到 Java 中的变量,在一些复杂的情况下,需要频繁的使用到引号和单引号的问题,变量越多,越复杂,而PreparedStatement可以使用占位符 ‘ ?’ 代替参数,接下来再进行参数的赋值,这样有利于代码的可读性

C:安全性

PreparedStatement 由于预编译,可以避免Statement中可能需要采取字符串与变量的拼接而导致SQL注入攻击【编写永等式,绕过密码登录】

我们先按照我们之前的做法,写一个简单的登录Demo,先创一张表!

CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
); SELECT * FROM USER; INSERT INTO USER VALUES(NULL,'admin','admin888');
INSERT INTO USER VALUES(NULL,'zhangsan','123456');

接着编写代码

package cn.ideal.login;

import cn.ideal.jdbc.JDBCUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner; public class LoginDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine(); boolean flag = new LoginDemo().login(username, password); if (flag) {
System.out.println("登录成功");
} else {
System.out.println("用户名或密码错误");
}
} /**
* 登录方法
*/
public boolean login(String username, String password) {
if (username == null || password == null) {
return false;
} Connection connection = null;
Statement statement = null;
ResultSet resultSet = null; try {
connection = JDBCUtils.getConnection();
//定义sql
String sql = "SELECT * FROM USER WHERE username = '" + username + "' AND password = '" + password + "' ";
//获取执行sql的对象
statement = connection.createStatement();
//执行查询
resultSet = statement.executeQuery(sql); return resultSet.next(); } catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(resultSet,statement, connection);
}
return false;
}
}

简单的来说,这样一个简单的登录Demo就写好了,但是这个时候,SQL注入问题中的一种情况就出现了,或许你听过,在早些年的时候,漏洞还是蛮常见的,一些黑客或者脚本小子们常常使用一些SQL注入的手段进行目标网站后台的入侵,我们今天所讲的这一种,就是其中一种,叫做SQL万能注入(SQL万能密码)

我们先来观察一下上述代码中关于SQL语句的部分

 String sql = "SELECT * FROM USER WHERE username = '" + username + "' AND password = '" + password + "' ";

也就是说它将我们所输入的 usernamepassword合成为SQL查询语句, 当数据库中不存在这样的字段就代表输入错误,但是对于存在SQL注入漏洞的程序,则可以通过构造一些特殊的字符串,达到登录的目的,先贴出来测试结果

//运行结果
请输入用户名
admin
请输入密码
1' or '1' = '1
登录成功

如果我们将上述代码中密码 (username) 部分用我们的这些内容代替是怎么样的呢

 String sql = "SELECT * FROM USER WHERE username = 'admin' AND PASSWORD = '1' or '1' = '1' ";

补充:在SQL语句中逻辑运算符具有优先级,= 优先于 and ,and 优先于 or

所以上面的式子中 AND先被执行,当然返回错,接着执行or部分,对于一个永等式 ‘1’ = ‘1‘ 来说返回值永远是true,所以SQL查询结果为true,即可以登录成功

//使用PrepareStemen替代主要部分

//定义sql
String sql = "SELECT * FROM USER WHERE username = ? AND password = ?";
//获取执行sql的对象
preparedStatement = connection.prepareStatement(sql);
//给?赋值
preparedStatement.setString(1, username);
preparedStatement.setString(2, password); //执行查询
resultSet = preparedStatement.executeQuery(); //运行结果
请输入用户名
admin
请输入密码
1' or '1' = '1
用户名或密码错误

结尾:

如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家 !_

如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的我们素不相识,却都在为了自己的梦而努力 ❤

一个坚持推送原创Java技术的公众号:理想二旬不止

JDBC:数据库连接技术的更多相关文章

  1. JDBC数据库连接技术

    [学习笔记]JDBC数据库连接技术(Java Database Connectivity) 一.JDBC简介 Java是通过JDBC技术实现对各种数据库的访问的,JDBC是Java数据库连接技术的简称 ...

  2. 【学习笔记】JDBC数据库连接技术(Java Database Connectivity)

    一.JDBC简介 Java是通过JDBC技术实现对各种数据库的访问的,JDBC是Java数据库连接技术的简称.它可以把数据持久保存,是一种持久化机制. 1.持久化 持久化就是将程序中的数据在瞬时状态和 ...

  3. Java数据库连接技术——JDBC

    大家好,今天我们学习了Java如何连接数据库.之前学过.net语言的数据库操作,感觉就是一通百通,大同小异. JDBC是Java数据库连接技术的简称,提供连接各种常用数据库的能力. JDBC API ...

  4. JDBC 数据库连接池

    http://www.cnblogs.com/lihuiyy/archive/2012/02/14/2351768.html JDBC 数据库连接池 小结   当对数据库的访问不是很频繁时,可以在每次 ...

  5. JAVA之JDBC数据库连接池总结篇

    JDBC数据库连接池 一.JDBC数据库连接池的必要性 二.数据库连接池技术 三.多种开源的数据库连接池 3.1 C3P0数据库连接池 3.2 DBCP数据库连接池 3.3 Druid(德鲁伊)数据库 ...

  6. JAVA基础知识之JDBC——JDBC数据库连接池

    JDBC数据库连接池 数据库的连接和关闭是很耗费资源的操作,前面介绍的DriverManager方式获取的数据库连接,一个Connection对象就对应了一个物理数据库连接,每次操作都要打开一个连接, ...

  7. 【Java123】JDBC数据库连接池建立

    需求场景:多SQL任务多线程并行执行 解决方案:建立JDBC数据库连接池,将线程与连接一对一绑定 https://www.cnblogs.com/panxuejun/p/5920845.html ht ...

  8. JDBC数据库连接池

    用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长.假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库 ...

  9. Spring框架的JDBC模板技术和事物

    Spring框架的JDBC模板技术         技术分析之Spring框架的JDBC模板技术概述  1. Spring框架中提供了很多持久层的模板类来简化编程,使用模板类编写程序会变的简单     ...

  10. Java自学-JDBC 数据库连接池

    数据库连接池 与线程池类似的,数据库也有一个数据库连接池. 不过他们的实现思路是不一样的. 本章节讲解了自定义数据库连接池类:ConnectionPool,虽然不是很完善和健壮,但是足以帮助大家理解C ...

随机推荐

  1. request.getParameter乱码

    String str= new String(request.getParameter("xxxx").getBytes("ISO-8859-1")," ...

  2. CF1153F Serval and Bonus Problem 【期望】

    题目链接:洛谷 作为一只沉迷数学多年的蒟蒻OIer,在推柿子和dp之间肯定要选推柿子的! 首先假设线段长度为1,最后答案乘上$l$即可. 对于$x$这个位置,被区间覆盖的概率是$2x(1-x)$(线段 ...

  3. Maven:element '******' cannot have character [children]

    此错误是由于XML文件的解析不正确造成的,因为在一个/某些标签之间存在奇怪和隐藏的字符. 这些字符可能来自网络上的复制粘贴.要解决此问题,请删除标签>标记定义之间的所有空格和换行符,然后将它们放 ...

  4. Async and Await (Stephen Cleary)

    https://blog.stephencleary.com/2012/02/async-and-await.html Most people have already heard about the ...

  5. hbase部署经验与坑总结

    1.本地单机部署hbase,想要使用独立zookeeper,不使用自带的 vim conf/hbase-env.sh export HBASE_MANAGES_ZK=false 设置不使用自带zook ...

  6. Oracle中shrink space命令

    shrink_clause:   http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_3001.htm#i2192484 ...

  7. 手把手教你实现RecyclerView的下拉刷新和上拉加载更多

    手把手教你实现RecyclerView的下拉刷新和上拉加载更多     版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...

  8. Android 显示系统:SurfaceFlinger详解

    一.Android系统启动 Android设备从按下开机键到桌面显示画面,大致过程如下图流程: 开机显示桌面.从桌面点击 App 图标到 Activity显示在屏幕上的过程又是怎样的呢?下面介绍And ...

  9. cmake log

    20:28:54: 为项目RoboticArmProject_CarTerminal_V20190530执行步骤 ...20:28:54: 正在启动 "/usr/bin/make" ...

  10. kettle的用法

    一: 从一个数据库导入表的数据到另一个 数据库的表中(表数据同步) 1:在 主对象树-- DB连接 中新建 连接: 在选项中 设置字符集: 2: 在 核心对象中 先增加一个  表输入: 再增加一个 插 ...