🎄欢迎来到@dandelionl_的csdn博文,本文主要讲解JDBC篇, DAO, DBUtils, 批处理, 数据库池的相关知识🎄
🌈我是dandelionl_,一个正在为秋招准备的学弟🌈
🎆喜欢的朋友可以关注一下,下次更新不迷路🎆
Ps: 月亮越亮说明知识点越重要 (难度可能也越大)🌑🌒🌓🌔🌕
🍬目录
🍔1. JDBC的本质
🌟 JDBC: Java DataBase Connectivity
♨️ S是SUN公司指定的一套接口
🌟鉴于图中程序员只需要知道接口JDBC的调用和实现即可, 至于每个厂商的实现类duck不必掌握
1️⃣面向接口编程:
面向接口编程是一种抽象的, 高效的编程思想 (就像Java里面的多态的思想. 解耦合)
解耦合 : 降低程序的耦合度(乐高坏了只需要换一个零件, 不香?), 提高程序的扩展力(比如多态)
-
SUN公司写了一个接口
-
各大厂商对这个接口进行实现
-
这个接口实现了"多态"的编程思想
-
-
面向接口编程的程序员
-
只需要调用JDBC即可
-
底层的实现是厂商写的代码, 程序员也不用去关心
-
-
厂商写的实现接口的.class文件, 也是驱动(Driver)
-
数据库驱动都是以.jar包的形式存在, .jar包当中有很多.class文件, 它们实现了JDBC接口
2️⃣ 模拟实现JDBC:
public interface JdbcInterface {
// 连接
Object getConnetcion();
// crud
void crud();
// 关闭连接
void close();
}
class MysqlToJdbc implements JdbcInterface {
@Override
public Object getConnection() {
System.out.println("得到Mysql的连接");
return null;
}
@Override
public void crud() {
System.out.println("完成了Mysql的crud功能");
}
@Override
public void close() [
Syste.out.println("完成了Mysql的关闭功能");
}
}
class OracleToJdbc implements JdbcInterface {
@Override
public Object getConnection() {
System.out.println("得到Oracle的连接");
return null;
}
@Override
public void crud() {
System.out.println("完成了Oracle的crud功能");
}
@Override
public void close() [
Syste.out.println("完成了Oracle的关闭功能");
}
}
public class Test {
public static void main(String[] args) {
JdbcInterface jdbc = new MysqlToJdbc();
jdbc.getConnection();
jdbc.crud();
jdbc.close();
System.out.println("+++++++++++++++++++++++++++++++");
jdbc = new OracleToJdbc();
jdbc.getConnection();
jdbc.crud();
jdbc.close();
}
}
配置文件的作用 : 在配置文件里写上数据库以及数据库的配置信息, 那么程序员只需要从配置文件中得到这些信息, 进行面向接口的编程即可, 不用在乎具体信息, 只需要关注功能实现即可
这更好的实现了面向接口编程
🍟 2.JDBC开发前的准备工作
如果是记事本的话, 需要把.jar包配置到环境变量classpath当中
如果是IDEA的话, 只需要把.jar包加载到项目中去即可(具体操作对应章节6)
(👉环境变量配置)
🌭 3.JDBC编程六步(🍕重🍕)
-
注册驱动, 必须将jar(数据库厂商写的驱动)文件引入
-
获取连接
-
获取数据库操作对象
-
执行SQL
-
excuteUpdate执行DML(insert, delete, update)
-
excuteQuery(select)
-
-
处理查询结果集(有上一步才有这一步)
-
释放资源
// 注册驱动(老版写法认识即可, 后面介绍新版写法)
com.mysql.cj.jdbc.Driver driver = new com.mysql.cj.jdbc.Driver();
// 类加载(在新版中, 可不写, 但是为了代码可读性和安全性尽量写上)
Class.forName("com.mysql.cj.jdbc.Driver");
// 注意上面参数是一个字符串, 字符串可以写到xxx.properties文件中
// 获取Mysql的连接
String url = "jdbc:mysql://localhost:3306/your_database_name";
Properties properties = new Properties();
properties.setProperty("user", "XXXX");
properties.setProperty("password", "XXXX");
Connection connect = driver.connect(url, properties);
// 获取数据库操作对象
Statement statement = connect.createStatement();
// 执行SQL
String sql = "insert into XXXX values('XXXX', 'XXXX', XXXX)";
int rows = statement.executeUpdate(sql); // 返回值是:影响数据库中的记录条数
// 处理查询结果集(有上一步才有这一步)
// 对ResultSet的处理
// 释放资源
statement.close();
connect.close();
🎗️背会下面6
- 获取驱动
- 建立连接
- 获取执行SQL的对象
- 执行SQL
- [ 处理查询结果集 ]
- 释放资源
⚠❗jdbc:mysql://localhost:3306❗这句话的解释:
-
URL统一资源定位器
-
协议(jdbc:mysql)
-
通信之前提前定好的数据传送格式
-
-
IP(localhost)
-
PORT(3306)
-
资源名(表名)
-
🍿 4. JDBC常用操作实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;
Properties properties = new Properties();
// 引入了配置文件
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
// 加载驱动可以不写但建议写一下
Class.forName(driver);
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
// 创建Statement对象并执行SQL查询:
Statement statement = connection.createStatement();
String sql = "SELECT * FROM mytable";
ResultSet resultSet = statement.executeQuery(sql);
// 处理查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
// 其他字段...
// 处理数据
System.out.println("ID: " + id + ", Name: " + name);
}
// 释放资源
java
resultSet.close();
statement.close();
connection.close();
🍕 5.处理查询结果集
当使用SQL中的查询语句的时候, 查询的结果会以结果集的形式返回一个集合
我们将对查询结果集进行处理, 输出或者其他操作(是DAO和JavaBean的结合接下来会重点讲解)
⚠:结果集有一个指针, 这个指针开始时指向第一行的上面, 调用.next()会指向第一行
读到一行, 如果要将每一列读取出来 (记住JDBC中的下标都是从1开始的)
读到最后会指向空代表已经没有数据可读, 如图中的最后的判断点
为了使程序更加健壮, getString的时候参数传的是查询结果集中的列名
🥙 6.IDEA上使用JDBC
Java工程导入依赖
-
项目创建lib文件夹
-
将我桌面上的那个jar包复制到这个文件夹下面
-
然后将粘贴进来的包右击Add as library
-
添加完毕
添加Liberal的方式
-
右击项目中的Open Moudule Settings
-
👉 点击
-
👉 点击
-
👉点击
-
👉点击
-
👉点击
-
👉结束
🥪 7. SQL注入
⚠:SQL注入
用户输入的信息中含有SQL关键字的语句, 并且这些关键字参与了SQL语句的编译过程, 导致SQL语句的原义被扭曲, 进而达到SQL注入, 字面意思就是本来以字符常量的身份执行, 但是由于关键字起到了SQL注入的作用, 所以称为SQL注入
SQL注入的解决:
-
⚙️ 只要是含有SQL关键字的语句不进行编译就可以
-
⚙️ PreparedStatement : 预编译的数据库操作对象(提前将SQL语句进行编译, 传参数的时候就不需编译了)
-
继承了Statement接口
-
原理:预先对SQL语句的框架进行编译, 然后再给SQL语句传"值"
-
所以你在输入的值就只是一个字符串, 不参加编译
-
因为只对SQL框架进行了编译(发送SQL语句框架给DMBS, 然后DMBS进行SQL语句的预编译)
-
-
Statement | PreparedStatement | |
---|---|---|
注入问题 | 存在SQL注入 | 不存在SQL注入 |
编译问题 | 编译一次, 执行一次 | 编译一次, 可多次执行 |
安全问题 | 没有安全检查 | 会在编译阶段做类型的安全检查(String) |
⚠: 必须使用Statement的情况:
-
需要SQL注入和拼接的时候
-
网页中要进行筛选时, 会给SQL语句发(desc 和 asc), 这时候就需要SQL注入来解决了
-
🌮 8.JDBC的事务自动提交机制的演示
测试结果 : JDBC中只要执行任意一条DML语句, 就自动提交(默认是自动提交, 可以改变参数来设置为手动的提交方式)一次(Debug的时候发现打了断点之前的程序已经出结果了, 说明执行一条就提交一次)
但是在实际业务中, 通常都是N条DML语句共同联合才能完成的, 必须保证他们这些DML语句在同一事务中同时成功或者同时失败.
🌯 9. 工具类JDBCUtils
显然连接和释放资源是很常用的, 为了减少代码冗余, 增加代码复用, 所以将连接和释放资源封装起来, 方便使用
public class JDBCUtils {
//定义相关的属性(4个), 因为只需要一份,因此,我们做出static
private static String user; //用户名
private static String password; //密码
private static String url; //url
private static String driver; //驱动名
//在static代码块去初始化
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的属性值
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
Class.forName(driver);
} catch (Exception e) {
//在实际开发中,我们可以这样处理
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//连接数据库, 返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
//判断是否为null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//将编译异常转成运行异常抛出
throw new RuntimeException(e);
}
}
}
🫔 10.JDBC实现模糊查询(?的使用)
PreparedStatement的使用, 返回ResultSet查询结果集
try {
Connection connection = DriverManager.getConnection(url, username, password);
PreparedStatement statement = connection.prepareStatement("SELECT * FROM employees WHERE name LIKE ?")) {
// 设置模糊查询参数
String keyword = "John"; // 搜索关键字
statement.setString(1, "%" + keyword + "%");
// 执行查询
ResultSet resultSet = statement.executeQuery();
// 处理查询结果
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
// 处理每条记录...
System.out.println("ID: " + id + ", Name: " + name);
}
} catch (SQLException e) {
e.printStackTrace();
}
11.悲观锁(行级锁)和乐观锁
乐观锁和悲观锁的区别就是 : 他们对待并发的态度是否是悲观和乐观的
-
悲观锁对待并发就是悲观的, 它假设并发冲突, 因此在操作数据之前就获取了锁
-
乐观锁对待并发就是乐观的, 它假设并发冲突是很少发生的, 因此不对数据进行加锁, 而是在提交更新时检查是否有其他事务对数据进行了修改(版本号)
-
如果发现该事务版本号没有发生变化, 那么就更新操作
-
如果发现该事务版本号发生了变化, 那么就回滚, (或者重新尝试)
-
行级锁的实例演示:
Connection conn = DBUtil.getConnection();
String sql = "select ename, job, sal from emp where job = ? for update";
ps = conn.prepareStatement(sql);
ps.setString(1, "MANAGER");
re = ps.executeQuery();
while(re.next()) {
System.out.println(rs.getString("ename")+","+rs.getString("jon")+","+rs.getDouble("sal"));
}
conn.setAutoCommit(false);
comm.commit();
🍰 12. 批处理
Batch批处理, 将1000条SQL语句一起发送给MySQL数据库进行处理, 而且搭配PreparedStatement使用可以使其只编译一次, 那么就只需要一次编译和5次发送(一次200条), 执行多个SQL语句的机制,以减少与数据库的通信次数,提高性能。
执行批处理的步骤:
-
创建
Statement
或PreparedStatement
对象:首先需要创建一个Statement
或PreparedStatement
对象,用于执行SQL语句。 -
设置批处理模式:通过调用
Statement
或PreparedStatement
对象的addBatch()
方法,将要执行的SQL语句添加到批处理队列中。可以多次调用addBatch()
方法,添加多个SQL语句。 -
执行批处理:通过调用
Statement
或PreparedStatement
对象的executeBatch()
方法,执行批处理队列中的所有SQL语句。该方法将返回一个int[]
数组,表示每个SQL语句执行后所影响的行数。 -
处理执行结果:根据需要,可以对批处理的执行结果进行处理。例如,可以根据
int[]
数组中的返回值判断每个SQL语句的执行成功与否。
🍡 13.数据库池
🗿 问题的引出:
传统的JDBC数据库连接, 每次向数据库建立连接的时候都要将Connection加载到内存中, 再验证IP地址, 用户名和密码, 需要连接数据库的时候, 就向数据库发送一个要求(建立连接的过程需要系统资源的支持), 这说明频繁的连接操作将占用很多的系统资源, 容易造成服务器奔溃
-
连接开销:与数据库建立连接是一项开销较大的操作,涉及网络通信、身份验证、资源分配等步骤。频繁地创建和关闭连接会增加系统的开销。
-
并发需求:随着应用程序和用户量的增加,数据库的并发访问需求也相应增加。如果每个用户请求都独立创建一个连接,会导致连接资源的浪费和数据库的压力过大。
-
长时间连接:有些应用场景,比如Web应用,需要保持与数据库的持久连接,以提高响应速度。但长时间的持久连接可能会占用数据库的并发连接数,造成资源浪费。
那么可以采用数据库连接池化技术来解决上述问题
👉 它通过预先创建一定数量的数据库连接,并将这些连接保存在连接池中,以便随时供应用程序获取和使用。连接池负责对连接进行管理、分配和释放,从而减少了连接的创建和关闭操作,提高了系统性能和资源利用率。👈
⚠:close()是放回到数据库池而不是关闭连接
🎄🎄 数据库池连接的两种方法:🎄🎄
操作(自己总结)
-
C3P0
-
建立一个数据库池
-
对数据库池进行初始化(起始大小和最大的大小)
-
得到连接
-
-
德鲁伊德
-
配置文件
-
将配置信息传参给Druid仓库函数
-
从Druid中得到连接
-
CP30 | Druid | |
---|---|---|
功能特性 | 功能丰富而稳定 | 高性能、高可用性的数据库连接池 |
性能与扩展性 | 在高并发和大规模应用中,由于其单一连接池的设计,存在性能瓶颈 | 适用于高并发和大规模应用,具有更好的性能和扩展性 |
配置和使用难度 | 配置相对简单,并且文档详尽,易于上手和使用 | 配置较为复杂,需要更深入的理解和配置,但也提供了丰富的配置选项和灵活性 |
监控与统计 | 提供了基本的连接池监控功能 | 提供了强大的监控和统计功能 |
🍔 14. Apache-DBUtils
问题的引出:
传统连接数据得到的数据查询集的缺点
-
关闭connection后, result结果集无法使用
-
结果集合与connection是关联的, 即如果关闭连接就不能再使用结果集
-
-
resultSet不利于数据的管理
-
方法太少并且枯燥, 只有自己的get[列]的方法, 所以不利于处理和管理
-
模拟实现实现DBUtils:
-
首先是建立一个关系类, 该类来接收每一条查询语句
-
当断开连接时并不影响该类对象的数据的数据
-
而且可以更好地对数据进行操作
actors.add(new Actor(id, name, sex, borndate, phone));
DBUtils的使用:
-
commons-dbutils 是 Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封 使用dbutils能极大简化jdbc编码的工作量[真的]。
-
DbUtils类
-
QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
-
使用QueryRunner类实现查询
-
ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
-
-
ArrayHandler:把结果集中的第一行数据转成对象数组。
-
ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
-
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
-
BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
-
ColumnListHandler:将结果集中某一列的数据存放到List中。
-
KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key。
-
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
-
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
代码解读
//(1) query 方法就是执行 sql 语句,得到 resultset ---封装到 --> ArrayList 集合中
//(2) 返回集合
// 以下是参数解释
//(3) connection: 连接
//(4) sql : 执行的 sql 语句
//(5) new BeanListHandler<>(Actor.class): 在将 resultset -> Actor 对象 -> 封装到 ArrayList
// 底层使用反射机制 去获取 Actor 类的属性,然后进行封装
//(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数 Object... params
//(7) 底层得到的 resultset ,会在 query 关闭, 关闭 PreparedStatment
//以上底层代码你都看过, 不记得就去再看一遍
// 分析 queryRunner.query 方法:
public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
PreparedStatement stmt = null;//定义 PreparedStatement
ResultSet rs = null;//接收返回的 ResultSet
Object result = null;//返回 ArrayList
try {
stmt = this.prepareStatement(conn, sql);//创建 PreparedStatement
this.fillStatement(stmt, params);//对 sql 进行 ? 赋值
rs = this.wrap(stmt.executeQuery());//执行 sql,返回 resultset
result = rsh.handle(rs);//返回的 resultset --> arrayList[result] [使用到反射,对传入 class 对象处理]
} catch (SQLException var33) {
this.rethrow(var33, sql, params);
} finally {
try {
this.close(rs); //关闭 resultset
} finally {
this.close((Statement) stmt); //关闭 preparedstatement 对象
}
}
return result;
}
所以:
-
DBUtils实际过程
-
对得到执行SQL的对象步骤进行了封装
-
对执行SQL语句的步骤进行封装
-
最后将得到的查询集进行封装, 然后处理返回
-
🥙 15. BasicDAO管理工具的使用
问题的引出:
-
apache-dbutils+ Druid简化了JDBC开发,但还有不足:
-
SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
-
对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型
-
将来的表很多,业务需求复杂,不可能只靠一个Java类完成
-
引出=》 BasicDAO画出示意图,看看在实际开发中,应该如何处理
-
⚠:
-
DAO: data access object 数据访问对象
-
这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作
-
在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如 Customer表-Customer.java类(javabean)-CustomerDao.java
总的来说是实现多个javaBean然后将对JavaBean的操作进行封装, 最后再呈现给用户, 程序员写代码更优雅, 解耦合很明显
附件:
powerDesigner的使用
-
模型的建立
-
表的命名
-
表的建立
-
模型的保存
-
SQL代码的保存
-
打开模型和表
-
将SQL文件拖动到source处即可运行
-
记得SQL文件的编译类型是UTF-8(为了正常编译)
-
终端可能和MySQL实际上的显示有区别, 所以要注意但不必担心
🎄🎄 总结:🎄🎄
🗿 事务: 为了全体执行
🗿 悲观锁 : 让select的信息有安全机制
🗿 批处理 : 多个SQL语句一次性发送给数据库执行(在连接的时候就说明了, 以批处理的方式得到连接)
🗿 数据库池 : 在池中放有一定数的连接好的connection, 可以直接用, 不够了就排队等着, 解决了并发遇到的问题, Druid方法
🗿 Apache-DBUtils : 为了让处理结果集变得更专业
🗿 BasicDAO : 对JavaBean分别进行实现, 并对每个JavaBean的操作进行封装, 这样可以直接传SQL语句和返回想要的数据类型