JDBC
# 简介
# 概念
JDBC 就是使用 Java 语言操作关系型数据库的一套 API
全称:(Java DataBase Connectitity) Java 数据库连接。
# 本质
期望:同一套代码操作不同的关系型数据库
Sun公司站出来了,定义了一套代码,JDBC,即一个规则,也就是 Java 语言里的接口,让不同的数据库厂商去实现各自的实现类。
我们可以使用这套接口(JDBC)编程,真正执行代码的是驱动
jar
包中的实现类。
# 好处
- 各数据库厂商使用相同的接口,Java 代码不需要针对不同数据库分别开发
- 可以随时替换底层数据库,访问数据库的 Java 代码基本不变
# 快速入门
# 步骤
创建工程,导入驱动
jar
包:mysql-connector-java-5.1.48.jar
,驱动下载地址:https://mirrors.tuna.tsinghua.edu.cn/mysql/downloads/Connector-J/ (opens new window)使用 IDEA 如何导入放进去的 jar 包:对着 jar 包右键找到添加到库,
add as library字样即可
,然后level
选择模块库,Globale library全局有效
,Project library
工程有效,module library
模块有效。注册驱动
Class.forName("com.mysql.jdbc.Driver");
1获取连接
Connection conn = DriverManager.getConnection(url, username, password);
1定义 SQL 语句
String sql = "update ...";
1获取执行 SQL 对象
Statement stmt = conn.createStatement();
1执行 SQL
stmt.executeUpdate(sql);
1处理返回结果
释放资源
import java.sql.*;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "";
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql = "update goods set goods_stock = goods_stock + 100 where id = 1";
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
// 5. 执行sql
int count = stmt.executeUpdate(sql);
// 6. 处理结果
System.out.println(count);
// 7. 释放资源
stmt.close();
conn.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
执行结果:
1
# API 详解
- DriverManager
- Connection
- Statement
- ResultSet
- PreparedStatement
# DriverManager
驱动管理类的作用:
注册驱动
mysql 驱动
package com.mysql.jdbc; import java.sql.DriverManager; import java.sql.SQLException; 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!"); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17提示
MySQL5之后的驱动
Class.forName("com.mysql.jdbc.Driver");
1这一行可以不写了。
原因是
MySQL
的jar包里的META-INF/serivces
有一个保存驱动的文件,会去读取里面的字符串进行加载类。获取数据库连接
参数:
url:连接路径
语法:
jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2...
示例:
jdbc:mysql://127.0.0.1:3306/db1
细节:
- 如果是连接的本机 mysql 服务器,并且 mysql 服务器的端口是 3306,则 url 可以简写为
jdbc:mysql:///数据库名称?参数键值对
- 配置
useSSL=false
参数,禁用安全连接方式,解决警告提示
Wed Dec 01 20:49:00 CST 2021 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
即配置为
jdbc:mysql:///db1?useSSL=false
- 如果是连接的本机 mysql 服务器,并且 mysql 服务器的端口是 3306,则 url 可以简写为
user:用户名
password:密码
# Connection
数据库连接对象的作用:
获取执行 SQL 的对象
普通执行 SQL 对象
Statement createStatement()
预编译 SQL 的执行 SQL 对象:防止 SQL 注入
PreparedStatement prepareStatement(sql);
1执行存储过程的对象
CallableStatement prepareCall(sql);
1
管理事务
MySQL 事务管理
# 开启事务 BEGIN TRANSACTION; # 或者 START TRANSACTION; # 提交事务 COMMIT; # 回滚事务 ROLLBACK; # MYSQL默认自动提交事务
1
2
3
4
5
6
7
8
9
10
11
12JDBC 事务管理:
Connection
接口中定义了 3 个对应的方法// 开启事务 setAutoCommit(boolean autoCommit); // true 为自动提交事务; false 为手动提交事务,即为开启事务 // 提交事务 commit(); // 回滚事务 rollback();
1
2
3
4
5
6
7
8
事务案例代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBC_Connection {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "";
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql1 = "update goods set goods_stock = goods_stock + 100 where id = 1";
String sql2 = "update goods set goods_stock = goods_stock + 200 where id = 2";
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
try {
// 开启事务
conn.setAutoCommit(false);
// 5. 执行sql
int count1 = stmt.executeUpdate(sql1);
int count2 = stmt.executeUpdate(sql2);
// 6. 处理结果
System.out.println(count1);
System.out.println(count2);
// 提交事务
conn.commit();
} catch (SQLException throwables) {
// 回滚事务
conn.rollback();
throwables.printStackTrace();
}
// 7. 释放资源
stmt.close();
conn.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
注意
回滚异常的,需要将 MySQL 表的存储引擎换成InnoDB
,默认的`MyISAM 不支持事务。
# Statement
就是用来执行 SQL 语句的。
int executeUpdate(sql); // 执行DML DDL语句的
DDL:对表和库的增删改查操作
DML:对数据的增删改操作
返回值:
- DML 语句影响的行数
- DDL 语句执行后,执行成功也可能返回 0
ResultSet executeQeury(sql); // 执行DQL语句 对数据的查询操作
DQL:对数据的查询操作
返回值:
ResultSet
结果集对象
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class JDBCDemo_Statement {
static String url = "jdbc:mysql://127.0.0.1:3306/db1";
static String username = "root";
static String password = "";
public static void testDML() throws Exception {
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql = "update goods set goods_stock = goods_stock + 100 where id = 1";
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
// 5. 执行sql
int count = stmt.executeUpdate(sql);
if (count > 0) {
System.out.println("修改成功");
} else {
System.out.println("修改失败");
}
// 6. 处理结果
System.out.println(count);
// 7. 释放资源
stmt.close();
conn.close();
}
public static void testDDL() throws Exception {
Connection conn = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql = "create database db2";
// String sql2 = "drop database db2";
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
// 5. 执行sql
int count = stmt.executeUpdate(sql);
// 6. 处理结果 DDL 不一定返回0
System.out.println(count);
// 7. 释放资源
stmt.close();
conn.close();
}
public static void main(String[] args) throws Exception {
testDML();
testDDL();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# ResultSet
结果集对象的作用:
封装了 DQL 查询语句的结果
ResultSet stmt.executeQuery(sql); // 执行DQL语句,返回ResultSet对象
1获取查询结果
boolean next();
1- 将光标从当前位置向后移动一行
- 判断当前行是否为有效行
返回值:
- true:有效行,当前行有数据
- false:无效行,当前行没数据
xxx getXxx(参数); // 获取数据 xxx 数据类型;如 int getInt(参数); String getString(参数);
1
2
3参数:
- int:列的编号,从 1 开始
- String:列的名称
使用步骤:
- 游标向下移动一行,并判断该行是否有数据:
next()
- 获取数据,getXxx(参数)
// 循环判断游标是否是最后一行末尾
while (rs.next()) {
// 获取数据
rs.getXxx(参数);
}
2
3
4
5
案例:
package com.wx.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo_ResultSet {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "";
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql = "select * from goods";
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 遍历rs的所有数据
while (rs.next()) {
int id = rs.getInt(1);
String goods_name = rs.getString(2);
int goods_stock = rs.getInt(3);
System.out.println(id);
System.out.println(goods_name);
System.out.println(goods_stock);
}
// 7. 释放资源
rs.close();
stmt.close();
conn.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
while (rs.next()) {
// 换一种方式获取数据
int id = rs.getInt("id");
String goods_name = rs.getString("goods_name");
int goods_stock = rs.getInt("goods_stock");
System.out.println(id);
System.out.println(goods_name);
System.out.println(goods_stock);
}
2
3
4
5
6
7
8
9
10
ResultSet 案例
需求:查询
goods
表数据,封装为Goods
对象,并且存储到ArrayList
集合中。
定义实体类
Goods
,一般是放在以pojo
命名的包中package com.wx.pojo; // 一般表名叫啥,类名就叫啥 public class Goods { // 和数据表的字段类型一一对应 private int id; private String goods_name; private int goods_stock; @Override public String toString() { return "Goods{" + "id=" + id + ", goods_name='" + goods_name + '\'' + ", goods_stock=" + goods_stock + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getGoods_name() { return goods_name; } public void setGoods_name(String goods_name) { this.goods_name = goods_name; } public int getGoods_stock() { return goods_stock; } public void setGoods_stock(int goods_stock) { this.goods_stock = goods_stock; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44查询数据
package com.wx.jdbc; import com.wx.pojo.Goods; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; public class JDBCDemo_ResultSet { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://127.0.0.1:3306/db1"; String username = "root"; String password = ""; // 1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2. 获取连接 Connection conn = DriverManager.getConnection(url, username, password); // 3. 定义sql String sql = "select * from goods"; // 4. 获取执行sql的对象 Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 创建一个集合 List<Goods> goodsList = new ArrayList<>(); // 遍历rs的所有数据 while (rs.next()) { // 对象的创建 Goods goods = new Goods(); int id = rs.getInt("id"); String goods_name = rs.getString("goods_name"); int goods_stock = rs.getInt("goods_stock"); // 给对象赋值 goods.setId(id); goods.setGoods_name(goods_name); goods.setGoods_stock(goods_stock); // 存到集合里 goodsList.add(goods); } // 7. 释放资源 rs.close(); stmt.close(); conn.close(); // 验证集合 System.out.println(goodsList); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65结果:
[Goods{id=1, goods_name='无解', goods_stock=200}, Goods{id=2, goods_name='带我去多', goods_stock=21}]
1
# PreparedStatement
继承自
Statement
。所以它也是用来执行 SQL 的对象。
作用:
- 预编译 SQL 语句并执行:预防 SQL 注入问题
- SQL 注入:是通过操作输入修改事先定好的 SQL 语句,用于达到执行代码对服务器进行攻击的方法。
警告
登录的接口,经常是判断用户名和密码是否一致的时候,才能进行登录;加入此时输入的密码的内容为
' or '1' == '1
,再次点击登录的时候,没做 SQL 注入防护的可能可以直接登录成功。
登录案例 SQL:
select * from user where username = 'dwqdqwdqw' and password = '123';
SQL 注入:
select * from user where username = 'dwqdqwdqw' and password = '' or '1' == '1';
这个操作就是,你直接将上面的输入的内容,直接复制到单引号内即可达到效果。
建立对应的数据库和数据表(数据库还是 db1)
drop table if exists user;
create table user (
id int,
username varchar(20),
password varchar(20)
);
-- 添加数据
insert into user values(1, '张三', '123'), (2, '李四', '123');
2
3
4
5
6
7
8
9
成功案例
package com.wx.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo_UserLogin {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "";
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
String name = "张三";
String pwd = "123";
// 3. 定义sql
String sql = "select * from user where username = '" + name + "' and password = '" + pwd + "'";
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
// 7. 释放资源
rs.close();
stmt.close();
conn.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
SQL 注入案例
package com.wx.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo_UserLogin {
public static void main(String[] args) throws Exception {
testInjectSQL();
}
public static void testInjectSQL() throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "";
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
String name = "张三";
String pwd = "' or '1' = '1";
// 3. 定义sql
String sql = "select * from user where username = '" + name + "' and password = '" + pwd + "'";
System.out.println(sql);
// 4. 获取执行sql的对象
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
// 7. 释放资源
rs.close();
stmt.close();
conn.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
select * from user where username = '张三' and password = '' or '1' = '1'
登录成功
2
提示
and
会先执行,前面 2 个为 true,后面在和or
进行判断,后面是恒等的,就等于查询全部。
解决 SQL 注入
获取
PreparedStatement
对象// sql语句中的参数值,使用 ? 占位符替代 String sql = "select * from user where username = ? and password = ?"; // 通过Connection的对象获取,并传入对应的sql语句 PreparedStatement pstmt = conn.prepareStatement(sql);
1
2
3
4
5设置参数值
PreparedStatement对象:setXxx(参数1, 参数2) // 给 ? 赋值
1Xxx:数据类型;如:
setInt(参数1, 参数2)
参数:
- 参数 1:
?
的位置编号,从 1 开始 - 参数 2:
?
的值
- 参数 1:
执行 SQL(不需要再传递 sql)
executeUpdate(); // 或者 executeQuery();
1
2
3
package com.wx.jdbc;
import java.sql.*;
public class JDBCDemo_PreparedStatement {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "";
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
String name = "张三";
String pwd = "' or '1' = '";
// 3. 定义sql
String sql = "select * from user where username = ? and password = ?";
// 获取 pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
pstmt.setString(2, pwd);
// 执行sql
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
// 7. 释放资源
rs.close();
pstmt.close();
conn.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
在进行setXxx
的时候,会对传递进去的参数进行转义,例如上述的注入的操作被转义成以下内容:
select * from user where username = '张三' and password = '\' or \'1\' = \'1';
# 此时里面的这个东西就会变成文本,然后就查询不到内容
2
3
# PreparedStatement 原理
好处:
- 预编译 SQL,性能更高
- 防止 SQL 注入:将敏感字符进行转义
开启预编译功能
其实它的预编译功能默认是关闭的
// 加到连接的url后面
useServerPrepStmts=true
2
String url = "jdbc:mysql://127.0.0.1:3306/db1?useServerPrepStmts=true";
配置 MySQL 执行日志(重启 MySQL 服务生效)
log-output=FILE
general-log=1
general_log_file="存储的位置"
slow-query-log=1
slow_query_log_file="慢查询日志的位置"
long_query_time=2
2
3
4
5
6
原理:
- 在获取
PreparedStatement
对象时,将 sql 语句发送给mysql
服务器进行检查,编译(这些步骤很耗时)。 - 执行时就不用再进行这些步骤了,速度更快
- 如果
sql
模板一样,则只需要进行一次检查、编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 这一步就已经将SQL语句发送给MySQL进行预编译。
2
# 数据库连接池
# 简介
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
# 好处:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
# 数据库连接池的实现
github wiki 地址 (opens new window)
标准接口:DataSource
官方 Sun 提供的数据库连接池标准接口,由第三方组织实现此接口
功能:获取连接
// 标准方法 Connection getConnection();
1
2
常见的数据库连接池:
- DBCP
- C3P0
- Druid
Druid(德鲁伊)
- Druid 连接池是阿里巴巴开源的数据库连接池项目
- 功能强大、性能优秀,是 Java 语言最好的数据库连接池之一
# 使用步骤
- 导入 jar 包
druid-1.1.12.jar
- 定义配置文件
- 加载配置文件
- 获取数据库连接池对象
- 获取连接
配置文件(src/druid.properties
)
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=
# 初始化连接池数量
initialSize=5
# 最大连接数量
maxActive=10
# 最大等待时间
maxWait=3000
2
3
4
5
6
7
8
9
10
package com.wx.druid;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
public class DruidDemo {
public static void main(String[] args) throws Exception {
// 1. 导入jar包
// 2. 定义配置文件
// 3. 加载配置文件
// 4. 获取连接池对象
Properties prop = new Properties();
// 注意路径问题
prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
// 5. 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 获取当前项目目录
// System.out.println(System.getProperty("user.dir"));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
输出
信息: {dataSource-1} inited
com.mysql.jdbc.JDBC4Connection@21a06946
2