JDBC 全解析:从入门到实战,掌握数据库交互核心技术
JDBC
JDBC概述
概述
java database connectivity java数据库的连接
目的使用java的代码来操作数据库
需要使用jdbc(jav数据库的连接)规范来操作数据
jdbc的规范
jdbc是一套接口规范
jdbc的实现类都是由各个数据库的生产商来提供的
只要学会了jdbc的接口和方法,就可以了
驱动
数据传输的桥梁
驱动指的是各个数据库生产商提供的是是实现类
需要来使用实现类,需要导入Mysql提交的驱动的jar包(千万不能忘记)
需要导入mysql-connector-java-5.1.13-bin.jar
jdbc开发的入门步骤

JDBC的快速入门
准备工作:创建数据库,创建表结构,添加若干条数据。
准备编写入门的程序
,需求:查询数据库中的数据?
加载驱动类(先导入开发的jar包,驱动包)
获取到连接(操作数据库,连接上数据库),过程中传入连接主机地址、用户名和密码
执行一些SQL语句(执行查询的SQL语句)
遍历结果集(查询的数据全部都封装到结果集中了)
释放资源(刚才连接的那些没有用的资源)
MySQL的快速入门的开发的步骤
了解记忆开发的步骤
先创建day07的数据库和表结构。
create database day07;use day07;create table t_user(id int primary key auto_increment,username varchar(30),password varchar(30),email varchar(30));insert into t_user values (null,'aaa','123','[aaa@163.com](mailto:aaa@163.com)');insert into t_user values (null,'bbb','456','[bb@163.com](mailto:bb@163.com)');insert into t_user values (null,'ccc','789','[ccc@163.com](mailto:ccc@163.com)');需求
查询的功能,把t_user表中的所有的数据全部都查询出来,显示到控制台上!!
开发的步骤
加载驱动使用DriverManager类
获取连接,返回Connection接口,说明连接上数据库的服务器
执行sql语句
编写sql语句
获取能执行sql语句的对象(Statement接口)
如果执行的查询的语句,返回的结果,封装ResultSet接口中,遍历该接口
释放资源(调用close()方法)
JDBC相关的接口和API(重要的方法必须掌握)
DriverManager类(驱动)
DriverManager
使用DriverManager来管理JDBC的驱动的实现类
作用
加载驱动
static void registerDriver(Driver driver)参数:传入的真正的参数其实是MySQL提供Driver的类
传入的参数
new Driver()这种方式不是特别好
过于依赖某一个实现类
驱动的jar包会加载两次(通过看源代码)

解决上述的两个问题
Class.forName("com.mysql.jdbc.Driver");注册驱动,切换一种写法,反射代码。加载com.mysql.jdbc.Driver类,只要该类一加载,static代码块就会执行(注册驱动)
获取连接
static Connection getConnection(String url, String user, String password)参数有3个
第一个参数:
jdbc:mysql://localhost:3306/day07
(背下来)
jdbc-- 代表的主协议mysql-- 子协议(有可能变化的)localhost-- 主机3306-- 默认的端口号day07-- 数据库
如果访问的是本地的自己的数据库,那么localhost: 3306就可以省略不写
mysql的命令 -uroot -proot
JDBC简写:jdbc:mysql://day07
第二个参数:root(用户名)
第三个参数:root(密码)
总结
管理驱动的
作用
加载驱动
Class.forName("com.mysql.jdbc.Driver");
获取连接
getConnection("jbc:mysql://day07","root","密码")
Connection接口(链接)
Connection
代表的数据库的连接,非常重要,并且连接比较稀有,用完一定要释放它
作用
能获取到执行SQL语句的对象(获取Statement接口)
Statement createStatement()-- 获取到Statement接口PreparedStatement prepareStatement(String sql)-- 获取到PreparedStatement接口,对象非常重要的,防止SQL注入的漏洞。
管理事务
void setAutoCommit(boolean autoCommit)-- 开启事务void commit()-- 提交事务void rollback()-- 回滚事务
总结
获取能执行SQL语句对象的方法
Statement createStatement()PreparedStatement prepareStatement(String sql)
只需要知道Connection接口可以管理事务就可以了!!
Statement接口(能执行SQL语句)
Statement接口
能执行SQL语句的对象
作用
能执行SQL语句
ResultSet executeQuery(String sql)-- 执行查询SQL语句的方法int executeUpdate(String sql)-- 能执行增删改的SQL语句
能执行批处理
void addBatch(String sql)-- 把SQL语句添加到批处理中void clearBatch()-- 清除批处理int[] executeBatch()-- 执行批处理
总结
就是能执行SQL语句
作用
执行SQL语句
执行批处理
ResultSet接口(代表结果集)
ResultSet接口
代表的是结果集,执行的是查询的SQL语句,把查询的数据的表格封装到ResultSet接口中,通过遍历该接口,获取到查询的结果!
总结
封装数据
ResultSet接口使用表格的方式来封装数据。
内部维护一个游标,默认指向的是第一行数据之前。
调用
next()方法来向下移动游标,移动到某一行,获取该行的数据。注意:游标默认只能向下移动。
获取值
根据字段的类型,调用不同的方法。
例如:
如果字段是
int或者bigint,使用getInt()或者getLong()。如果字段是
varcharchar类,使用getString()。使用
getObject()获取任何类型的数据,自己强转。
获取数据的方法都是重载的:
getInt(int index)-- 通过下标值来取值,默认从1开始。getInt(String s)-- 通过字段的名称来取值,比较常用的。
释放资源(必须要做的)
连接的对象必须要释放。
释放资源的代码一般会在finally代码块中释放。
一定会执行的。
释放的代码的标准:
if(conn != null){try {// 释放资源conn.close();} catch (SQLException e) {e.printStackTrace();}conn = null;}公共类 DBConnection,用于管理数据库连接工具类
package com.qcby.db;// 导入 java.sql 包中的 Connection 和 DriverManager 类,用于数据库连接操作import java.sql.Connection;import java.sql.DriverManager;public class DBConnection {String driver = "com.mysql.jdbc.Driver"; //5...//String driver = "com.mysql.cj.jdbc.Driver";//8...// 定义字符串变量 driver,指定 MySQL 5.x 版本的 JDBC 驱动类名// 注释中提到如果是 MySQL 8.x 及以上版本,应使用 "com.mysql.cj.jdbc.Driver"String url = "jdbc:mysql://127.0.0.1:3306/javaweb";// 定义字符串变量 url,指定数据库的连接地址// 这里表示连接本地(127.0.0.1)3306 端口上的名为 javaweb 的数据库String user = "root";// 定义字符串变量 user,指定数据库连接的用户名,这里为 rooString password = "root";//密码// 定义字符串变量 password,指定数据库连接的密码,这里为 rootpublic Connection conn;// 定义一个公共的 Connection 类型变量 conn,用于保存数据库连接对象public DBConnection() {try {Class.forName(driver);// 使用 Class.forName() 方法加载指定的数据库驱动类// 这一步是为了将驱动类注册到 DriverManager 中conn = (Connection) DriverManager.getConnection(url, user, password);//// 通过 DriverManager 的 getConnection() 方法建立与数据库的连接// 将返回的 Connection 对象赋值给成员变量 conn// if(!conn.isClosed())// System.out.println("Succeeded connecting to the Database!");} catch (Exception e) {e.printStackTrace();// 捕获并打印可能出现的异常,例如驱动类加载失败、数据库连接失败等}}public void close() {// 定义一个公共方法 close(),用于关闭数据库连接try {this.conn.close();// 调用 conn 的 close() 方法关闭数据库连接} catch (Exception e) {e.printStackTrace();}}}使用jdbc对数据库进行增删改查工具类
package com.qcby.db;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;public class MysqlUtil {//添加public static int add(String sql) {int i=0;DBConnection db = new DBConnection();// 创建一个 DBConnection 对象,用于建立数据库连接try { PreparedStatement preStmt = (PreparedStatement) db.conn.prepareStatement(sql);// 使用传入的 SQL 语句创建一个 PreparedStatement 对象,用于执行预编译的 SQL 语句i=preStmt.executeUpdate();// 执行 SQL 语句,返回受影响的行数preStmt.close();// 关闭 PreparedStatement 对象,释放资源db.close();// 关闭数据库连接} catch (Exception e) {e.printStackTrace();}return i;// 返回受影响的行数}//修改public static int update(String sql) {int i =0;DBConnection db = new DBConnection();try {PreparedStatement preStmt = (PreparedStatement) db.conn.prepareStatement(sql);i = preStmt.executeUpdate();preStmt.close();db.close();} catch (SQLException e) {e.printStackTrace();}return i;// 逻辑与 add 方法类似,用于执行修改操作,返回受影响的行数}//删除public static int del(String delstr) {int i=0;DBConnection db = new DBConnection();try { PreparedStatement preStmt = (PreparedStatement) db.conn.prepareStatement(delstr);i=preStmt.executeUpdate();preStmt.close();db.close();} catch (SQLException e){e.printStackTrace();}// 逻辑与 add、update 方法类似,用于执行删除操作,返回受影响的行数return i;}//查数量public static int getCount(String sql) {int sum = 0;DBConnection db = new DBConnection();try {Statement stmt = (Statement) db.conn.createStatement();// 创建一个 Statement 对象,用于执行静态 SQL 语句ResultSet rs = (ResultSet) stmt.executeQuery(sql);// 执行 SQL 查询,返回结果集while (rs.next()) {sum += rs.getInt(1);// 遍历结果集,将第一列的值累加到 sum 中}rs.close();// 关闭结果集db.close();// 关闭数据库连接} catch (Exception e) {}return sum;// 返回查询到的数量总和}//查找,返回json格式的数据public static String getJsonBySql( String sql,String[] colums){ArrayList<String[]> result = new ArrayList<String[]>();DBConnection db = new DBConnection();try {Statement stmt = (Statement) db.conn.createStatement();ResultSet rs = (ResultSet) stmt.executeQuery(sql);while(rs.next()){String[] dataRow = new String[colums.length];for( int i = 0; i < dataRow.length; i++ ) {dataRow[i] = rs.getString( colums[i] );// 从结果集中获取指定列的值,存入 dataRow 数组}result.add(dataRow);// 将 dataRow 数组添加到 result 列表中}rs.close();db.close();} catch (SQLException e) {e.printStackTrace();}return listToJson(result,colums);// 调用 listToJson 方法将结果列表转换为 JSON 字符串并返回}//将列表数据转换为json字符串的方法public static String listToJson( ArrayList<String[]> list,String[] colums) {String jsonStr = "{\"code\":0,\"msg\":\"ok\",\"data\":[";for(int i = 0; i < list.size(); i++) {String arr = "{";for( int j = 0; j < list.get(0).length; j++) {if( list.get(i)[j] == null || "NULL".equals(list.get(i)[j])) {arr += "\"" + colums[j] + "\":\"\"";}else {arr += "\"" + colums[j] + "\""+":" ;arr += "\"" + list.get(i)[j].replace("\"","\\\"") + "\"";}if( j < list.get(0).length - 1 ) {arr += ",";}}arr += "}";if( i < list.size() - 1 ) {arr += ",";}jsonStr += arr;}jsonStr += "]}";return jsonStr;}}SQL注入问题
//模拟登录的功能,通过用户名和密码从数据库中查询//演示sql注入的问题,漏洞,在已知用户名的情况下,通过sql语言关键字,登录系统。密码随意输入的//SQL注入产生原因是SQL语句的拼接,利用SQL关键字产生效果public class Test{public static void main(String[] args) {String result = login("aaa 'or' 1=1","123456sadfa")} //模拟登录的功能pulbic string login(String username,String password) {Class.forName(“com.mysql.jdbc.Driver”);Connection coon = DriverManager.getConnection("jdbc:mysql://127.0.0.0:3306/javaweb");Statement stmt = coon.createStatement();ResultSet res = stmt.executeQuery("select * from user where username = '""+username+"' and password = '"+password +"' ");if(res.next()) {return '登录成功'} else {return '登录失败'}if(res != null) {res.close()} if(stmt != null) {stmt.close()} if(coon != null) {coon.close()} }}什么是 SQL 注入?
SQL 注入是一种常见的 Web 安全漏洞,攻击者通过在用户输入中插入恶意 SQL 代码,干扰或控制数据库查询逻辑,从而窃取、篡改或破坏数据
示例:
正常查询:
SELECT * FROM users WHERE username = 'admin' AND password = '123456'攻击者输入:
' OR '1'='1(在用户名或密码字段中)恶意 SQL:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ''
由于
'1'='1'永远为真,攻击者可能绕过登录验证,直接获取所有用户数据。
SQL 注入的危害**
数据泄露:窃取敏感信息(如用户密码、信用卡号)。
数据篡改:修改或删除数据库中的数据。
数据库破坏:执行
DROP TABLE等危险操作,导致服务瘫痪。权限提升:获取数据库管理员权限,进一步攻击系统
SQL 注入的常见场景**
(1) 用户登录验证
攻击者输入恶意用户名或密码,绕过身份验证。
示例
SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx'--是 SQL 注释符,导致后续条件被忽略。
(2) 搜索功能
攻击者在搜索框中输入恶意 SQL,如:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx'可能泄露其他表的数据。
(3) 动态 SQL 拼接
如果代码直接拼接用户输入到 SQL 语句中,如:
String sql = "SELECT * FROM products WHERE name = '" + userInput + "'";攻击者输入:
' OR 1=1 --,导致查询所有产品。
SQL注入解决方式
预编译方式
把SQL语句中的参数使用?占位符来表示,先把SQL语句编译,格式固定的,再给?传入值,传入任何内容都表示值,数据库会判断SQL执行的结果。
//获取到连接Onnection coon = DriverManner.geetConnection()//使用?占位符String sql = "select * from user where username = ? and password = ?"//预编译SQL语句,把SQL语句固定 PrepareStatement stmt = connection.prepareStatement(sql)//需要给?设置值,传入的值是什么类型就是setxxx,第一个参数是第几个问号,第二个参数是传入的值stmt.setString(index,result) 事务
概述
事务是数据库提供的一个特性
组成各个数据的执行的单元,要么都成功,要么都不成功。

在JDBC中操作事务
1.操作事务,需要掌握的是Connection接口,提供了方法
void setAutoCommit(boolean autoCommit) -- 如果传入了false,设置mysql数据库的事务不默认提交。
void commit() -- 提交事务
void rollback() --回滚事务
2.Connection接口的2个作用
操作数据库,都需要使用Connection接口
管理事务
connection coon = null;//预编译执行SQL语句对象preparedStatement stmt =null;tyr{//获取到连接coon = DriverManner.getConnection();//使用?占位符Sting sql = " update user set money = money + ? where username = ?"//预编译SQL语句,把SQL语句固定stmt = coon.preparedStatment(sql)//开启事务,设置事务不自动提交,西药手动提交coon.setAutoCommit(false)//需要给?设值stmt.setDouble(1,-1000)stmt.setDouble(2,冠希)//长记性SQL语句,给冠希扣除1000元stmt.executUpdate()//--------------------------------------int a = 10 / 0stmt.setDouble(1,-000)stmt.setDouble(2,美美)stmt.executUpdate()//提交事务coon.commit()} catch(exception e){//回滚事务coon.rollback()}事务的特性
原子性:原子性对应的英文是Atomicity,即表示事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败;
一致性:一致性对应的英文是Consistency,事务执行后,数据库状态与其它业务规则保持一致。例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的;
隔离性:隔离性对应的英文是Isolation,是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰;
持久性:持久性对应的英文是Durability,指的是一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须得保证通过某种机制恢复数据。
事务的特性
原子性 -- 强调的是事务的不可分割的特性
一致性 -- 强调的是事务执行前后数据需要保证一致
隔离性 -- 强调的是多个事务同时操作一条记录,事务之间不能互相干扰
持久性 -- 强调的是事务一旦结束了,数据将永久的保存到数据库中
这些特性都是数据库提供的
不考虑事务的隔离性会引发的问题
脏读:一个事务读取到了另一个事务尚未提交的数据。(事务 A 修改数据但未提交,事务 B 读取了该数据,随后事务 A 回滚,导致事务 B 读取到无效数据。)
不可重复读:一个事务读取到了另一个事务已提交的更新数据,致使同一事务内多次查询结果不一致,侧重于数据更新(update)导致的问题。(事务 A 第一次读取数据,事务 B 修改并提交该数据,事务 A 再次读取时发现数据变化。)
虚读(幻读):一个事务读取到了另一个事务已提交的新插入数据,造成同一事务内多次查询结果不一致,强调的是插入(insert)操作向表中添加数据所引发的问题。(事务 A 查询某条件的数据,事务 B 插入符合该条件的新数据并提交,事务 A 再次查询时发现多了一条记录。)
设置事务的隔离级别
解决上述各种读的问题,设置数据库的隔离级别
事务的隔离级别(通过设置数据库的隔离级别,依据级别差异解决上述读取问题)
Read uncommitted -- 无法解决任何问题
Read committed -- 可避免脏读,但不可重复读和虚读有可能出现
Repeatable read -- 能避免脏读和不可重复读,虚读仍有产生的可能
Serializable -- 可避免脏读、不可重复读和虚读等各种读取问题
4种隔离级别在安全性和效率方面的表现
安全:Serializable > Repeatable read > Read committed > Read uncommitted
效率:Serializable < Repeatable read < Read committed < Read uncommitted
数据库均有自身默认的隔离级别
MySQL数据库,默认的隔离级别是Repeatable read,可避免脏读和不可重复读。
演示脏读
脏读:一个事务读取到了另一个事务未提交的数据。
进行测试
开启两个窗口,一个为A窗口(左),一个为B窗口(右)。
在A窗口中,查询A窗口的隔离级别:
select @@tx_isolation;设置A窗口的隔离级别为最低级别:
set session transaction isolation level read uncommitted;在两个窗口中,都开启事务:
start transaction;在B窗口中完成转账操作(冠希给美美转1000元):
update t_account set money = money - 1000 where username = '冠希';update t_account set money = money + 1000 where username = '美美';
注意:B窗口的事务未提交。
在A窗口中,查询美美的结果:
select * from t_account;在B窗口中,回滚事务:
rollback;
避免脏读:
避免脏读的发生,提高隔离级别。
set session transaction isolation level read committed;
在两个窗口中,都开启事务:
start transaction;在B窗口中完成转账操作(冠希给美美转1000元):
update t_account set money = money - 1000 where username = '冠希';update t_account set money = money + 1000 where username = '美美';注意:此时事务尚未提交,需观察A窗口能否读取到B创建但未提交的数据。
在A窗口中,查询美美的结果:
select * from t_account;在B窗口中提交事务:
commit;在A窗口中,再次查询:两次查询结果不一致,发生了不可重复读。
数据库连接池
概述
1. 总结
原先在程序运行过程中,需自行创建连接与销毁连接,这一过程较为耗费时间和资源。如今引入连接池,预先创建好部分连接,程序可从连接池中获取连接,使用完毕后归还至连接池。这种方式节省了创建与释放连接的时间,降低了性能消耗,同时连接池中的连接可起到复用作用,有效提升了程序性能。
2. 连接池(池参数,若不指定,有默认值)
初始大小:10个
最小空闲连接数:3个
增量:一次创建的最小单位(5个)
最大空闲连接数:12个
最大连接数:20个
最大的等待时间:1000毫秒
3. 4个参数
对于任何开源的连接池,以下4大参数均需自行设置:
驱动的名称:-- com.mysql.jdbc.Driver
连接:-- jdbc:mysql:///day14
用户名:-- root
密码:-- root
druid连接池
DataSource接口概述
DataSource接口性质:DataSource接口是由SUN公司提供的接口,任何开源的连接池或是自定义的连接池都必须要实现该接口。
DataSource接口方法
方法签名:
Connection getConnection()方法作用:开源的连接池已经实现了DataSource接口,通过调用
getConnection()方法就能够从连接池中获取到数据库连接。
连接池
初始化连接:在创建连接池的时候,会预先初始化好一些数据库连接。
连接归还:当程序使用完获取的连接后,需要将连接归还到连接池中。
常见的连接池
DBCP连接池
C3P0连接池
DRUID连接池
概述:Druid首先是一个数据库连接池。它是目前表现极为出色的数据库连接池,在功能、性能以及扩展性方面,均超越其他数据库连接池,如DBCP、C3P0、BoneCP、Proxool、JBoss DataSource等。Druid在阿里巴巴已经部署了超过600个应用,并且历经一年多生产环境大规模部署的严峻考验。
功能
替代功能:能够替换DBCP和C3P0。Druid提供了一个高效、功能强大且可扩展性良好的数据库连接池。
性能监控:具备数据库访问性能监控功能。Druid内置了功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能很有帮助。
密码加密:支持数据库密码加密。直接将数据库密码写在配置文件中是不安全的行为,容易导致安全问题,而Druid的DruidDriver和DruidDataSource都支持PasswordCallback。
日志监控:提供SQL执行日志功能。Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,用户可以按需选择相应的LogFilter,以监控应用的数据库访问情况。
使用:
1.导入开发的jar包druid-1.1.10 jar
2.编写测试程序
//创建连接池对象,从连接池中获取到连接对象DriudDataSource dataSouce = new DruidScouce();//设置4个参数 驱动类 地址 用户名 密码dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql:///jva_web");dataSuorce.setUserName("root");dataSource.setPassword("root");//设置初始化连接个数,默认是0dataSource.setInitialSize(5);//设置最大连接数dataSuource.setMaxActive(10)//最大等待时间,单位是毫秒,dataSource.setMaxWait(2000)// -----------连接池创建完成 //定义连接对象Connection coon = null;PreparedStatemtne stmt = null;//获取到连接对象coon = dataSource.getConnection()//编写SQL语句String sql = "insert into user values(null,?,?,?)"//预编译SQL语句stmt = coon.prepareStatement(sql)//设置值stmt.setString(1,"eee");stmt.setString(2,"lll");stmt.setString(3,666);//执行sqlstmt.executUpdate();//释放资源if(stmt != null) {stmt.close();}if(coon != null) {//把coon关闭了,其实连接池的底层已经对close()方法进行增强,以前是销毁连接,现在是归还连接coon.close(); }3.使用配置文件的方式
写一个配置文件driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://jaba-webusername=rootpassword=rootinitialSize=5maxActive=10maxWait=3000maxIdle=6minIdle=3//加载属性文件,使用工厂对象创建连接池对象properties pro = new Properties();//加载属性文件InputStream inputStream = Test.class.getResourceAsStream("配置文件的路径");pro.load(inputStream)//创建连接池对象DataSource dataSource = DruidDataSourceFactory.createDataSource(pro)//获取连接,预编译SQL语句dataSource.getContention();