JDBC(Java Data Base Connectivity, Java数据库连接 )是Java程序和数据库之间的桥梁,包含 了⼀套Java定义的用于执行SQL语句的接口,使开发者能够编写数据库的程序。JDBC 的主要作用是: 与数据库建立连接、发送SQL语句和处理数据库执行结果。
- 接口是一套规则,接口中定义一系列系统方法,这些方法由具体的实现类去完成实现逻辑。而JDBC是Java平台提供的接口,具体的实现是由数据库厂商完成的。
- 之前学的都是操作数据库的语言------SQL,而JDBC是Java语言中用来执行SQL语句的一套API(应用程序编程接口),它让Java开发者能够通过编写代码来发送SQL语句,并处理数据库返回的结果。
- 简单来说,SQL是"语言",JDBC是Java访问数据库的"工具"。

访问数据库要经过以下几个重要步骤:
- 确定数据库服务器的地址,端口号
- 建立连接,用户名,密码(不同的数据库以不同的协议建立连接)

- 发送要执行的SQL语句:以什么样的形式发送,这里主要考虑编码的格式(协议------就是事先商量好的规则)
- 接受返回的结果(结果集,受影响的行数)
使用Java操作数据库,必须处理不同数据库对数据的编解码,这些是由数据库厂商实现的。 - 关闭连接。
以上的五步对应到JDBC中就是:
- 确定数据源
- 数据库连接
- 确定执行对象
- 得到结果集
- 释放资源,关闭连接
总结:
- Java是把以上操作步骤定义了相应的接⼝,具体的实现交给数据库⼚商去做,Java程序员只需要按照需要调用接⼝中定义的方法即可,这样不论使用什么数据库,都对于Java程序没有任 何影响,即便是换⼀个数据库,也只需要换⼀下相应厂商的实现依赖。
- JDBC使用过程可以概括为:加载数据库厂商的驱动包、建⽴连接、创建Statement、执行SQL、 处理结果释放资源和关闭连接。
1.使用JDBC
1.1 settings.xml文件配置-配置国内镜像
首先需要打开idea的settings设置界面,通过以下的操作找到idea中内置的Maven,Maven就类似于应用商店,在maven仓库中维护了所有的Java工程需要用到的依赖:

但是这个maven仓库默认是国外的,会导致下载速度慢等问题,所以要把它修改成国内的,就是在 Maven 的 settings.xml 中配置国内镜像(如阿里云),那么要修改 settings.xml 的配置,要找到idea的安装路径:
右击 idea 打开属性,选择打开文件所在位置,然后通过以下的具体路径找到 settings.xml文件:

然后打开 settings.xml 文件,修改这个Maven配置文件,在 <mirror></mirror> 标签中添加 mirror 子节点,将以下的代码整个复制替换到 <mirror> 标签中(镜像的父节点也统一替换掉):
XML
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
-->
<!-- 加入如下mirror节点 使用国内阿里云仓库镜像 开始 -->
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>central</id>
<mirrorOf>*</mirrorOf>
<name>aliyun central</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
<mirror>
<id>spring</id>
<mirrorOf>*</mirrorOf>
<name>aliyun spring</name>
<url>https://maven.aliyun.com/repository/spring</url>
</mirror>
<!-- 加入如下mirror节点 使用国内阿里云仓库镜像 结束-->
</mirrors>
替换如下图,然后保存修改结果:
修改完配置之后,我们可以将 settings.xml 这个文件复制到我们自定义的目录中,然后修改设置文件路径,同样的 repository 也一样:
如果设置完目录路径没有找到记得点击刷新,或者重启 idea:
1.2 创建Maven工程并修改 pom.xml 文件
完成上述的操作,现在我们开始创建一个Maven工程:
打开这个项目之后,就会直接显示 pom.xml 文件:
我们需要在工程中的pom.xml中的新增的<dependencies></dependencies>标签中添加MySQL依赖,在添加之前,我们需要获取MySQL驱动包(通过在 pom.xml 中声明依赖,Maven 会根据坐标自动从仓库(本地或镜像)下载对应的 .jar 文件):
在Maven仓库Maven Repository: Search/Browse/Explore搜索MySQL,找到下载的对应MySQL版本的驱动包:

如以上画红框的XML,就是要添加进pom.xml的依赖,添加进去之后,一定要记得点击「刷新」按钮(🔄)刷新,如果没有找到,就到Maven面板里面点击刷新:
就会在 repository 目录之下生成许多文件:
但是,如果上述XML添加进去之后刷新后报错或者关闭Maven项目后再打开报错了,那么将坐标改成新的坐标,然后再点击刷新:
原因 :改为新坐标之前刷新生成的文件是 Maven 按照新坐标 com.mysql:mysql-connector-j 自动下载的驱动包(因为旧坐标 mysql:mysql-connector-java 从 8.0.31 起就被重定向到新坐标了)。
最后,如果在Maven项目左侧目录中出现了8.0.33,也就是刚才在pom.xml中添加的依赖,就说明已经加载到工程中了:
以上的过程也就是------加载数据库厂商的驱动包。接下来通过数据库厂商提供的驱动来操作数据库,也就是------建立数据库连接,在开始建立之前,我们先学习JDBC常用的接口和类。
1.3 JDBC常用的接口和类
1.3.1 DriverManager 和 DataSource
- DataSource驱动管理类,用于管理JDBC驱动程序,可以从驱动程序中获取数据库连接,始于 JDK1.1。
- DataSource数据源是DriverManager的替代方案,是一个接口,始于JDK1.4,是获取数据库连接的首选方法, 推荐使用。
两者区别:
- DriverManager和DataSource都可以获取到数据库连接,但它们之间存着着⼀些区别,主要在于连接的管理方式和资源利用效率。
- 连接管理方式不同:
- DriverManager每次调用 getConnection 方法都会初始化⼀个新的连接,使用完成后会关闭真实连接,导致资源浪费。也就是说,该类每调用一次 getConnection 方法,都会开启一个新的会话/客户端。
- DataSource使用了连接池的技术,会在初始化时创建⼀定数量的数据库连接,这些连接可以重复使用,关闭时并不是真正关闭连接,而是将连接归还给连接池,以供后续使用,有效地提高资源利用率和和性能。
当使用DriverManager来获取数据库连接时,需要先手动加载数据库厂商提供的驱动类,使用以下的代码获取:
java
Class.forName("com.mysql.cj.jdbc.Driver");
以上代码中参数是通过完全限定名加载指定类到JVM ,也就是说,Class.forName("com.mysql.cj.jdbc.Driver");本质是让 JVM 加载 MySQL 驱动类,并执行它的静态代码块,MySQL 驱动类(Driver)的核心源码:

【注意:MySQL 5.x 版本(JDBC 3.0 及以下):必须手动加载。MySQL 8.0+ 版本(JDBC 4.0+):可以省略手动加载,但是建议保留。】
手动加载完驱动类之后,使用DriverManager类中的getConnection()方法获取数据库连接,该方法的返回值类型为Connection类,以下是源码:

- 其中,该方法的第一个参数 url :JDBC 连接数据库的 URL 地址,用来指定要连接的数据库类型、地址、端口、库名等信息。格式规范(以 MySQL 为例):

- 第二个参数 user :数据库的 用户名,用于身份认证,验证你是否有权限访问该数据库。
- 第三个参数 password :数据库用户对应的 密码,和 user 配对,完成身份认证。
示例:forName方法和getConneaction方法会抛出异常,以下URL中的数据库以DesignTable为例
java
public static void main(String[] args) {
try {
//加载数据库厂商提供的驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/DesignTable?characterEncoding=utf8" +
"&allowPublicKeyRetrieval=true&useSSL=false", "root", "123456");
} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch(SQLException e) {
e.printStackTrace();
}
}
当使用DataSource来获取数据库连接(getConnection())时,完全不用手动加载驱动,因为连接池会自动处理驱动加载。
DataSource是JDBC 定义的通用数据库连接接口,只规定了 "获取连接" 的方法 ,但不实现具体逻辑;而 MysqlDataSource 是 MySQL 驱动专门为 MySQL 数据库编写的 DataSource 实现类,是 DataSource 接口的「MySQL 专属版本」。简单说:DataSource 是 "抽象的连接池标准",MysqlDataSource 是 "MySQL 对这个标准的具体落地"。
因此,在使用 DataSource 获取数据库连接之前,先使用 MysqlDataSource 实现类配置一下MySQL连接的信息(URL,User,Password):
创建MysqlDataSource对象,并使用setURL(),setUser(),setPassword()方法设置连接信息,最后DataSource 获取 MysqlDataSource 设置的连接信息,并使用getConnection()方法实现数据库连接。
示例:注意getConnection()是不带参数的。
java
public static void main(String[] args) {
//定义MySQL数据源对象
MysqlDataSoruce mysqlDataSoruce = new MysqlDataSource();
//设置数据库连接信息
mysqlDataSource.setURL("jdbc:mysql://127.0.0.1:3306/DesignTable?characterEncoding=utf8" +
"&allowPublicKeyRetrieval=true&useSSL=false");
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("123456");
//定义JDBC数据源对象
DataSource dataSource = mysqlDataSource;
try {
//获取数据库连接
Connection connection = dataSource.getConnection();
} catch(SQLException e) {
e.printStackTrace();
}
}
通过上述的操作,已经将数据库与Java建立了联系(JDBC),那么接下来要在Java中对数据库进行操作。
1.3.2 Connection 数据库连接
数据库连接(会话)对象,在连接的上下文中执行SQL语句并返回结果。也就是开始使用插入/更新/查询语句。
详细版本:Connection 接口是 Java 程序和数据库之间的 **「桥梁 / 会话」** ------ 所有对数据库的操作(执行 SQL、事务管理、创建执行对象)都必须通过 Connection 来完成,它是 JDBC 中最核心的接口之一。
Connection 接口的作用 最常见的就是创建执行 SQL 的对象: Connection 最常用的功能是创建 Statement/PreparedStatement 对象 ------ 这些对象是真正执行 SQL 的「工具」,没有 Connection 就无法创建**。** 
Statement
-
用于执行静态SQL语句并返回执行结果,由于只能执行静态语句,所以这里会有⼀个问题,假设⼀ 个语句中需要动态的参数,比如where子句中的条件,那么只能通过字符串拼接的方式组装完成的 SQL语句,比如:
javaString sql = "select id,name,sn,mail,class_id from student where name = '" + name + "'"; -
上述字符串拼接形式构造SQL语句 时,如果不处理参数中的特殊字符就会造成SQL注入,这是⼀个非常严重的安全性问题。
-
上述查询语句,以数据库 designtable 中的student表为例:

SQL注入即是指web应用程序++对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应 ⽤程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询++,从而进⼀步得到相应的数据信息。
示例 :查询名字为王五的记录,正常执行:
构造恶意参数来执行非法的SQL,把正常的条件值王五替换成**'or/**/1=1;#'** :
以上查询出来了所有的记录,此时学生表中的数据全部都将泄漏,这就是 使用Statement对象 执行SQL语句会遇到的 SQL注入 问题。
为了解决这个问题,可以使用 PreparedStatement 接口,该接口继承于 Statement :
PreparedStatement
预编译SQL语句对象,SQL语句被预编译并存储在PreparedStatement对象中,可以使用该对象多次执行SQL语句,同时可以解决SQL注入问题。
与 Statement 最关键的区别就在 SQL语句上,Statement使用的是字符串拼接,这样极易被注入,而 PreparedStatement 使用的是占位符(?) ,这样天然的预防了注入的风险:
java
String sql = "select id,name,sn,mail,class_id from student where name = ?";
1.3.3 executeQuery() 和 executeUpdate()
executeQuery()
执行结果返回的是⼀个结果集,通常用于select 操作。返回值的类型是ResultSet类。
executeUpdate()
执行结果返回的是⼀个整形(表示受影响的行数),通常用于insert,update,delete操作。返回值的类型是整型。
Statement 和 PrepareStatement 的区别
核心:Statement 是「简单 SQL 执行器」,每次执行都重新编译 SQL,有 SQL 注入风险;PreparedStatement 是「预编译 SQL 执行器」,SQL 只编译一次、可复用,能防 SQL 注入,是生产环境的首选。
执行方法:传 SQL vs 无参执行:
- Statement:必须在执行时传入完整 SQL 字符串(因为每次都要编译),即executeQuery(sql)/executeUpdate(sql);
- PreparedStatement:预编译时已传入 SQL,执行时无参,即executeQuery()/executeUpdate()
参数传递:字符串拼接 vs 占位符:
- Statement:字符串拼接(极易被注入):恶意输入的**'or/**/1=1;#'**被拼接到 SQL 中,数据库会把它当成合法 SQL 指令执行。
- PreparedStatement:占位符(天然防注入):PreparedStatement 把「SQL 结构」和「参数值」完全分离 ------------ 用 ? 占位符 + setXxx() 赋值 :SQL 预编译时只解析结构(select id,name,sn,mail,class_id from student where name = ? ),参数值会被数据库当作「纯数据」处理,即使用 setXxx() 方法用真实值去替换 ?,不会被解析为 SQL 指令,从根源杜绝注入
- 在前面介绍 Connection 接口时说过,执行SQL语句的对象 Statement 和 PrepareStatement 分别是 connection.createStatement() 和 connection.prepareStatement(sql),也就是在创建 Statement对象时不需要闯入SQL语句,而创建 PrepareStatement对象时需要传入SQL语句,这是为了防止SQL注入

- 在前面介绍 Connection 接口时说过,执行SQL语句的对象 Statement 和 PrepareStatement 分别是 connection.createStatement() 和 connection.prepareStatement(sql),也就是在创建 Statement对象时不需要闯入SQL语句,而创建 PrepareStatement对象时需要传入SQL语句,这是为了防止SQL注入
对比总结图:

1.3.4 ResultSet 结果集
- 是⼀个查询结果集的数据表,通常由执行查询操作的语句生成。执行插入,更新,删除的操作不需要用到,因为查询的结果只会显示受影响的行数。
- ResultSet对象维护了⼀个指向当前数据行的游标,最初游标位于第⼀行之前,调用next方法法将游标移动到下⼀行,当ResultSet中没有更多的数据行时返回false,所以可以在while循环中使⽤它来遍历结果集。------ 迭代器
- ResultSet接⼝提供了getter方法法(getString、getBoolean、getLong 等),用于从当前行检索列值,可以使用列的索引号或列的名称来检索值 。⼀般来说,使用列索引会更有效率,索引编号从1开始,按照从左 到右的顺序读取。
1.3.5 释放资源
在整个数据库访问过程中创建的对象都需要释放,包括:ResultSet,Statement和Connection, 后创建的先释放。使用ResultSet,Statement和Connection的close()方法释放,该方法会抛出一个异常。
1.3.6 示例
到这里,JDBC中常用的接口和类全部学习完成,现在我们利用上述学的知识在Java中实现一个SQL查询语句,例如以下:
1)使用 DriverManager 和 Statement
java
public class Demo_DataSource {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultset = null;
try {
//1.加载数据库厂商提供的驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/DesignTable?characterEncoding=utf8" +
"&allowPublicKeyRetrieval=true&useSSL=false", "root", "123456");
//3.创建Statement对象
statement = connection.createStatement();
//4.定义SQL语句
System.out.println("请输入学生姓名:");
Scanner scanner = new Scanner(System.in);
//接收用户输入
String name = scanner.next();
String sql = "select id,name,sn,mail,class_id from student where name = '" + name + "'";
//5.执行SQL语句
resultset = statement.executeQuety(sql);
//6.遍历结果集,获取数据
while (resultSet.next()) {
long stuId = resultSet.getLong(1);
String stuName = resultSet.getString(2);
String stuSn = resultSet.getString(3);
String stuMail = resultSet.getString(4);
Long classId = resultSet.getLong(5);
System.out.println(MessageFormat.format("学生编号={0},姓名={1},学号={2},邮箱={3},班级编号={4}",
stuId,stuName,stuSn,stuMail,classId));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//7.依次释放资源,关闭连接
if(resultset != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
但是,通过前面的学习我们直到,Statement会有SQL注入的问题,因此可以使用PreparedStatement预处理执行SQL语句。并且 DriverManager 类每次使用都会获取一个新的连接,导致浪费,那么可以使用 DataSource 接口,该接口一次链接可以执行很多SQL,直到关闭资源close(通过一个连接池管理很多个连接,当需要SQL执行的时候,从连接池拿一个空间连接出来,用完后返还给连接池)。
2)优化:使用 DataSource 和 PreparedStatement
java
public class Demo_DataSource {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultset = null;
//定义MySQL数据源
MysqlDataSource mysqlDataSource = new MysqlDataSource();
//设置连接信息
mysqlDataSource.setURL("jdbc:mysql://127.0.0.1:3306/DesignTable?characterEncoding=utf8" +
"&allowPublicKeyRetrieval=true&useSSL=false");
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("123456");
//1.定义JDBC数据源
DataSource dataSource = mysqlDataSource;
try {
//2.获取数据库连接
connection = dataSource.getConnection();
//3.创建预处理对象
//定义SQL语句
String sql = "select id,name,sn,mail,class_id from student where name = ?";
preparedStatement = connection.preparedStatement(sql);
//接受用户输入
System.out.println("请输入学生姓名:");
Scanner scanner = new Scanner(System.in);
String name = scanner.next();
//4.用真实值替换占位符(?)
preparedStatement.setString(1,name);
//5.执行SQL,获取结果集
resultset = preparedStatement.executeQuery();
//6.遍历结果集
while (resultSet.next()) {
long stuId = resultSet.getLong(1);//也可以是具体的列,例如1改成id
String stuName = resultSet.getString(2);
String stuSn = resultSet.getString(3);
String stuMail = resultSet.getString(4);
Long classId = resultSet.getLong(5);
System.out.println(MessageFormat.format("学生编号={0},姓名={1},学号={2},邮箱={3},班级编号={4}",
stuId,stuName,stuSn,stuMail,classId));
}
}catch (SQLException e) {
e.printStackTrace();
} finally {
//6.依次释放资源,关闭连接
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:


3)再优化:封装
可以将数据源信息,资源的关闭单独在一个类DBUtil中进行封装,想要使用的时候再进行调用。
- 在DBUtil类中,定义静态成员变量:数据源DataSource;定义静态常量:URL,User,Password,将MySQL数据源的定义与连接信息的设置定义在静态构造方法中,这样当类加载时,就会执行数据源的初始化。
- 由于在获取数据库连接时需要DataSource.getCOnnection()方法,那么创建一个公共方法getConnection()进行返回获取,记得抛出一个异常。
- 最后close()方法关闭资源。
- 可以将DBUtil 构造方法私有化,防止new这个对象。
java
public class DBUtil {
//数据源
private static DataSource dataSource = null;
//数据库连接串
private static final Strinf URL = "jdbc:mysql://127.0.0.1:3306/DesignTable?characterEncoding=utf8" +
"&allowPublicKeyRetrieval=true&useSSL=false";
private static final String USER = "root";
private static final String PASSWORD = "123456";
//当类加载到JVM时,执行数据源的初始化
static {
MysqlDataSource mysqlDataSOurce = new MysqlDataSource();
mysqlDataSOurce.setURL(URL);
mysqlDataSOurce.setUser(USER);
mysqlDataSOurce.setPassword(PASSWORD);
dataSource = mysqlDataSOurce;
}
//构造方法私有化,防止new这个对象
private DBUtil() {};
//获取数据库连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//释放资源,关闭连接
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
接下来,我们以往数据库中新增insert一条关于学生小华的记录为例在Java中执行SQL语句:
注意:插入操作不需要定义结果集对象ResultSet,insert返回的是受影响的行数
java
public class Demo_insert {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//1.获取数据连接
connection = DBUtil.getConnection();
//2.定义SQL语句
String sql = "insert into student(name,sn,mail,class_id) values(?,?,?,?)";
//3.创建预处理对象
preparedStatement = connection.preparedStatement(sql);
//4.接受用户输入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入姓名:");
String name = scanner.next();
System.out.println("请输入学号:");
String sn = scanner.next();
System.out.println("请输入邮箱:");
String mail = scanner.next();
System.out.println("请输入班级编号:");
Long classId = Long.valueOf(scanner.next());
//5.用真实值替换?
preparedStatement.setString(1,name);
preparedStatement.setString(2,sn);
preparedStatement.setString(3,mail);
preparedStatement.setLong(4,classId);
//6.执行SQL语句
int row = preparedStatement.executeUpdate();
//7.判断结果
if (row == 1) {
System.out.println("插入成功");
}else {
System.out.println("插入失败");
}
}catch (SQLException e) {
e.printStackTrace();
} finally {
//8.释放资源,关闭连接
DBUtil.close(null,preparedStatement,connection);
}
}
}
输出结果:执行插入小华的数据记录,插入成功后,当我们打开数据库的客户端,去查询表中的记录,发现确实有增加了一条数据,这就说明了数据库与Java之间相互连接了(JDBC):
