JDBC入门
JDBC(Java Database Connectivity)是 Java 提供的一种标准 API,用于连接和操作关系型数据库。它是 Java 程序和数据库之间的桥梁,允许开发人员通过 Java 代码与数据库交互,执行查询、更新和其他数据库操作。
一、JDBC的快速入门
- 官网下载数据库连接驱动jar包。https://downloads.mysql.com/archives/c-j/
- 创建Java项目,在项目下创建lib文件夹,将下载的驱动jar包复制到文件夹里
- 选中lib文件夹右键->
Add as Library
,与项目集成
创建数据库:
create database db05;
use db05;
create table t_emp
(
emp_id int auto_increment comment '员工编号'
primary key,
emp_name varchar(100) not null comment '员工姓名',
emp_salary double(10, 5) not null comment '员工薪资',
emp_age int not null comment '员工年龄'
);
insert into t_emp (emp_name,emp_salary,emp_age)
values ('andy', 777.77, 32),
('大风哥', 666.66, 41),
('康师傅',111, 23),
('Gavin',123, 26),
('小鱼儿', 123, 28);
编写代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCQuick {
public static void main(String[] args) throws Exception {
// 1、注册驱动
// 将数据库厂商提供的驱动类,通过类加载的方式加载到我们的程序当中
Class.forName("com.mysql.cj.jdbc.Driver");
// 2、获取连接对象
String url = "jdbc:mysql://localhost:3306/db05";
String username = "root";
String password = "password";
Connection connection = DriverManager.getConnection(url, username, password);
// 3、获取执行SQL语句的对象
Statement statement = connection.createStatement();
// 4、编写SQL语句并执行,接收返回的结果集
String sql = "select emp_id, emp_name, emp_salary, emp_age from t_emp";
ResultSet resultSet = statement.executeQuery(sql);
// 5、处理结果:遍历resultSet结果集
// resultSet.next()是判断下一行是否存在数据,并返回布尔类型的值
while(resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String s = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + s + "\t" + empSalary + "\t" + empAge);
}
// 6、释放资源(先开后关原则)
resultSet.close();
statement.close();
connection.close();
}
}
二、代码核心API的理解
1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
- 在 Java 中,当使用
JDBC (Java Database Connectivity)
连接数据库时,需要加载数据库特定的驱动程序,以使与数据库进行通信。加载驱动程序的目的是为了注册驱动程序,使得JDBC API
能够识别并与特定的数据库进行交互 - 在指定路径的
Driver
文件中,核心代码就是DriverManager.registerDriver(new Driver());
相当于是底层的创建驱动,因此在java代码中也可以使用这行代码进行创建 - 从
JDK6
开始,不再需要显式地调用Class.forName()
来加载JDBC
驱动程序,只要在类路径中集成了对应的jar
文件,会自动在初始化时注册驱动程序
2、连接对象Connection
Connection
接口是JDBC API
的重要接口,用于建立与数据库的通信通道,只要Connection
对象不为空,则代表一次数据库连接。在建立连接时,需要指定的数据库URL、用户名、密码参数。
- URL:
jdbc:mysql://localhost:3306/db05
jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2
- 如果要连接的是本机的数据库,则可以简写为
jdbc:mysql:///db05
,省略本机的IP地址:端口号
- URL:
Connection
接口还负责管理事务,Connection
接口提供了commit
和rollback
方法,用于提交事务和回滚事务可以创建
Statement
对象,用于执行SQL语句并与数据库进行交互在使用JDBC技术时,必须要先获取
Connection
对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏
3、Statement
Staticment
接口用于执行SQL语句并与数据库进行交互。它是JDBC API
中的一个重要接口。通过Staticment
对象,可以向数据库发送SQL语句并获取执行结果- 结果可以是一个或多个结果
- 增删改:受影响行数单个结果
- 查询:单行单列、多行多列、单行多列等结果
- 但是
Statement
接口在执行SQL语句时,会产生SQL
注入攻击问题:- 当使用
Statement
执行动态构建的SQL查询时,往往需要将查询条件与SQL语句拼接在一起,直接将参数SQL语句一并生成,让SQL的查询条件始终为true
得到结果
- 当使用
SQL注入的演示:
import javax.lang.model.util.SimpleAnnotationValueVisitor6;
import java.sql.*;
import java.util.Scanner;
public class JDBCInjection {
public static void main(String[] args) throws Exception {
//Sql注入问题
// 1、注册驱动
// 2、获取连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
// 3、获取执行SQL语句的对象
Statement statement = connection.createStatement();
System.out.println("请输入员工姓名:");
Scanner in = new Scanner(System.in);
// 允许输入空格
String name = in.nextLine();
// 4、编写SQL语句,并执行,接收返回的结果
String sql = "select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_name = '" + name + "'";
ResultSet resultSet = statement.executeQuery(sql);
// 5、处理结果,遍历resultSet
while(resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String s = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + s + "\t" + empSalary + "\t" + empAge);
}
// 6、释放资源(先开后关)
resultSet.close();
statement.close();
connection.close();
}
}
当从键盘获取的数据为:
组合后的语句就是select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_name = 'abc' or '1' = '1'
,也就是只要有一个条件满足就会执行(自己加一个true条件),就会查询到数据库中的所有结果
4、PreparedStatement
预防sql注入,将传入的值都变成字符,用户输入的单引号也会自动加上转义字符,进行成功预防sql注入
PreparedStatement
是Statement
接口的子接口,用于执行预编译的SQL查询,作用如下:- 预编译SQL语句:在创建
PreparedStatement
时,就会预编译SQL语句,也就是SQL语句已经固定 - 防止SQL注入:
PreparedStatement
支持参数化查询,将数据作为参数传递到SQL语句中,采用?
占位符的方式,将传入的参数用一对单引号包裹起来,无论传递什么都作为值。有效防止传入关键字或值导致SQL注入问题 - 性能提升:
PreparedStatement
是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析
- 预编译SQL语句:在创建
import java.sql.*;
import java.util.Scanner;
public class JDBCPrepared {
public static void main(String[] args) throws Exception {
//使用PrepareStatement来预防Sql注入问题
// 1、注册驱动
// 2、获取连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
// 3、获取执行SQL语句的对象
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id, emp_name, emp_salary, emp_age from t_emp where emp_name = ?");
System.out.println("请输入员工姓名:");
Scanner in = new Scanner(System.in);
// 允许输入空格
String name = in.nextLine();
// 4、为?占位符赋值,并执行SQL语句,接收返回的结果
// 索引是从占位符的1开始的,而不是0
preparedStatement.setString(1, name);
ResultSet resultSet = preparedStatement.executeQuery();
// 5、处理结果,遍历resultSet
while(resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String s = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + s + "\t" + empSalary + "\t" + empAge);
}
// 6、释放资源(先开后关)
resultSet.close();
preparedStatement.close();
connection.close();
}
}
5、ResultSet
ResultSet
是JDBC API
中的一个接口,用于表示从数据库中执行查询语句所返回的结果集。它提供了一种用于遍历和访问查询结果的方式- 遍历结果:
ResultSet
可以使用next()
方法将游标移动到结果集的下一行,逐行遍历数据库查询到结果,返回值为boolean
类型,true
代表有下一行结果,false
则代表没有 - 获取单列结果:可以通过
getXxx
的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取
三、基于PreparedStatement
实现CRUD
CRUD
的理解
CRUD 是数据库和软件开发中常用的术语,表示对数据的基本操作,包含四种功能:
- Create(创建):插入或添加新的数据。
- Read(读取):查询或读取现有的数据。
- Update(更新):修改已有的数据。
- Delete(删除):移除数据。
这些操作是管理和操作数据库中数据的核心,是所有数据库应用程序和接口设计的基础。
1、查询单行单列
@Test
public void testQuerySingleRowAndCol() throws Exception {
// 1.注册驱动
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
// 3.预编译SQL语句,得到PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement("select count(*) as count from t_emp");
// 4.执行SQL语句,获取结果
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果(如果自己明确只有一个结果,那么resultSet最少要做一次next的判断,才能拿到我们要的列的结果)
while(resultSet.next()) {
// 对于结果是一行一列的情况,可以直接使用下标获取
System.out.println(resultSet.getInt(1)); // 输出: 5
// 也可以使用别名
System.out.println(resultSet.getInt("count")); // 输出: 5
}
// 6.关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
注意事项:
- 使用
.next()
方法时,查询开始的第一行是在字段名的那一栏,所有即使是单行,仍然需要调用该方法,否则编译错误- 如果已知结果就是单行单列,也可以直接使用下标1获取
- 如果名字较为复杂时,也可以通过给字段名取别名的形式使用别名的字段标签获取
2、查询单行多列
@Test
public void testQuerySingleRow() throws Exception {
// 1.获取驱动
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
// 3.预编译SQL语句获取PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_id = ?");
// System.out.println("请输入要查找的员工ID:");
// Scanner in = new Scanner(System.in);
// int id = in.nextInt();
// 给占位符?赋值
preparedStatement.setInt(1, 5);
// 4.执行,并接收结果
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果
while(resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_Salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + empName + "\t" + empSalary + "\t" + empAge);
}
// 6.资源释放
resultSet.close();
preparedStatement.close();
connection.close();
}
注意事项:
- 使用占位符的时候,一定要给占位符赋值
- 如果使用注解
@Test
,测试方法在运行时,无法接收用户的输入,如果想要接收,则可以使用main
方法- 同样的,即使只有一行结果,仍然需要先调用一次
.next()
方法
3、查询多行多列
@Test
public void testQueryMoreRow() throws Exception {
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
PreparedStatement preparedStatement = connection.prepareStatement("select emp_id, emp_name, emp_salary, emp_age from t_emp where emp_age > ?");
preparedStatement.setInt(1, 25);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_Salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + empName + "\t" + empSalary + "\t" + empAge);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
4、插入数据
@Test
public void testInsert() throws Exception {
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp (emp_name, emp_salary, emp_age) VALUES (?, ?, ?)");
preparedStatement.setString(1, "小明");
preparedStatement.setDouble(2, 15000.26);
preparedStatement.setInt(3, 25);
int result = preparedStatement.executeUpdate();
if (result > 0) {
System.out.println("成功!");
}else {
System.out.println("失败!");
}
preparedStatement.close();
connection.close();
}
注意事项:
- 添加完数据只用通过调用
preparedStatement.executeUpdate()
方法判断此作用的影响行即可,大于0,则表示插入成功;反之失败- 该方法无需调用
.executeQuery()
创建ResultSet
对象,因此释放内存时也只用释放两个变量
5、修改数据
@Test
public void testUpdate() throws Exception {
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
PreparedStatement preparedStatement = connection.prepareStatement("update t_emp set emp_salary = ? where emp_id = ?");
preparedStatement.setDouble(1, 23000.66);
preparedStatement.setInt(2, 5);
int result = preparedStatement.executeUpdate();
if (result > 0) {
System.out.println("成功!");
}else {
System.out.println("失败!");
}
preparedStatement.close();
connection.close();
}
注意事项:
- 修改数据和插入数据一样,都不需要调用
.executeQuery()
创建ResultSet
对象,且调用的判断方法也是.executeQuery()
6、删除数据
@Test
public void testDelete() throws Exception {
Connection connection = DriverManager.getConnection("jdbc:mysql:///db05", "root", "password");
PreparedStatement preparedStatement = connection.prepareStatement("delete from t_emp where emp_name = ?");
preparedStatement.setString(1, "小明");
int result = preparedStatement.executeUpdate();
if (result > 0) {
System.out.println("成功!");
}else {
System.out.println("失败!");
}
preparedStatement.close();
connection.close();
}
四、可能会出现的问题
1、资源的管理
- 在使用JDBC相关的资源时,使用完毕后需要及时关闭这些资源以释放数据库服务器资源和避免内存泄漏是很重要的
2、SQL语句问题
java.sql.SQLSyntaxErrorException
SQL语句错误:- SQL语句有错误,建议先在sql工具中调试好
- 连接数据库的URL中,数据库名称编写错误,也会报这个错误
3、SQL语句未设置参数问题
java.sql.SQLException: No value specified for parameter 1
在使用预编译的SQL语句时,如果有?
占位符,要为给每一个占位符赋值,否则就会报该错
4、用户名或密码错误问题
- 连接数据库时,如果用户名或密码输入错误,也会报
SQLException
,看清楚后面错误的描述
5、通信异常
CommunicationsException
在连接数据库的URL中如果IP或端口写错了,就会报这种错误
资源以释放数据库服务器资源和避免内存泄漏是很重要的
2、SQL语句问题
java.sql.SQLSyntaxErrorException
SQL语句错误:- SQL语句有错误,建议先在sql工具中调试好
- 连接数据库的URL中,数据库名称编写错误,也会报这个错误
3、SQL语句未设置参数问题
java.sql.SQLException: No value specified for parameter 1
在使用预编译的SQL语句时,如果有?
占位符,要为给每一个占位符赋值,否则就会报该错
4、用户名或密码错误问题
- 连接数据库时,如果用户名或密码输入错误,也会报
SQLException
,看清楚后面错误的描述
5、通信异常
CommunicationsException
在连接数据库的URL中如果IP或端口写错了,就会报这种错误