???? 博主简介:
???? Java领域新星创作者
???? 阿里云开发者社区专家博主、星级博主、技术博主
???? 交流社区:BoBooY(优质编程学习笔记社区)
前言:上一节我带大家上手了JDBC的基本代码,这节我们仔细讲一下JDBC的API和数据库连接池。
3、JDBC API详解
3.1、DriverManager
DriverManager(驱动管理类)作用:注册驱动、获取数据库连接
- 注册驱动
registerDriver方法是用于注册驱动的,但是我们之前做的入门案例并不是这样写的。而是如下实现
Class.forName("com.mysql.jdbc.Driver");
我们查询MySQL提供的Driver类,看它是如何实现的,源码如下:
在该类中的静态代码块中已经执行了 DriverManager 对象的registerDriver() 方法进行驱动的注册了,那么我们只需要加载 Driver 类,该静态代码块就会执行。而Class.forName("com.mysql.jdbc.Driver");
就可以加载Driver 类。
提示:MySQL 5之后的驱动包,可以省略注册驱动的步骤,自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
- 获取数据库连接
参数说明:
- user :用户名
- password:密码
- url : 连接路径
语法:
jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
示例:
jdbc:mysql://localhost:3306/jdbc
补充:
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对
配置 useSSL=false 参数,禁用安全连接方式,解决警告提示
3.2、Connection
Connection(数据库连接对象)作用:获取执行 SQL 的对象、管理事务
3.2.1、获取执行对象
- 普通执行SQL对象
Statement createStatement()
入门案例中就是通过该方法获取的执行对象。
- 预编译SQL的执行SQL对象:防止SQL注入(重要!!!)
PreparedStatement prepareStatement(sql)
- 执行存储过程的对象
CallableStatement prepareCall(sql)
3.2.2、事务管理
回顾一下MySQL事务管理的操作:
-
开启事务 :
BEGIN; 或者 START TRANSACTION;
-
提交事务 :
COMMIT;
-
回滚事务 :
ROLLBACK;
MySQL默认是自动提交事务
JDBC事务管理的方法
Connection几口中定义了3个对应的方法:
- 开启事务
参与autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。而开启事务需要将该参数设为为false。
- 提交事务
- 回滚事务
案例测试事务管理
- 编写代码
package com.bby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* @author BoBooY
* @date 2022/10/29 21:47
*/
public class JdbcTransaction {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql:///jdbc?useSSL=false";
String username = "root";
String password = "1234";
String sql = "update acount set money = 1002 where id = 1";
String sql2 = "update acount set money = 1002 whe id = 1"; //注意这里SQL语句故意将where写错
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
Statement statement = connection.createStatement();
try {
connection.setAutoCommit(false); // 开启手动事务
//执行SQL语句并处理结果
int count = statement.executeUpdate(sql);
System.out.println(count);
int count2 = statement.executeUpdate(sql2);
System.out.println(count2);
//提交事务
connection.commit();
} catch (Exception e) {
//程序出现异常,回滚事务
connection.rollback();
e.printStackTrace();
}
}
}
- 执行程序,看控制台结果
- 数据库更新前
- 数据库更新后
这里程序出现异常,进行了事务的回滚,所以数据都没有被更新
- 将代码更改正确
String sql2 = "update acount set money = 1002 wher id = 2";
- 执行程序测试
3.3、Statement
3.3.1、概述
Statement对象的作用就是用来执行SQL语句。而针对不同类型的SQL语句使用的方法也不一样。
- 执行DDL、DML语句
- 执行DQL语句
3.3.2、代码演示
此处只展示核心代码,具体代码可以参考上面的代码案例
- DML语句
// 定义sql
String sql = "update account set money = 3000 where id = 1";
// 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
// 执行sql
int count = stmt.executeUpdate(sql); //执行完DML语句,受影响的行数
- 执行DDL语句
// 定义sql
String sql = "drop database db2";
// 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
// 执行sql
int count = stmt.executeUpdate(sql); //执行完DDL语句,可能是0
3.4、ResultSet
3.4.1、概述
ResultSet(结果集对象)作用:封装了SQL查询语句的结果
执行了DQL语句后就会返回该对象,对应执行DQL语句的方法如下:
ResultSet executeQuery(sql):执行DQL 语句,返回 ResultSet 对象
那么我们就需要从 ResultSet 对象中获取我们想要的数据。ResultSet 对象提供了操作查询结果数据的方法,如下:
如下图为执行SQL语句后的结果
一开始光标指定于第一行前,如图所示红色箭头指向于表头行。当我们调用了 next() 方法后,光标就下移到第一行数据,并且方法返回true,此时就可以通过 getInt(“id”) 获取当前行id字段的值,也可以通过 getString(“name”) 获取当前行name字段的值。如果想获取下一行的数据,继续调用 next() 方法,以此类推。
3.4.2、代码演示
- 编写JdbcResultSet
package com.bby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* @author BoBooY
* @date 2022/10/30 14:25
*/
public class JdbcResultSet {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc?useSSL=false";
String username = "root";
String password = "1234";
Connection connection = DriverManager.getConnection(url, username, password);
Statement statement = connection.createStatement();
//定义查询的SQL语句
String sql = "select * from acount";
//执行查询语句,获取结果集
ResultSet resultSet = statement.executeQuery(sql);
//遍历结果集
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String money = resultSet.getString("money");
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("money:" + money);
System.out.println("-----------------");
}
resultSet.close();
statement.close();
connection.close();
}
}
- 查看控制台输出
3.5、PreparedStatement
3.5.1、SQL注入
SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。
- 代码模拟SQL注入问题
package com.bby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* @author BoBooY
* @date 2022/10/30 14:41
*/
public class JdbcSqlInjection {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc?useSSL=false";
String username = "root";
String password = "1234";
Connection connection = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "abcdefg";
String pwd = "' or '1' = '1";
String sql = "select * from users where name = '" + name + "' and password = '" + pwd + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
// 判断登录是否成功
if (resultSet.next()) {
System.out.println("登录成功~");
} else {
System.out.println("登录失败~");
}
resultSet.close();
statement.close();
connection.close();
}
}
上面代码是将用户名和密码拼接到sql语句中,拼接后的sql语句如下:
select * from users where name = 'abcdefg' and password = '' or '1' = '1'
从上面语句可以看出条件name = 'abcdefg' and password = ''
不管是否满足,而 or
后面的 '1' = '1'
是始终满足的,最终条件是成立的,就可以正常的进行登陆了。
- 所以不管登录的密码是否正确都可以登录成功,控制台输出如下
3.5.2、PreparedStatement 概述
PreparedStatement作用:预编译SQL语句并执行,预防SQL注入问题
- 获取 PreparedStatement 对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from users where name = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement ps = connection.prepareStatement(sql);
- 设置参数值
上面的sql语句中参数使用 ? 进行占位,在之前之前肯定要设置这些 ? 的值。
PreparedStatement对象:
setXxx(参数1,参数2); //给 ? 赋值, 参数1是编号(从1开始) 参数2是值
(1)Xxx:数据类型 ; 如 setInt (参数1,参数2)
(2)参数1: ?的位置编号,从1 开始 参数2: ?的值
- 执行SQL语句
executeUpdate(); // 执行DDL语句和DML语句
executeQuery(); // 执行DQL语句
注意:调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了。
3.5.3、代码演示
- 编写JdbcPreparedStatement
package com.bby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author BoBooY
* @date 2022/10/30 17:37
*/
public class JdbcPreparedStatement {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql:///jdbc?useSSL=false";
String username = "root";
String password = "1234";
String name = "abcdefg";
String pwd = "' or '1' = '1";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
//定义SQL语句
String sql = "select * from users where name = ? and password = ?";
//预编译SQL语句
PreparedStatement ps = connection.prepareStatement(sql);
//填充占位符
ps.setString(1, name);
ps.setString(2, pwd);
//执行SQL语句
ResultSet resultSet = ps.executeQuery();
//判断登录是否成功
if(resultSet.next()) {
System.out.println("登陆成功!");
} else {
System.out.println("登录失败");
}
resultSet.close();
ps.close();
connection.close();
}
}
- 查看控制台结果
执行上面语句就可以发现不会出现SQL注入漏洞问题了。那么PreparedStatement又是如何解决的呢?它是将特殊字符进行了转
义,转义的SQL如下:
select * from `users` where name = 'abcdefg' and password = '\'or \'1\' = \'1'
3.5.4、PreparedStatement原理
PreparedStatement 好处:1.预编译SQL,性能更高。2.防止SQL注入:将敏感字符进行转义
Java代码操作数据库流程如图所示:
-
将sql语句发送到MySQL服务器端
-
MySQL服务端会对sql语句进行如下操作
- 检查SQL语句
检查SQL语句的语法是否正确。 - 编译SQL语句。将SQL语句编译成可执行的函数。
检查SQL和编译SQL花费的时间比执行SQL的时间还要长。如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行。这样就提高了性能。 - 执行SQL语句
- 检查SQL语句
-
MySQL服务端将结果返回
4、数据库连接池
4.1、数据库连接池简介
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
- 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
- 好处:资源重用、提升系统响应速度、避免数据库连接遗漏
之前我们代码中使用连接使没有使用都创建一个Connection对象,使用完毕就会将其销毁。这样重复创建销毁的过程是特别耗费计算机的性能的及消耗时间的。而数据库使用了数据库连接池后,就能达到Connection对象的复用,如下图:
连接池是在一开始就创建好了一些连接(Connection)对象存储起来。用户需要连接数据库时,不需要自己创建连接,而只需要从连
接池中获取一个连接进行使用,使用完毕后再将连接对象归还给连接池;这样就可以起到资源重用,也节省了频繁创建连接销毁连接
所花费的时间,从而提升了系统响应的速度。
4.2、数据库连接池实现
- 标准接口:DataSource
官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。该接口提供了获取连接的功能:
Connection getConnection()
那么以后就不需要通过 DriverManager
对象获取 Connection
对象,而是通过连接池(DataSource)获取 Connection
对象。
- 常见的数据库连接池:DBCP 、C3P0 、Druid
我们现在使用更多的是Druid,它的性能比其他两个会好一些
- Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一
4.3、Driud 的使用
- 导入jar包 druid-1.1.12.jar
将druid的jar包放到项目下的lib下并添加为库文件
- 编写配置文件 druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbc
useSSL=false&useServerPrepStmts=true
username=root
password=1234
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
- 编写Java代码(JdbcDruid)
package com.bby;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @author BoBooY
* @date 2022/10/30 19:12
*/
public class JdbcDruid {
public static void main(String[] args) throws Exception {
// 加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
// 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
// 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
System.out.println(connection); //获取到了连接后就可以继续做其他操作了
}
}
- 运行查看控制台结果
尾言:创作不易,如果本文的内容对您有帮助,还望客官可以三连支持一下博主,????(点赞)+✏️(评论)+⭐️(收藏)是我创作的巨大动力!