[MySQL] JDBC
1.JDBC 的简介
JDBC (Java Data Base Connectivity,Java数据库连接)是 Java 程序和数据库之间的桥梁 , 包含了一套 Java 定义于执行 SQL 语句的接口 , 使开发者能够编写数据库的程序
JDBC 的主要作用 : 与数据库建立连接 , 发送 SQL 语句 , 处理数据库执行结果

JDBC 工作原理 : 加载驱动 , 建立连接 , 创建 Statement , 执行 SQL , 处理结果 , 关闭资源
2.为什么使用 JDBC
创建客户端操作数据库的过程 :
- 确定连接地址 , 添加端口号(数据源)
- 连接到数据库服务(数据库连接)
- 发送 SQL 语句(执行对象)
- 得到返回结果集并显示(结果集)
- 关闭连接(释放资源)
Java 采取的做法是把上述操作步骤定义了相应的接口 , 具体实现交给数据库厂商去做 , Java 程序员只需按照需要调用接口中的定义方法即可 , 这样不论使用什么数据库 , 都对 Java 程序没有任何影响 , 即使换一个数据库 , 也只需要换一下相应厂商的是是实现依赖
JDBC 使用过程可以概括为 : 加载数据库厂商的驱动包 , 建立连接 , 创建 Statement , 执行 SQL , 处理结果释放资源 , 关闭连接
3.使用 JDBC
3.1 创建 Maven 工程并配置国内镜像
- 修改 Maven 配置文件 , 在<mirrors></mirrors>标签中替换为以下代码
<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>- IDEA 中 Maven 的配置文件夹 : IDEA 安装目录/plugins/maven/lib/maven3/conf/settings.xml
3.2 获取 MySQL 的驱动包
在 mvnrepository.com 下搜索 MySQL , 找到最新的驱动包

3.3 修改 pom.xml 文件
在 maven 工程中的 pom.xml 中的<dependencies></denpendencies>标签中添加 MySQL 依赖
如图所示:

4.创建工程
import java.sql.*;
import java.text.MessageFormat;
import java.util.Scanner;public class test1 {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/bit_1027?characterEncoding=utf8" +"&allowPublicKeyRetrieval=true&useSSL=false","root","119856");// 3. 创建 statement对象statement = connection.createStatement();// 4. 定义SQL并执行SQL语句System.out.println("请输入学生姓名");Scanner scanner = new Scanner(System.in);//接收用户输入String name = scanner.next();String sql = "select id,name,gender ,age,class_id from students where name = '"+name+"'";// 5. 执行SQL, 获取查询结果集resultSet = statement.executeQuery(sql);// 6. 对结果集进行遍历 , 获取数据while(resultSet.next()){int id = resultSet.getInt(1);String stuname = resultSet.getString(2);String stugender = resultSet.getString(3);int age = resultSet.getInt(4);int class_id = resultSet.getInt(5);System.out.println(MessageFormat.format("学生编号 = {0} , 学生姓名 = {1} , 性别 = {2} , 年龄 = {3} , 班级编号 = {4}",id,stuname,stugender,age,class_id));}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}finally {// 依次从下往上释放资源,关闭连接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 语句
- 执行 select 查询时返回的是一个结果集 , 用 ResultSet 接收
- 执行 nsert , update , delete 操作时 , 返回的是受影响的行数 , 用 int 类型接收
- executeQuery() , 执行返回的是一个结果集 , 通常用于 select 操作
- executeUpdate() , 执行结果是一个整型 , 通常用于 insert , update , delete 操作

⑤ 处理结果集
- 如果返回的是一个结果集 , 则需要遍历这个集合获取对应列的值

⑥ 释放资源
- 在整个数据库访问的过程中的对象都需要释放 , 包括 : ResultSet,Statement和Connection, 后创建的先释放

5. 优化 1
1.SQL 注入
SQL注⼊即是指web应⽤程序对⽤⼾输⼊数据的合法性没有判断或过滤不严,攻击者可以在web应 ⽤程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现⾮法操 作,以此来实现欺骗数据库服务器执⾏⾮授权的任意查询,从⽽进⼀步得到相应的数据信息
示例 : 将 正常的条件值替换为
'or/**/1=1;# 
- 此操作仅用于理解 SQL 注入的危害,实际开发中必须使用
PreparedStatement参数化查询(如之前建议的代码修改),从根源上杜绝注入风险。 - 未经授权对系统进行注入测试属于违法行为,务必遵守法律法规和道德规范。
![]()
解决方案
使用 PreparedStatement 预编译 SQL 语句对象 , 实现参数化查询 , 杜绝 SQL 注入分享 , 符合企业级开发规范
通过 Connection 对象获取到 PreparedStatement 对象 , 需要传入 SQL 模板 , 动态参数用占位符 ? 表示

2.如果要执行别的 SQL 语句需要重复打开客户端
使用 DriverManager 每次调用 getConnection 方法都会初始化一个新的连接 , 使用完后会关闭真实连接 , 导致资源浪费

解决方案
使用 DataSource 连接池技术 , 会在初始化时 创建一定数量的数据库连接 , 这些连接可以重复使用 , 关闭时并不是真正关闭 , 而是将连接归还给连接池 , 以供后续使用 , 有效提高资源利用率和性能

import com.mysql.cj.jdbc.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.Scanner;public class test2 {public static void main(String[] args) {//定义MySQL数据源对象MysqlDataSource mysqlDataSource = new MysqlDataSource();//设置数据库连接串mysqlDataSource.setURL("jdbc:mysql://127.0.0.1:3306/bit_1027?characterEncoding=utf8" +"&allowPublicKeyRetrieval=true&useSSL=false");//用户名mysqlDataSource.setUser("root");//密码mysqlDataSource.setPassword("119856");//定义JDBC的数据源对象DataSource dataSource = mysqlDataSource;//定义连接对象Connection connection = null;//定义预处理SQL执行对象PreparedStatement statement = null;//定义结果集对象ResultSet resultSet = null;try {// 1.通过数据源获取数据库连接connection = dataSource.getConnection();// 2.获取预处理SQL执行对象//定义要执行的SQLString sql = "select id,name,gender ,age,class_id from students where name = ?";statement = connection.prepareStatement(sql);//接收用户输入System.out.println("请输入学生姓名");Scanner scanner = new Scanner(System.in);String name = scanner.next();// 3.用真实姓名替代占位符statement.setString(1,name);// 4.执行SQL ,获取结果集resultSet = statement.executeQuery();//遍历结果集while(resultSet.next()){int id = resultSet.getInt(1);String stuname = resultSet.getString(2);String stugender = resultSet.getString(3);int age = resultSet.getInt(4);int class_id = resultSet.getInt(5);System.out.println(MessageFormat.format("学生编号 = {0} , 学生姓名 = {1} , 性别 = {2} , 年龄 = {3} , 班级编号 = {4}",id,stuname,stugender,age,class_id));}// while(resultSet.next()){// int id = resultSet.getInt("id");// String stuname = resultSet.getString("name");// String stugender = resultSet.getString("gender");// int age = resultSet.getInt("age");// int class_id = resultSet.getInt("class_id");// // 打印逻辑不变// }} catch (SQLException e) {e.printStackTrace();}finally {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();}}}}}6. 优化 2
1. 封装
将 获取数据库连接 和 释放资源,关闭连接 等 , 封装起来
package util;import com.mysql.cj.jdbc.MysqlDataSource;
import com.mysql.cj.xdevapi.Collection;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;public class DBUtil {//数据源private static DataSource dataSource = null;//数据库连接对象private static final String URL = "jdbc:mysql://127.0.0.1:3306/bit_1027?characterEncoding=utf8" +"&allowPublicKeyRetrieval=true&useSSL=false";//用户名private static final String USER = "root";//密码private static final String PASSWORD = "119856";//当类加载到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();}}}
}示例 : 执行一条插入语句
import util.DBUtil;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;public class insertTest1 {public static void main(String[] args) {Connection connection = null;PreparedStatement statement= null;//插入语句不需要定义结果集 (insert返回的是受影响的行数)try {// 1.获取数据库连接connection = DBUtil.getConnection();// 2.定义SQL语句String sql = "insert into students(name,gender,age,class_id) values (?,?,?,?);";// 3.定义SQL预处理对象statement = connection.prepareStatement(sql);// 4.接收用户参数System.out.println("请输入姓名");Scanner scanner = new Scanner(System.in);String name = scanner.next();System.out.println("请输入性别");String gender = scanner.next();System.out.println("请输入年龄");Integer age = Integer.valueOf(scanner.next());System.out.println("请输入班级");Integer class_id = Integer.valueOf(scanner.next());// 5.用真实数据填充占位符statement.setString(1,name);statement.setString(2,gender);statement.setInt(3,age);statement.setInt(4,class_id);// 执行插入操作并获取受影响行数int rows = statement.executeUpdate();System.out.println("插入成功,受影响行数:" + rows);} catch (SQLException e) {e.printStackTrace();}finally {//释放资源,关闭连接DBUtil.close(null,statement,connection);}}
}