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; |