JDBC
★★★第二十一章 JDBC和数据库连接池
★★★JDBC原理示意图
JDBC
为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。Java
程序员使用JDBC
,可以连接任何提供了JDBC
驱动程序的数据库系统,从而完成对数据库的各种操作。
JDBC
是Java
提供的一套用于数据库操作的接口APl
,**Java
程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同的实现**。
JDBC API
是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL
语句,并得到返回结果等各类操作,相关类和接口在java.sql
与javax.sql
包中。
★JDBC快速入门
JDBC
程序编写步骤(以使用mysql
为例):前置工作:在项目下创建一个文件夹,例如
lib
,将mysql.jar
文件拷贝到该目录下,通过添加库导入该jar
包。-> MySQL中 java 的 JDBC 编程使用方法及驱动包的下载和使用 - CSDN博客- 注册驱动 - 加载
Driver
类。 - 获取连接 - 得到
Connection
。 - 执行增删改查 - 发送
SQL
给mysql
执行。 - 释放资源 - 关闭相关连接。
- 注册驱动 - 加载
以下面的例子为例(
java_db
是已经创建的一个数据库):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
45package com.f.chapter21.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/4 19:21
* JDBC 的快速入门
*/
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
//1.注册驱动
Driver driver = new Driver(); //创建驱动对象
//2.获取连接
// (1) jdbc:mysql:// 是规定好的,用来表示协议。通过 jdbc 的方式连接 mysql
// (2) localhost 表示主机,可以是 ip 地址
// (3) 3306 表示 mysql 监听的端口
// (4) java_db 表示连接到 MySQL DBMS 的 java_db 数据库
//MySQL 连接的本质就是前面学过的 Socket 连接
String url = "jdbc:mysql://localhost:3306/java_db?useSSL=false";
//将用户名和密码放入到 Properties 对象中
Properties properties = new Properties();
// user 和 password 关键字是规定好的,不要乱写
properties.setProperty("user", "root"); //用户
properties.setProperty("password", "123456"); //密码
Connection connect = driver.connect(url, properties);
//3.执行sql
String sql = "INSERT INTO test_table VALUES (100, 'tom')";
//Statement 对象用于执行静态 SQL 语句并返回其生成的结果的对象。
Statement statement = connect.createStatement(); //statement是帮我们执行sql语句的
int rows = statement.executeUpdate(sql); //如果是dml语句,返回的就是影响的行数
System.out.println(rows > 0 ? "sql语句执行成功" : "sql语句执行失败");
//4.关闭连接,释放资源
statement.close();
connect.close();
}
}- 总结就是:
- 用驱动
Driver
进行数据库的连接。 - 用连接
Connection
创建Statement
。 - 由
Statement
对象执行静态 SQL 语句并返回其生成的结果的对象。
- 用驱动
- 总结就是:
★数据库连接的五种方式
直接使用
Driver
进行数据库的连接:1
2
3
4
5
6
7
8Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/java_db?useSSL=false";
Properties properties = new Properties();
properties.setProperty("user", "root"); //用户
properties.setProperty("password", "123456"); //密码
Connection connect = driver.connect(url, properties);- 缺点是
com.mysql.jdbc.Driver
是第三方的,而且是静态加载,灵活性不足,依赖强。
- 缺点是
通过反射机制连接数据库:
1
2
3
4
5
6
7
8
9Class clazz = Class.forName("com.mysql.jdbc.Driver"); //参数可以通过配置文件导入
Driver driver = (Driver)clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/java_db?useSSL=false";
Properties properties = new Properties();
properties.setProperty("user", "root"); //用户
properties.setProperty("password", "123456"); //密码
Connection connect = driver.connect(url, properties);- 是动态加载,更加灵活,降低了依赖性。
使用
DriverManager
替代Driver
,进行统一管理:1
2
3
4
5
6
7
8
9
10
11Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/java_db?useSSL=false";
Properties properties = new Properties();
properties.setProperty("user", "root"); //用户
properties.setProperty("password", "123456"); //密码
DriverManager.registerDriver(driver); //注册Driver驱动
Connection connection = DriverManager.getConnection(url, properties);DriverManager
是用于管理一组JDBC
驱动程序的基本服务。
使用
Class.forName
自动完成注册驱动,简化代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//使用反射加载 Driver 类
/**
* 在加载 Driver 类时,静态代码块会执行一次:
* static {
* try {
* DriverManager.registerDriver(new Driver());
* } catch (SQLException var1) {
* throw new RuntimeException("Can't register driver!");
* }
* }
* 该静态代码块的执行完成了注册 Driver 的工作
* */
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/java_db?useSSL=false";
Properties properties = new Properties();
properties.setProperty("user", "root"); //用户
properties.setProperty("password", "123456"); //密码
Connection connection = DriverManager.getConnection(url, properties);注意:从
mysql 驱动 5.1.6
开始就可以无需显式使用Class.forName("com.mysql.jdbc.Driver")
。从
jdk1.5
以后使用了jdbc4
,不再需要显式调用class.forName()
注册驱动而是自动调用驱动jar
包下的META-INF\services\java.sql.Driver
文本中的类名称去注册。- 建议还是写上
Class.forName("com.mysql.jdbc.Driver")
,更加明确。
- 建议还是写上
基于方式
4
的优化,通过使用配置文件,使得连接数据库更加灵活:1
2
3
4
5
6
7
8
9
10//使用配置文件,连接数据库更加灵活
Properties properties = new Properties();
properties.load(new FileInputStream("file\\jdbc.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
Class.forName(driver); //建议写上
Connection connection = DriverManager.getConnection(url, user, password);- 以这种方式获取连接是使用的最多的,推荐使用。
★★★ResultSet
public interface ResultSet extends Wrapper, AutoCloseable
表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
ResultSet
对象保持一个光标,指向其当前的数据行。 最初,光标位于第一行之前。next
方法将光标移动到下一行,并且由于在ResultSet
对象中没有更多行时返回false
,因此可以在while
循环中使用循环来遍历结果集。有点类似于迭代器
iterator
。ResultSet
对象通过getXxx(i)
得到光标指向的当前行的第i
列的数据(从1
开始)。也可以通过
getXxx("列名")
来得到光标指向的当前行的对应列的数据。-> 更推荐用这种
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
48package com.f.chapter21.jdbc.resultset_;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/5 20:45
* 演示SELECT语句返回ResultSet,并取出结果
*/
public class ResultSet_ {
public static void main(String[] args) throws Exception {
//1.连接数据库,得到Statement对象
Properties properties = new Properties();
properties.load(new FileInputStream("file\\jdbc.properties"));
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
// (1)注册驱动
Class.forName(driver);
// (2)得到连接
Connection connection = DriverManager.getConnection(url, user, password);
// (3)得到Statement对象
Statement statement = connection.createStatement();
//2.执行SELECT语句,返回ResultSet
String selectSQL = "SELECT * " +
"FROM `news`";
ResultSet resultSet = statement.executeQuery(selectSQL);
//3.通过while循环取出 ResultSet 中的数据
while (resultSet.next()) { //让光标向后移动,如果没有更多行则返回 `false`
int id = resultSet.getInt(1); //获取当前记录的第一列数据
String content = resultSet.getString(2); //获取当前记录的第二列数据
System.out.println(id + " - " + content);
}
//4.释放资源
resultSet.close();
statement.close();
connection.close();
}
}
★★★Statement
public interface Statement extends Wrapper, AutoCloseable
用于执行静态
SQL
语句并返回其生成的结果的对象。
SQL注入
在连接建立后,需要对数据库进行访问,执行命名是
SQL
语句,可以通过Statement
[存在SQL
注入] -> 在实际开发中不会使用PreparedStatement
[预处理]CallableStatement
[调用存储过程]
执行。
Statement
对象执行SQL
语句,存在SQL
注入风险。SQL
注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL
语句段或命令,恶意攻击数据库。要防范
SQL
注入,只要用PreparedStatement
(从Statement
扩展而来)取代Statement
就可以了。
★PreparedStatement
public interface PreparedStatement extends Statement
表示预编译的
SQL
语句的对象。SQL
语句已预编译并存储在PreparedStatement
对象中。 然后可以使用该对象多次有效地执行此语句。
PreparedStatement
执行的SQL
语句中的参数用问号?
来表示,通过调用PreparedStatement
对象的setXxx()
方法来设置这些参数。1
String sql = "SELECT COUNT(*) FROM admin WHERE user = ? AND password = ?";
setXxx()
方法有两个参数,第一个参数是要设置的SQL
语句中的参数的索引(从1
开始),第二个是设置的SQL
语句中的参数的值。调用
executeQuery(String sql)
,返回的是ResultSet
对象。调用
executeUpdate(String sql)
,执行更新,包括增、删、修改,返回的是影响的行数。
1 | package com.f.chapter21.jdbc.preparedstatement_; |
- 预处理的好处:
- 不再使用
+
拼接sql
语句,减少语法错误。 - 有效的解决了
sql
注入问题。 - 大大减少了编译次数,效率较高。
- 不再使用
★★★JDBC API小结


JDBC工具类
在
JDBC
操作中,“获取连接” 和 “释放资源” 是经常使用到的,可以将其封装为JDBC
连接的工具类: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
66
67
68
69
70
71package com.f.chapter21.jdbc.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author fzy
* @date 2023/8/6 13:32
* JDBC的工具类,完成mysql的连接和关闭资源
*/
public class JDBCUtils {
//定义相关属性
private static String driver; //驱动名
private static String url; //连接数据库的url
private static String user; //用户名
private static String password; //密码
//在static代码块去初始化相关属性
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("file\\jdbc.properties"));
driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
} catch (IOException e) {
//在实际开发中,我们可以这样处理:
// 1.将编译异常用运行时异常抛出
// 2.这时调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
throw new RuntimeException(e);
}
}
//连接数据库,返回Connection对象
public static Connection getConnection() {
try {
Class.forName(driver);
return DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 关闭相关资源:
* 1.结果集 ResultSet
* 2.Statement 或者 PreparedStatement
* 3.Connection
* 4.如果需要关闭资源,就传入对象,否则传入null
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}JDBC
工具类的使用: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
66package com.f.chapter21.jdbc.utils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author fzy
* @date 2023/8/6 13:58
* 演示如何使用JDBCUtils工具类,完成dml和select语句
*/
public class JDBCUtils_Use {
public void testDML() {
//1.得到连接
Connection connection = JDBCUtils.getConnection();
System.out.println(connection.getClass()); //class com.mysql.jdbc.JDBC4Connection
//2.组织一个dml sql语句
String insertSQL = "INSERT INTO test_table VALUES (?, ?)";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
try {
preparedStatement = connection.prepareStatement(insertSQL);
//给占位符 ? 赋值
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, "北京新闻");
//执行dml sql语句
preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//4.关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
public void testSelect() {
//1.得到连接
Connection connection = JDBCUtils.getConnection();
//2.组织一个select sql语句
String selectSQL = "SELECT * FROM test_table WHERE id = ?";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
//4.创建ResultSet对象
ResultSet resultSet = null;
try {
preparedStatement = connection.prepareStatement(selectSQL);
//给占位符 ? 赋值
preparedStatement.setInt(1, 1);
//执行select sql语句,得到 ResultSet 结果集
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getInt("id") + " - " + resultSet.getString("name"));
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//5.关闭资源
JDBCUtils.close(resultSet, preparedStatement, connection);
}
}
}
事务
JDBC
程序中当一个Connection
对象创建时,默认情况下是自动提交事务:每次执行一个SQL
语句时,如果执行成功,就会向数据库自动提交,而不能回滚。JDBC
程序中为了让多个SQL
语句作为一个整体执行,需要使用事务。- 调用
Connection
的setAutoCommit(false)
可以取消自动提交事务。 - 在所有的
SQL
语句都成功执行后,调用Connection
的commit()
方法提交事务。 - 在其中某个操作失败或出现异常时,调用
Connection
的rollback()
方法回滚事务。rollback()
方法可以放在异常的catch
块中。
以转账业务为例进行代码的编写:
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
56package com.f.chapter21.jdbc.transaction;
import com.f.chapter21.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author fzy
* @date 2023/8/6 14:50
* 演示在JDBC中事务的使用
*/
public class Transaction_ {
public void testTransaction_() {
//1.得到连接
//Connection在默认情况下是自动提交事务的
Connection connection = JDBCUtils.getConnection();
//2.组织sql语句
String sql1 = "UPDATE account SET balance = balance - 100 WHERE id = 1";
String sql2 = "UPDATE account SET balance = balance + 100 WHERE id = 2";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement = null;
try {
//取消Connection自动提交事务,也是事务开始的地方
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate(); //执行第一条sql
//...
//可能发生异常,出现程序无法继续运行下去的情况,例如下面的语句
//int i = 1 / 0;
//...
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); //执行第二条sql
//如果程序正常运行,则可以在这里进行事务的提交
connection.commit();
} catch (Exception e) {
try {
//可以在这里进行回滚,撤销已经执行的sql
//默认回滚到事务开始的地方
System.out.println("发生了异常,撤销执行的sql,进行回滚");
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
} finally {
//4.关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
}
批处理
当需要成批插入或者更新记录时。可以采用
Java
的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。JDBC
的批量处理语句包括下面方法:addBatch()
:添加需要批量处理的SQL
语句或参数。executeBatch()
:执行批量处理语句。clearBatch()
:清空批处理包的语句。
JDBC
连接MySQL
时,如果要使用批处理功能,请在url
中添加参数:**
?rewriteBatchedStatements=true
**。批处理往往和
PreparedStatement
一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高。
1 | package com.f.chapter21.jdbc.batch; |