目录
🌑1. JDBC的本质
🌑2.JDBC开发前的准备工作
🌕3.JDBC编程六步(重)
🌒4. JDBC常用操作实现
🌒5.处理查询结果集
🌓6.IDEA上使用JDBC
🌒7. 示例一: 用户登录业务介绍
🌔8.JDBC的事务自动提交机制的演示
🌔9. 工具类DBUtil
🌓10.JDBC实现模糊查询(?的使用)
🌔11.悲观锁(行级锁)和乐观锁
🌕12. 批处理
🌕13.数据库池
🌓14. Apache-DBUtils
🌕15. BasicDAO管理工具的使用
附件:
总结:
🍔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中得到连接
C3P0和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语句和返回想要的数据类型