JDBC基础用法
JDBC简介
- JDBC(Java Database Connectivity) 是Java语言访问数据库的标准接口。它允许Java应用程序与各种数据库进行通信。
JDBC架构
- JDBC API:
- Java应用程序通过JDBC API与数据库进行交互。这个API定义了一系列接口和类,用于连接数据库、发送查询、处理结果等操作。
- JDBC Driver Manager:
- JDBC Driver Manager负责加载和管理数据库驱动程序。它是一个基本的服务,用于建立数据库连接。
- JDBC Driver:
- JDBC Driver是实现JDBC API的具体驱动程序。不同的数据库厂商提供不同的JDBC驱动程序,用于连接其特定的数据库系统。常见的JDBC驱动类型包括四种:JDBC-ODBC桥接器驱动、本地API驱动、网络协议驱动和本地协议驱动。
- Connection:
- Connection对象表示与数据库的连接。通过Connection对象,Java应用程序可以与数据库建立通信。
- Statement:
- Statement对象用于向数据库发送SQL语句。有三种类型的Statement:Statement、PreparedStatement和CallableStatement,分别用于不同类型的SQL操作。
- ResultSet:
- ResultSet对象代表数据库查询的结果集。通过ResultSet,Java应用程序可以获取查询结果并对其进行处理。
- SQLException:
- SQLException是JDBC中的异常类,用于处理与数据库交互过程中可能出现的异常情况。
JDBC连接步骤
- 加载数据库驱动程序:
- 使用Class.forName()方法加载数据库驱动程序,例如Class.forName(“com.mysql.cj.jdbc.Driver”);。
- 建立数据库连接:
- 使用DriverManager.getConnection()方法建立与数据库的连接,传入数据库的URL、用户名和密码,
- 如Connection connection = DriverManager.getConnection(“jdbc:mysql://localhost:3306/mydatabase”, “username”, “password”);。
- 创建Statement对象:
- 通过连接对象创建Statement对象,用于执行SQL语句,
- 例如Statement statement = connection.createStatement();。
- 执行SQL查询:
- 使用Statement对象的executeQuery()方法执行查询语句,
- 如ResultSet resultSet = statement.executeQuery(“SELECT * FROM mytable”);。
- 处理查询结果:
- 遍历ResultSet对象获取查询结果,可以使用while(resultSet.next())来逐行处理结果集。
- 执行更新操作:
- 如果需要执行更新操作(如插入、更新、删除数据),可以使用Statement对象的executeUpdate()方法,
- 例如int rowsAffected = statement.executeUpdate(“INSERT INTO mytable VALUES (1, ‘John Doe’)”);。
- 关闭连接:
- 在完成数据库操作后,需要关闭ResultSet、Statement和Connection对象,以释放资源,顺序为先关闭ResultSet,然后是Statement,最后是Connection,
- 如resultSet.close(); statement.close(); connection.close();。
JDBC常用接口和类
- Connection:表示与特定数据库的连接。
- Statement:用于执行静态SQL语句。
- PreparedStatement:继承自Statement,用于执行预编译的SQL语句。
- ResultSet:表示数据库结果集的数据表。
预编译语句和防止SQL注入
- 使用PreparedStatement可以预编译SQL语句,提高性能并防止SQL注入攻击。
事务管理
- 使用Connection对象可以管理事务,通过commit()和rollback()方法提交或回滚事务。
批处理
- 使用addBatch()和executeBatch()可以执行批处理操作,提高效率。
元数据
- 使用DatabaseMetaData和ResultSetMetaData可以获取数据库和结果集的元数据信息。
异常处理
- 在JDBC编程中,需要适当处理SQL异常,以确保程序的稳定性。
statement的详细使用
模拟登录
public class StatementUserLoginPaart {
public static void main(String[] args) throws SQLException {
Scanner sc = new Scanner(System.in);
System.out.println("Enter your username: ");
String username = sc.nextLine();
System.out.println("Enter your password: ");
String password = sc.nextLine();
sc.close();
/**
* 1.注册驱动
* TODO:
* 注册驱动
* 依赖:驱动版本8+ com.mysql.cj.jdbc.Driver
* 驱动版本5+ com.mysql.jdbc.Driver
*
* 此代码看会注册两次驱动:DriverManager.registerDriver(new Driver());
* 1.DriverManager.registerDriver()方法本身会被注册一次
* 2.Driver.static{DriverManager.registerDriver()}静态代码块也会注册一次
*
* 解决方法:只触发静态代码块即可
* 在进行类加载的时候会触发静态代码块
* 触发类加载:
* 1.new
* 2.调用静态方法
* 3。调用静态属性
* 4.接口1.8default默认实现
* 5.反射
* 6.子类触发父类
* 7.程序入口
*
*/
//方案1
// DriverManager.registerDriver(new Driver());
//方案2:mysql新版本驱动,换成Oracle就会出现问题
//new Driver
//方案三,利用反射;字符串->提取到外部的配置文件->可以在不改变代码的情况下实现数据库的切换
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
/**
* 2.获取连接
* TODO:
* java程序,连接数据库也是调用某个方法,方法也需要填入连接数据库的基本信息
* 数据库ip:
* 数据库端口号:
* 账号:
* 密码:
* 连接数据库的名称:
*/
String url = "jdbc:mysql://localhost:3306/dbtest";
String user = "root";
String pass = "root";
Connection conn = DriverManager.getConnection(url, user, pass);
//创建发生SQL语句的statement对象
Statement stmt = conn.createStatement();
//发送SQL语句
//这种拼接字符串的方式会出现注入攻击的可能,比如直接使用逻辑或进行短接
String sql = "SELECT * FROM user WHERE username ='"+username+"' AND password ='"+password+"';";
//用于执行会修改数据的操作,返回受到影响的数据条数
// int i = stmt.executeUpdate(sql);
//用于查询语句,返回查询到的结果集
ResultSet resultSet = stmt.executeQuery(sql);
//查询结果集进行解析
/**
* resultSet -> 逐行获取数据->每行数据为一个记录
* resultSet内部包含一个游标,指定当前行数据
* 默认游标指定的是第一行数据之前的
* 我们可以调用next()向后移动一行
*
* 我们可以使用while(next)来访问每一个数据
* boolean = next() true:有更多数据,并向下移动一行
* false:没有更多行数据
*
* resultSet.get数据类型(列名)来获取数据类型对应的列
*/
if (resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("username");
System.out.println("ID: "+id+" | Name: "+name+"login susess");
}else {
System.out.println("No such user");
}
//关闭资源
//从里到外关闭
resultSet.close();
stmt.close();
conn.close();
}
}
PreparedStatement的详细使用
在使用上与statement的使用对比
- Statement
- 先创建了发送SQL语句的statement对象,
- 再将SQL语句用字符串拼接的方式传送给对应的方法
- 由于该方法不是预编译的,JDBC不知道所要执行的SQL语句类型故要指定执行的方法
- PreparedStatement
- 先编写sql语句并用问号对动态值进行替代,
- 再创建PreparedStatement对象,
- 再使用set数据类型(index,obj)方法来对替代部分进行赋值。
- 最后使用ps.executeQuery()或者ps.executeUpdate()执行sql语句
- 前者用于查询语句返回的是ResultSet对象,后者用于数据修改,返回影响数据条数
PreparedStatement
//编写sql语句并用问号对动态值进行替代
String sql = "select * from users where username=? and password=?";
//创建预编译statement并设置SQL语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//单独的占位符进行赋值
/**
* 参数1:index 占位符的位置,从左到右从1开始
* 参数2:object 占位符的值,可以设置任何数据类型的数据
*/
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
//发送SQL语句并获取返回结果
ResultSet resultSet = preparedStatement.executeQuery();
Statement
//创建发生SQL语句的statement对象
Statement stmt = conn.createStatement();
//发送SQL语句
//这种拼接字符串的方式会出现注入攻击的可能,比如直接使用逻辑或进行短接
String sql = "SELECT * FROM user WHERE username ='"+username+"' AND password ='"+password+"';";
//用于执行会修改数据的操作,返回受到影响的数据条数
// int i = stmt.executeUpdate(sql);
//用于查询语句,返回查询到的结果集
ResultSet resultSet = stmt.executeQuery(sql);
利用List<Map>
来封装查询数据
resultSet
- 之前的查询再获取结果集的时候使用的是ResultSet
- 其内部有一个游标,默认指向数据的第一行之前
- 这种方法再获取数据的时候是获取的一行一行的数据
- 我们可以使用next()方法来移动光标指向数据行
- 通过get数据类型(列名)的方法来获取光标指向的行内的某列数据
手动的方法(便于理解)
- 此种方法由于是将列名固定的写在代码里面,可移植性不好
List<Map> list = new ArrayList<>();
while(resultSet.next()){
Map map = new HashMap();
map.put("id",resultSet.getInt("id"));
map.put("username",resultSet.getString("username"));
map.put("password",resultSet.getString("password"));
list.add(map);
}
ResultSetMetaData
- 该接口提供了一些方法来获取关于ResultSet对象中表的列名及相关信息
- 可以看作是对于ResultSet所对应表的定义信息的抽象表示
- getColumnCount(): 返回 ResultSet 中的列数。
- getColumnName(int column): 返回指定列的名称。
- getColumnType(int column): 返回指定列的 SQL 类型。
- getColumnTypeName(int column): 返回指定列的数据库特定类型名称。
- isNullable(int column): 指示指定列是否允许 NULL 值。
通过ResultSetMetaData自动遍历
//发送SQL语句并获取返回结果
ResultSet resultSet = preparedStatement.executeQuery();
//通过 ResultSetMetaData 对象获取有关查询结果的元数据信息
//表的元数据即为表的属性,包含了列名,数据类型,长度等信息
ResultSetMetaData metaData = resultSet.getMetaData();
//用于水平遍历,得到列数
int columnCount = metaData.getColumnCount();
List<Map> list = new ArrayList<>();
//垂直遍历,利用光标指向每行数据
while(resultSet.next()){
Map map = new HashMap();
//水平遍历得到光标指向行的列名和对应数据
for(int i=1;i<=columnCount;i++>){
//获取指定列下标的值
Object value = resultSet.getObject(i);
//获取指定列下角标的列的名称
String columnLabel = metaData.getColumnLabel(i);
map.put(columnLabel,value);
}
list.add(map);
}
使用总结
//1.注册驱动
//方案1:调用静态方法,但是会注册2次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//方案2:反射触发
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection(url,user,password);
//3.创建statement
//静态
Statement statement = connection.createStatement();
//预编译
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.占位符赋值
preparedStatement.setObject(?的位置 从左到右 从1开始,值);
//5.发送sql语句获取结果
int rows = executeUpdate();//执行非DQL
Resultset = executeQuery();//DQL
//6.查询结果集解析
//移动光标指向行数据 next();if(next());while(next());
//获取列的数据 get类型(int 列的下标 从1开始 | int 列的label(别名或列名))
//获取列的信息 getMetadata();ResultSetMetaData对象 包含的就是列的信息
//7.关闭资源
//从内到外关闭
close()
JDBC扩展用法
主键回显
public class Main {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydatabase";
String username = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, username, password)) {
String sql = "INSERT INTO my_table (name) VALUES (?)";
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, "John Doe");
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
if (rs.next()) {
int generatedKey = rs.getInt(1);
System.out.println("Generated Key: " + generatedKey);
}else{
pstmt.close();
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
优化批量插入
使用批处理操作
- addBatch():
- 当调用addBatch()方法时,将当前的 SQL 语句添加到批处理命令列表中。这个方法并不立即执行 SQL 语句,而是将其暂时存储在内存中,等待一次性执行。
- executeBatch():
- 当调用executeBatch()方法时,JDBC 驱动程序会将存储在批处理命令列表中的所有 SQL 语句一次性发送给数据库执行。数据库会尽可能地将这些 SQL 语句作为一个批处理进行处理,以提高执行效率。
Connection conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false); // 关闭自动提交
String sql = "INSERT INTO table_name (column1, column2) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
for (int i = 0; i < 1000; i++) {
pstmt.setString(1, "value1");
pstmt.setString(2, "value2");
pstmt.addBatch(); // 添加批处理操作
}
int[] result = pstmt.executeBatch(); // 执行批处理
conn.commit(); // 提交事务
pstmt.close();
conn.close();
实现事务
import java.sql.*;
public class TransactionExample {
public static void main(String[] args) {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database_name", "username", "password");
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
statement.executeUpdate("INSERT INTO table_name (column1, column2) VALUES ('value1', 'value2')");
statement.executeUpdate("UPDATE table_name SET column1 = 'new_value' WHERE column2 = 'value2'");
connection.commit();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
// 如果出现异常,回滚事务
try {
if (connection != null) {
connection.rollback();
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}