一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
写在前面👀
今天讲讲JDBC的常用API:DriverManager 类、Connection 接口、Statement 接口、ResultSet 接口、PreparedStatement 接口。
一、DriverManager 类🍕
数据库驱动管理类,用来注册驱动和连接数据库
1️⃣注册驱动🍔
- 我们加载注册驱动用的是以下代码👇
Class.forName("com.mysql.cj.jdbc.Driver");
- 其实在Driver接口的实现类里出现过
DriverManager类的registerDriver注册驱动方法 - com.mysql.jdbc.Driver 源码如下👇
- 使用
DriverManager注册给定的驱动程序。 新加载的驱动程序类应调用方法registerDriver以使其自身为DriverManager。
- 使用
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver()); //注册数据库驱动
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
- 提示:目前普遍使用的JDBC版本已经可以不用注册驱动而直接使用。
2️⃣连接数据库🍟
1. getConnection方法🌭
/* DriverManager类中的静态方法 */
Connection getConnection (String url, String user, String password)
/* 示例 */
Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "root", "123456");
2.连接数据库的几个参数🍿
| JDBC 连接数据库的三个参数 | 说明 |
|---|---|
| url | 连接数据库的 URL 地址格式:协议名:子协议://服务器名或 IP 地址:端口号/数据库名?参数=参数值 连接MySQL示例:jdbc:mysql://localhost:3306/db |
| user | 数据库登录的用户名 |
| password | 数据库登录的密码 |
- 提示:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:
jdbc:mysql:///数据库名称
二、Connection 接口🧂
Connection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象。
主要作用:1.获取执行SQL的对象 2.管理事务
1️⃣获取执行SQL的对象🥚
//普通执行SQL对象
Statement createStatement()
//预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement(sql)
//执行存储过程的对象
CallableStatement prepareCall(sql)
2️⃣事务管理🍳
1.开启事务🧇
void setAutoCommit (boolean autoCommit) throws SQLException
- 参数autoCommit
true表示启用自动提交模式;false表示启用手动提交事务 - MySQL默认是自动提交事务的,所以开启事务需要将autoCommit设置为
false
2.提交事务🥞
void commit() throws SQLException
3.回滚事务🧈
void rollback() throws SQLException
4.代码示例🍞
try {
// 关闭自动提交:
conn.setAutoCommit(false);
// 执行多条SQL语句:
......
// 提交事务:
conn.commit();
} catch (SQLException e) {
// 回滚事务:
conn.rollback();
} finally {
//恢复默认自动提交
conn.setAutoCommit(true);
//释放资源
conn.close();
}
三、Statement 接口🥐
用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象
1️⃣执行DDL、DML语句🥨
int executeUpdate (String sql) throws SQLException
- SQL语句可以是INSERT , UPDATE ,或DELETE处理数据的
DML语句,int返回受影响的行数;也可以是和数据库、表相关的DLL语句,不过DDL语句是没有返回值的
2️⃣执行DQL查询语句🥯
ResultSet executeQuery (String sql) throws SQLException
- 返回的ResultSet对象是一个结果集
四、ResultSet 接口🥖
作用:封装数据库查询的结果集。
结果集指包含在 ResultSet 对象中的行和列的数据
1️⃣常用方法🧀
| ResultSet 接口中的方法 | 描述 |
|---|---|
boolean next() | 1. 游标向下移动 1 行 2. 返回 boolean 类型,如果下一行还有记录,返回 true,否则返回 false |
数据类型 getXxx() | 1. 通过字段名,参数是 String 类型。返回不同的类型 2. 通过列号,参数是整数,从 1 开始。返回不同的类型 |
2️⃣常用数据类型转换表🥗
| SQL 类型 | JDBC对应方法 | 返回类型 |
|---|---|---|
| BIT(1) bit(n) | getBoolean() | boolean |
| TINYINT | getByte() | byte |
| SMALLINT | getShort() | short |
| INT | getInt() | int |
| BIGINT | getLong() | long |
| CHAR,VARCHAR | getString() | String |
| Text(Clob) Blob | getClob getBlob() | Clob Blob |
| DATE | getDate() | java.sql.Date 只代表日期 |
| TIME | getTime() | java.sql.Time 只表示时间 |
| TIMESTAMP | getTimestamp() | java.sql.Timestamp 同时有日期和时间 |
3️⃣代码示例🥙
- 使用完毕以后先关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection
@Test
public void testResultSet() throws Exception {
//1. 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from stu_info";
//4. 获取statement对象
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql);
// 创建集合
List<Student> list = new ArrayList<>();
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
Student Student = new Student();
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
byte gender = rs.getByte("gender");
float math = rs.getFloat("math");
float english = rs.getFloat("english");
String hometown =rs.getString("hometown");
//6.2赋值
Student.setId(id);
Student.setName(name);
Student.setAge(age);
Student.setGender(gender);
Student.setEnglish(english);
Student.setMath(math);
Student.setHometown(hometown);
//6.3存入集合
list.add(Student);
}
//6.4打印集合
System.out.println(list);
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
- 演示结果如下👇
五、PreparedStatement 接口🥪
PreparedStatement 是 Statement 接口的子接口,继承父接口中所有的方法。它能
预编译 SQL 语句,能有效防止SQL注入问题。
1️⃣SQL注入问题🌮
SQL 注入就是在用户输入的字符串中加入 SQL 语句,修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法
- 用代码模拟一下SQL注入👇
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//连接数据库
String url = "jdbc:mysql:///db";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String name = scanner.nextLine();
System.out.print("请输入密码:");
String pwd = scanner.nextLine();
String sql = "select * from user where name = '" + name + "' and password = '" + pwd + "'";
// 获取stmt对象
Statement stmt = conn.createStatement();
// 执行sql
ResultSet rs = stmt.executeQuery(sql);
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
System.out.println("id:"+rs.getInt("id") );
System.out.println("真正的用户名:"+rs.getString("name"));
System.out.println("真正的密码:" + rs.getString("password"));
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
-
演示结果如下👇
-
可以发现即使输入错误的用户名和密码也能登录成功,所以说SQL注入很危险
-
实际原理也很简单,就是利用or 后面的**'1'='1'**
2️⃣使用 步骤🌯
- 编写 SQL 语句,待输入内容使用
?占位,如"SELECT * FROM user WHERE name=?AND password=?" - 获得 PreparedStatement 对象
- 设置实际参数:
setXxx(占位符的位置, 真实的值) - 执行参数化 SQL 语句
- 关闭资源
| PreparedStatement 中设置参数的方法 |
|---|
void setDouble(int parameterIndex, double x) |
void setFloat(int parameterIndex, float x) |
void setInt(int parameterIndex, int x) |
void setLong(int parameterIndex, long x) |
void setObject(int parameterIndex, Object x) |
void setString(int parameterIndex, String x) |
- 举个栗子
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//连接数据库
String url = "jdbc:mysql:///db";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String name = scanner.nextLine();
System.out.print("请输入密码:");
String pwd = scanner.nextLine();
String sql = "select * from user where name = ? and password = ?";
// 获取prstmt对象
PreparedStatement prstmt = conn.prepareStatement(sql);
prstmt.setString(1, name);
prstmt.setString(2, password);
// 执行sql
ResultSet rs = prstmt.executeQuery();
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
System.out.println("id:" + rs.getInt("id"));
System.out.println("真正的用户名:" + rs.getString("name"));
System.out.println("真正的密码:" + rs.getString("password"));
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
prstmt.close();
conn.close();
}
-
演示结果如下👇
-
可以发现
PreparedStatement确实可以防止SQL注入
3️⃣PreparedSatement 的执行原理🍖
Satement对象会将每条SQL语句都发送给数据库检查和编译,数据库再执行,效率很低。而prepareStatement(sql)会先将SQL语句发送给数据库预编译,可以多次传入不同的参数给PreparedStatement对象并执行,检查SQL语句和编译SQL语句将不需要重复执行,这样就提高了性能。
1.打开预编译功能🍗
/* 在连接数据库的uel地址后加入下面的参数 */
useServerPrepStmts=true
/* 示例 */
jdbc:mysql:///db?useServerPrepStmts=true
2.开启MySQL日志🥩
- 在mysql配置文件(my.ini)中添加如下配置,并重启MySQL服务
/* 添加配置 */
log-output=FILE
general-log=1
general_log_file="D:\develop\mysql-5.7.30-winx64\mysql.log" # 注意写你自己放MySQL文件的目录下
slow-query-log=1
slow_query_log_file="D:\develop\mysql-5.7.30-winx64\mysql_slow.log" # 注意写你自己放MySQL文件的目录下
long_query_time=2
/* 重启MySQL服务 */
-- 以管理员身份打开cmd,输入以下命令
net stop mysql //停止mysql服务
net start mysql //启动mysql服务
- 会出现下面两个文件
3.观察日志🍤
- 可以看到只进行了一次预编译,所以这是
PreparedSatement比Satement性能高的原因 - 还注意到了他对引号进行了
转义,所以这是他能防止SQL注入的原因
写在后面🍻
感谢观看啦✨
有什么不足,欢迎指出哦💖
掘金的运营同学审核辛苦了💗