前言
本文是JDBC的全套核心知识点梳理,从JDBC基础概念到实际开发中的封装、连接池、事务处理等核心内容全覆盖,代码示例可直接运行,适合Java初学者入门学习,也可作为开发中的速查手册。
一、JDBC基础认知
1.1 什么是JDBC
JDBC(Java DataBase Connectivity)即Java数据库连接,是SUN公司提供的一套操作数据库的标准接口,通过Java语言向数据库发送SQL语句,实现程序与数据库的自动化交互(替代Navicat等手动操作方式)。
1.2 JDBC的核心原理
-
SUN只提供JDBC接口,不提供具体实现;
-
各数据库厂商遵循JDBC规范,提供各自的数据库驱动(接口的实现类);
-
程序通过JDBC接口调用驱动,实现对不同数据库的统一操作,屏蔽数据库底层差异。
1.3 JDBC核心API
JDBC的核心能力是:建立连接→发送SQL→处理结果,核心接口/类如下:
|
接口/类 |
核心作用 |
|---|---|
|
DriverManager |
管理数据库驱动,获取数据库连接Connection |
|
Connection |
代表数据库物理连接,是操作数据库的入口 |
|
Statement |
发送执行SQL语句(简单SQL,有注入风险) |
|
PreparedStatement |
Statement子类,预编译SQL,解决注入问题 |
|
ResultSet |
保存SQL查询的结果集,提供数据读取方法 |
二、JDBC操作数据库的通用步骤
JDBC操作增删改查的基础步骤统一,仅执行SQL的方法和结果处理不同,核心五步:
步骤1:加载数据库驱动
通过Class.forName()加载驱动类,不同数据库驱动类不同,核心区别:
// MySQL5
Class.forName("com.mysql.jdbc.Driver");
// MySQL8(推荐)
Class.forName("com.mysql.cj.jdbc.Driver");
步骤2:创建数据库连接
通过DriverManager.getConnection()获取Connection,需指定URL、用户名、密码:
String url = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
String user = "root";
String pwd = "123456";
Connection conn = DriverManager.getConnection(url, user, pwd);
URL格式详解:协议://主机:端口/数据库名?参数1&参数2
-
协议:
jdbc:mysql(固定) -
主机:本地为
localhost,远程为服务器IP -
端口:MySQL默认3306
-
参数:
useSSL=false(关闭SSL)、characterEncoding=UTF8(设置编码)
步骤3:创建Statement/PreparedStatement,发送SQL
-
简单SQL用
Statement; -
带参数SQL用
PreparedStatement(预编译,推荐)。
步骤4:处理执行结果
-
增/删/改:返回受影响行数(int),判断是否执行成功;
-
查:返回ResultSet结果集,遍历获取数据。
步骤5:关闭数据库资源
关闭顺序:ResultSet → Statement/PreparedStatement → Connection(与创建顺序相反),避免资源泄漏。
三、JDBC核心操作实现(增删改查)
以下示例基于student表,数据库为jdbc_db,驱动使用MySQL5,所有代码均为可运行完整代码。
3.1 新增操作(INSERT)
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class AddTest {
// 配置常量
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
private static final String USER = "root";
private static final String PWD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName(DRIVER);
// 2.创建连接
Connection conn = DriverManager.getConnection(URL, USER, PWD);
// 3.创建Statement
Statement stmt = conn.createStatement();
// 4.执行SQL,获取受影响行数
String sql = "insert into student values(1,'小刚',32,'男','湖北省武汉市')";
int n = stmt.executeUpdate(sql);
// 5.处理结果
if (n > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
// 6.关闭资源
stmt.close();
conn.close();
}
}
3.2 修改操作(UPDATE)
核心:executeUpdate(sql)执行更新SQL,逻辑与新增一致,仅SQL语句不同:
String sql = "update student set name='小明',age=23 where id=1";
int res = stat.executeUpdate(sql);
if(res>0){
System.out.println("修改成功");
}
3.3 删除操作(DELETE)
注意:删除必须加WHERE条件,否则删除全表数据!
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class DeleteTest {
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
private static final String USER = "root";
private static final String PWD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER, PWD);
Statement stat = conn.createStatement();
// 带WHERE条件,删除指定ID数据
String sql = "delete from student where id=1";
int res = stat.executeUpdate(sql);
if (res > 0) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
stat.close();
conn.close();
}
}
3.4 查询操作(SELECT)
核心:executeQuery(sql)返回ResultSet,通过next()遍历,getXXX()获取列数据:
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class QueryTest {
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
private static final String USER = "root";
private static final String PWD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER, PWD);
Statement stmt = conn.createStatement();
// 执行查询SQL
String sql = "select * from student";
ResultSet rs = stmt.executeQuery(sql);
// 遍历结果集
while (rs.next()) {
int id = rs.getInt(1); // 通过列索引获取(从1开始)
String name = rs.getString("name"); // 通过列名获取(推荐)
int age = rs.getInt("age");
System.out.println(id + " " + name + " " + age);
}
// 关闭资源(包含ResultSet)
rs.close();
stmt.close();
conn.close();
}
}
ResultSet数据读取技巧:
-
getInt(index/name):读取整型 -
getString(index/name):读取字符串 -
getObject(name):通用方法,读取任意类型
3.5 分页查询
MySQL分页使用limit 起始索引,每页条数,起始索引=(页码-1)每页条数*:
// 分页参数
int pageNum = 2; // 第2页
int pageSize = 5; // 每页5条
// 拼接分页SQL
String sql = "select * from student limit " + (pageNum-1)*pageSize + "," + pageSize;
ResultSet rs = stmt.executeQuery(sql);
四、JDBC核心问题:SQL注入及解决
4.1 什么是SQL注入
通过拼接用户输入的恶意字符串,篡改原始SQL逻辑,导致非授权访问或数据泄露。
示例:登录功能中,用户输入username='1' or '1'='1',密码任意,拼接后的SQL为:
select * from sys_user where username='1' or '1'='1' and password='xxx'
该SQL恒成立,无需正确密码即可登录。
4.2 解决方法:使用PreparedStatement(预编译)
PreparedStatement是Statement的子类,核心特性:
-
SQL语句预编译,占位符
?接收参数,避免字符串拼接; -
执行效率更高(多次执行同一SQL时,无需重复编译);
-
从根本上解决SQL注入问题。
4.3 预编译实现登录(防注入)
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class LoginTest {
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
private static final String USER = "root";
private static final String PWD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER, PWD);
// 1.预编译SQL,用?作为占位符
String sql = "select * from sys_user where username=? and password=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 2.为占位符赋值(索引从1开始)
pstmt.setString(1, username);
pstmt.setString(2, password);
// 3.执行SQL,无需传入参数
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登录成功!");
} else {
System.out.println("用户名或密码错误!");
}
// 关闭资源
rs.close();
pstmt.close();
conn.close();
}
}
核心方法:setXxx(int index, Xxx value),根据参数类型选择对应方法(如setInt、setString)。
五、JDBC工具类封装(DBUtils)
原始JDBC代码中,加载驱动、创建连接、关闭资源的代码重复率极高,需封装为通用工具类DBUtils,简化开发。
5.1 第一步:创建配置文件(db.properties)
将数据库配置抽离为配置文件,避免硬编码,便于后期修改,放在src目录下:
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8
username=root
password=1111
5.2 第二步:实现DBUtils工具类
核心功能:加载配置+获取连接+通用关闭资源:
package com.hg.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBUtils {
// 配置变量
private static String DRIVER;
private static String URL;
private static String USER;
private static String PWD;
// 静态代码块:加载配置+注册驱动(只执行一次)
static {
try {
// 读取配置文件
InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(is);
// 为变量赋值
DRIVER = prop.getProperty("driverClass");
URL = prop.getProperty("url");
USER = prop.getProperty("username");
PWD = prop.getProperty("password");
// 注册驱动
Class.forName(DRIVER);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据库连接
public static Connection getConn() {
try {
return DriverManager.getConnection(URL, USER, PWD);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("创建连接失败!");
return null;
}
}
// 通用关闭资源方法(AutoCloseable为Connection/Statement/ResultSet的父接口)
public static void close(AutoCloseable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.3 工具类使用示例(简化新增操作)
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
public class AddTestByDBUtils {
public static void main(String[] args) throws SQLException {
// 1.获取连接(一行代码)
Connection conn = DBUtils.getConn();
Statement stmt = conn.createStatement();
// 2.执行SQL
String sql = "insert into student values(2,'小红',28,'女','湖南省长沙市')";
int n = stmt.executeUpdate(sql);
if (n > 0) {
System.out.println("添加成功");
}
// 3.关闭资源(一行代码,无需逐个关闭)
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
六、JDBC事务处理
6.1 事务的概念
事务是数据库操作的最小工作单元,一组操作要么全部执行成功,要么全部回滚,保证数据一致性。
6.2 事务的四大特性(ACID)
-
原子性(Atomicity):操作不可分割,要么全做,要么全不做;
-
一致性(Consistency):事务执行后,数据库从一个一致状态到另一个一致状态;
-
隔离性(Isolation):多个事务并发执行,互不干扰;
-
持久性(Durability):事务提交后,修改永久生效,不会因故障丢失。
6.3 JDBC事务实现(以转账为例)
JDBC默认自动提交事务(每执行一条SQL自动提交),实现手动事务需三步:
-
关闭自动提交:
conn.setAutoCommit(false); -
执行完所有SQL后,手动提交:
conn.commit(); -
出现异常时,回滚事务:
conn.rollback()。
转账案例代码实现
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
public class TransactionTest {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1.获取连接
conn = DBUtils.getConn();
// 2.关闭自动提交,开启手动事务
conn.setAutoCommit(false);
// 3.执行转账SQL(扣钱+加钱)
stmt = conn.createStatement();
String sql1 = "update account set amount = amount-1000 where aid=1"; // 账户1扣1000
String sql2 = "update account set amount = amount+1000 where aid=2"; // 账户2加1000
stmt.executeUpdate(sql1);
// 模拟异常:int i = 1/0;
stmt.executeUpdate(sql2);
// 4.无异常,提交事务
conn.commit();
System.out.println("转账成功!");
} catch (Exception e) {
e.printStackTrace();
// 5.有异常,回滚事务
try {
if (conn != null) {
conn.rollback();
System.out.println("转账失败,事务回滚!");
}
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 6.关闭资源
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
}
测试:在sql1和sql2之间添加int i = 1/0;模拟异常,会触发事务回滚,两个账户的金额不会发生变化。
七、高级封装:BaseDAO(通用数据访问层)
在DBUtils基础上,结合反射+泛型封装BaseDAO,实现通用增删改查,无需为每个实体类重复编写DAO代码,模拟ORM框架的核心思想。
7.1 BaseDAO核心功能
-
通用查询列表:
selectList(sql, Class<T>, params); -
通用查询单个对象:
selectOne(sql, Class<T>, params); -
通用增删改:
update(sql, params); -
通用分页查询:
selectPage(sql, Class<T>, page, limit)。
7.2 BaseDAO完整代码
package com.hg.dao;
import com.hg.utils.DBUtils;
import com.hg.common.PageInfo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class BaseDao {
/**
* 通用查询列表
* @param sql SQL语句
* @param clss 实体类字节码
* @param params SQL占位符参数
* @return 实体类列表
*/
public <T> List<T> selectList(String sql, Class<T> clss, Object... params) {
List<T> data = new ArrayList<>();
Connection conn = DBUtils.getConn();
PreparedStatement prep = null;
ResultSet rs = null;
try {
prep = conn.prepareStatement(sql);
// 为占位符赋值
for (int i = 0; i < params.length; i++) {
prep.setObject(i + 1, params[i]);
}
// 执行查询
rs = prep.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 遍历结果集,通过反射封装实体类
while (rs.next()) {
T t = clss.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取列别名(与实体类属性名一致)
String columnLabel = metaData.getColumnLabel(i + 1);
// 获取列值
Object columnValue = rs.getObject(columnLabel);
// 反射获取实体类属性,赋值
Field field = clss.getDeclaredField(columnLabel);
field.setAccessible(true); // 突破私有属性访问限制
field.set(t, columnValue);
}
data.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(rs);
DBUtils.close(prep);
DBUtils.close(conn);
}
return data;
}
/**
* 通用查询单个对象
*/
public <T> T selectOne(String sql, Class<T> clss, Object... params) {
List<T> list = selectList(sql, clss, params);
if (!list.isEmpty() && list.size() == 1) {
return list.get(0);
}
return null;
}
/**
* 通用增删改操作
* @return 执行成功返回true,失败返回false
*/
public boolean update(String sql, Object... params) {
Connection conn = DBUtils.getConn();
PreparedStatement prep = null;
try {
prep = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
prep.setObject(i + 1, params[i]);
}
int m = prep.executeUpdate();
return m >= 1;
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(prep);
DBUtils.close(conn);
}
return false;
}
/**
* 通用分页查询
* @param page 页码
* @param limit 每页条数
* @return 分页结果对象(数据+总条数)
*/
protected <T> PageInfo<T> selectPage(String sql, Class<T> cls, String page, String limit) {
Integer pageNo = (Integer.parseInt(page) - 1) * Integer.parseInt(limit);
// 分页SQL
String listSql = sql + " limit " + pageNo + "," + limit;
// 查询列表数据
List<T> data = selectList(listSql, cls);
// 查询总条数
Long count = getCount(sql);
// 封装分页结果
PageInfo<T> pageInfo = new PageInfo<>();
pageInfo.setData(data);
pageInfo.setCount(count);
return pageInfo;
}
/**
* 查询总条数
*/
private Long getCount(String sql) {
String countSql = "select count(1) from (" + sql + ") rs";
Connection conn = DBUtils.getConn();
PreparedStatement prep = null;
ResultSet rs = null;
try {
prep = conn.prepareStatement(countSql);
rs = prep.executeQuery();
rs.next();
return rs.getLong(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(rs);
DBUtils.close(prep);
DBUtils.close(conn);
}
return 0L;
}
}
7.3 分页结果封装类(PageInfo)
package com.hg.common;
import java.util.List;
public class PageInfo<T> {
private List<T> data; // 分页数据列表
private Long count; // 符合条件的总条数
// getter/setter
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
}
7.4 BaseDAO使用示例
创建UserDao继承BaseDAO,无需编写底层JDBC代码,直接调用父类方法:
package com.hg.dao.impl;
import com.hg.dao.BaseDao;
import com.hg.pojo.User;
public class UserDao extends BaseDao {
/**
* 根据用户名和密码查询用户
*/
public User selectUser(String username, String password) {
String sql = "select id, username, password, realname from user where username=? and password=?";
return super.selectOne(sql, User.class, username, password);
}
/**
* 修改用户状态
*/
public boolean updateUserState(String id, Integer deleted) {
String sql = "update user set deleted=?, deleted_time=now() where id=?";
return super.update(sql, deleted, id);
}
}
八、数据库连接池(DataSource)
8.1 为什么需要连接池
-
原始
DriverManager每次获取连接都新建物理连接,创建/销毁连接耗时耗资源; -
连接池在服务器初始化时创建一批连接,放入内存缓冲池,程序需要时从池子里取,使用完归还,连接复用,大幅提升性能;
-
连接池可配置最大连接数、最小空闲连接数等,避免数据库连接耗尽。
8.2 DriverManager vs DataSource
|
特性 |
DriverManager |
DataSource |
|---|---|---|
|
类型 |
工具类 |
JDBC标准接口 |
|
连接方式 |
新建物理连接 |
从连接池获取复用连接 |
|
性能 |
低(频繁创建/销毁) |
高(连接复用) |
|
连接池支持 |
不支持 |
天然支持 |
|
使用场景 |
学习/测试/小Demo |
企业项目/正式开发 |
8.3 主流连接池
-
Druid:阿里开源,功能强大(监控、防注入、配置灵活),国内企业首选;
-
HikariCP:SpringBoot官方推荐,轻量、速度最快;
-
c3p0/DBCP:老牌连接池,功能老旧,逐步被淘汰。
8.4 Druid连接池使用(整合DBUtils)
步骤1:引入Druid依赖
<!-- Maven依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
步骤2:修改DBUtils,整合Druid
将原始的DriverManager获取连接替换为Druid连接池:
package com.hg.utils;
import com.alibaba.druid.pool.DruidDataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class DBUtils {
private static DruidDataSource ds; // Druid连接池对象
static {
try {
// 读取配置文件
InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(is);
// 初始化Druid连接池
ds = new DruidDataSource();
ds.setDriverClassName(prop.getProperty("driverClass"));
ds.setUrl(prop.getProperty("url"));
ds.setUsername(prop.getProperty("username"));
ds.setPassword(prop.getProperty("password"));
// 连接池配置
ds.setInitialSize(2); // 初始连接数
ds.setMaxActive(5); // 最大活跃连接数
ds.setMinIdle(1); // 最小空闲连接数
} catch (Exception e) {
e.printStackTrace();
}
}
// 从连接池获取连接
public static Connection getConn() {
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("从连接池获取连接失败!");
return null;
}
}
// 通用关闭资源(Druid的close()是归还连接,不是销毁)
public static void close(AutoCloseable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:Druid重写了Connection的close()方法,调用close()并非销毁连接,而是将连接归还到连接池。
九、JDBC批处理(批量操作)
当需要执行大量相同结构的SQL(如批量插入1000条数据),使用批处理可大幅提升效率,核心通过addBatch()添加SQL,executeBatch()执行批量操作。
批处理示例(批量插入学生数据)
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Random;
public class BatchTest {
public static void main(String[] args) throws SQLException {
Connection conn = DBUtils.getConn();
// 预编译SQL,提升批量执行效率
String sql = "insert into student(id,name,age,sex) values(?,?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
Random random = new Random();
// 关闭自动提交,提升批量执行效率
conn.setAutoCommit(false);
for (int i = 1; i <= 1000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "学生" + i);
pstmt.setInt(3, 18 + random.nextInt(10));
pstmt.setString(4, random.nextBoolean() ? "男" : "女");
pstmt.addBatch(); // 添加到批处理队列
// 每500条执行一次,避免队列过大
if (i % 500 == 0) {
pstmt.executeBatch(); // 执行批处理
pstmt.clearBatch(); // 清空队列
}
}
// 执行剩余的批处理
pstmt.executeBatch();
// 手动提交事务
conn.commit();
System.out.println("批量插入成功!");
// 关闭资源
DBUtils.close(pstmt);
DBUtils.close(conn);
}
}
优化技巧:
-
使用
PreparedStatement预编译SQL; -
关闭自动提交,批量执行后手动提交;
-
分批次执行(如每500条),避免批处理队列过大。
十、JDBC核心知识点总结
-
JDBC本质:SUN提供的操作数据库的标准接口,数据库厂商提供驱动实现;
-
通用步骤:加载驱动→创建连接→创建Statement→执行SQL→处理结果→关闭资源;
-
防注入:使用
PreparedStatement预编译SQL,用?占位符接收参数; -
工具类封装:DBUtils封装连接和关闭资源,BaseDAO结合反射+泛型实现通用DAO;
-
事务处理:关闭自动提交
setAutoCommit(false),无异常commit(),有异常rollback(); -
性能优化:使用数据库连接池(Druid/HikariCP)、批处理、预编译;
-
核心接口:
Connection(连接)、PreparedStatement(预编译)、ResultSet(结果集)、DataSource(连接池)。
创作不易,觉得有帮助的话,点赞+收藏+关注吧!后续会持续更新Java后端核心知识点~
3859

被折叠的 条评论
为什么被折叠?



