Java数据库连接(JDBC)
JDBC(Java Database Connectivity)是Java语言访问数据库的标准API(Application Programming Interface)。
它提供了一组用于执行SQL语句、访问和处理数据库的类和接口。通过JDBC,Java应用程序可以连接到各种数据库管理系统(如MySQL、Oracle、SQL Server等),并执行诸如查询、插入、更新和删除等操作。
# JDBC基础
# 1 JDBC架构
JDBC架构由两个重要的组件构成:JDBC驱动程序和JDBC API。JDBC驱动程序负责与特定的数据库管理系统进行通信,而JDBC API则提供了一组类和接口,供Java应用程序与JDBC驱动程序进行交互。
+-----------------+
| JDBC API |
+-----------------+
|
|
V
+-----------------+
| JDBC Driver |
+-----------------+
|
|
V
+---------------------------+
| Database System |
+---------------------------+
# 2 JDBC组件介绍
以下的示例均使用MySQL数据,要了解相关知识,请参考:理解MySQL。
- DriverManager:
DriverManager
类是JDBC的基础类之一,它负责管理JDBC驱动程序。通过DriverManager
可以获取数据库连接对象。
// 示例代码
String url = "jdbc:mysql://localhost:3306/mydatabase";
String user = "username";
String password = "password";
Connection connection = DriverManager.getConnection(url, user, password);
- Driver:
Driver
接口是JDBC驱动程序的核心接口,每个数据库驱动程序都必须实现该接口。通过Driver接口的实现类,可以注册驱动程序到DriverManager
中。
// 示例代码
String driverClassName = "com.mysql.jdbc.Driver";
Class.forName(driverClassName); // 注册驱动程序
- Connection:
Connection
接口表示与数据库的连接。通过Connection
对象,可以创建Statement
对象、提交事务、关闭连接等操作。
// 示例代码
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
- Statement:
Statement
接口用于执行SQL语句并返回结果。可以通过Statement
对象执行查询语句(executeQuery)或更新语句(executeUpdate)。
// 示例代码
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM employees");
- ResultSet:
ResultSet
接口用于表示SQL查询的结果集。通过ResultSet对象可以读取和处理查询结果。
// 示例代码
while (resultSet.next()) {
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
// 处理结果
}
# 建立连接与增删改查
# 1 安装JDBC驱动
安装JDBC驱动程序是使用JDBC的前提条件。
以MySQL为例,在Maven项目中,请将以下内容添加到您的项目的pom.xml
文件中的<dependencies>
标签内:
<dependencies>
<!-- 其他依赖项 -->
<!-- MySQL 8 驱动程序 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- 其他依赖项 -->
</dependencies>
如果是Gradle项目,请将以下内容添加到您的项目的build.gradle
文件中的dependencies
部分:
dependencies {
// 其他依赖项
// MySQL 8 驱动程序
implementation 'mysql:mysql-connector-java:8.0.27'
// 其他依赖项
}
# 2 查询
以下是一个使用Statement
进行查询的完整示例,包括建立连接、关闭连接和异常处理等:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class StatementExample {
public static void main(String[] args) {
// JDBC连接信息
String url = "jdbc:mysql://localhost:3306/mydatabase";
String username = "your_username";
String password = "your_password";
// 建立数据库连接
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 建立连接
connection = DriverManager.getConnection(url, username, password);
// 创建Statement对象
statement = connection.createStatement();
// 执行查询
String sql = "SELECT * FROM employees";
resultSet = statement.executeQuery(sql);
// 处理查询结果
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
// 其他列的处理...
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
请注意替换示例中的url
、username
和password
为您的实际数据库连接信息。
在代码中,我们使用了Class.forName
来注册MySQL驱动程序,然后使用DriverManager.getConnection
建立与数据库的连接。接下来,创建Statement
对象并执行查询语句。最后,在finally
块中关闭连接、关闭Statement
和关闭ResultSet
以释放资源。
这里简单介绍一下ResultSet
,它使用next()
方法将游标移动到结果集的下一行,并使用getXXX()
方法获取当前行的数据。
比如getInt()
、getString()
等方法,这些方法的参数可以是列名,也可以是列索引,比如getInt(1)
。需要注意,列索引是从1开始计数的。
# 3 PreparedStatements和防止SQL注入
看一下PreparedStatements
和Statement
的区别:
- 预编译性能:
PreparedStatement
在执行之前进行预编译,将SQL语句和参数分离,并且数据库可以缓存预编译的语句,提高了执行相同语句的性能。而Statement
在执行时直接将SQL语句发送给数据库执行,没有预编译的过程,因此每次执行都需要解析和编译SQL语句,性能较低。 - 参数设置:
PreparedStatement
允许通过占位符(?)的方式动态设置参数值,参数值可以通过方法设置,并且支持各种数据类型的自动转换。而Statement
没有直接支持参数设置的功能,参数值需要通过字符串拼接的方式嵌入SQL语句中,不够灵活和安全。 - 防止SQL注入攻击:由于
PreparedStatement
的参数设置是通过占位符的方式进行的,参数值会被正确地进行转义和处理,从而防止了SQL注入攻击。而Statement
中的参数值嵌入在SQL语句中,容易受到恶意输入的影响,存在SQL注入的风险。 - 可读性和维护性:
PreparedStatement
在代码中使用了参数占位符,使得SQL语句与参数值的逻辑分离,代码更加清晰和易读。而Statement
的参数值直接嵌入在SQL语句中,使得SQL语句变得冗长,可读性和维护性较差。
综合来看,推荐在开发中尽可能使用PreparedStatement
来执行SQL操作。
以下是一个PreparedStatement
查询的示例:
...
// 建立连接
connection = DriverManager.getConnection(url, username, password);
// 使用PreparedStatement进行查询
String query = "SELECT * FROM employees WHERE age > ? AND name LIKE ? AND salary BETWEEN ? AND ? AND department IS NULL";
PreparedStatement preparedStatement = preparedStatement = connection.prepareStatement(query);
preparedStatement.setInt(1, 30); // 设置年龄大于30
preparedStatement.setString(2, "%John%"); // 设置名字中包含"John"
preparedStatement.setDouble(3, 2000.0); // 设置工资范围下限
preparedStatement.setDouble(4, 5000.0); // 设置工资范围上限
resultSet = preparedStatement.executeQuery();
// 处理查询结果
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
double salary = resultSet.getDouble("salary");
String department = resultSet.getString("department");
// 其他列的处理...
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age + ", Salary: " + salary + ", Department: " + department);
}
...
# 3 增删改
以下示例展示了如何使用PreparedStatement
进行增删改操作:
...
// 建立连接
connection = DriverManager.getConnection(url, username, password);
// 插入数据
String insertQuery = "INSERT INTO employees (name, age, salary, department) VALUES (?, ?, ?, ?)";
PreparedStatement preparedStatement = preparedStatement = connection.prepareStatement(insertQuery);
preparedStatement.setString(1, "John Doe");
preparedStatement.setInt(2, 30);
preparedStatement.setDouble(3, 5000.0);
preparedStatement.setString(4, "IT");
int rowsInserted = preparedStatement.executeUpdate();
System.out.println(rowsInserted + " row(s) inserted.");
// 更新数据
String updateQuery = "UPDATE employees SET salary = ? WHERE department = ?";
preparedStatement = connection.prepareStatement(updateQuery);
preparedStatement.setDouble(1, 6000.0);
preparedStatement.setString(2, "IT");
int rowsUpdated = preparedStatement.executeUpdate();
System.out.println(rowsUpdated + " row(s) updated.");
// 删除数据
String deleteQuery = "DELETE FROM employees WHERE age > ?";
preparedStatement = connection.prepareStatement(deleteQuery);
preparedStatement.setInt(1, 40);
int rowsDeleted = preparedStatement.executeUpdate();
System.out.println(rowsDeleted + " row(s) deleted.");
...
插入后获得其自增ID:
...
// 插入数据
String insertQuery = "INSERT INTO employees (name, age, salary, department) VALUES (?, ?, ?, ?)";
preparedStatement = connection.prepareStatement(insertQuery, PreparedStatement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, "John Doe");
preparedStatement.setInt(2, 30);
preparedStatement.setDouble(3, 5000.0);
preparedStatement.setString(4, "IT");
int rowsInserted = preparedStatement.executeUpdate();
System.out.println(rowsInserted + " row(s) inserted.");
// 获取自增ID
generatedKeys = preparedStatement.getGeneratedKeys();
if (generatedKeys.next()) {
int generatedId = generatedKeys.getInt(1);
System.out.println("Generated ID: " + generatedId);
}
...
# 事务管理
# 1 什么是事务?
事务是一组对数据库的操作,这些操作要么全部成功执行,要么全部失败回滚。事务具有四个特性,即ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
# 2 JDBC中的事务控制(commit和rollback)
JDBC提供了commit
和rollback
方法来控制事务的提交和回滚。在执行事务操作之前,需要将Connection
对象的自动提交模式关闭,以启用事务支持。
// 示例代码
connection.setAutoCommit(false); // 关闭自动提交
// 执行事务操作
connection.commit(); // 提交事务
connection.rollback(); // 回滚事务
示例:
...
// 建立连接
connection = DriverManager.getConnection(url, username, password);
// 关闭自动提交
connection.setAutoCommit(false);
// 创建Statement对象
Statement statement = connection.createStatement();
// 执行事务操作
try {
// 第一条SQL语句
String sql1 = "UPDATE employees SET salary = salary + 1000 WHERE department = 'IT'";
statement.executeUpdate(sql1);
// 第二条SQL语句
String sql2 = "UPDATE employees SET salary = salary - 500 WHERE department = 'HR'";
statement.executeUpdate(sql2);
// 提交事务
connection.commit();
System.out.println("事务已提交");
} catch (SQLException e) {
// 回滚事务
connection.rollback();
System.out.println("事务已回滚");
}
// 恢复自动提交
connection.setAutoCommit(true);
...
这里解释一下自动提交。
在JDBC中,自动提交(auto-commit)是指每个SQL语句在执行完毕后是否立即提交到数据库。默认情况下,连接对象的自动提交模式是打开的(即connection.setAutoCommit(true)
)。
当自动提交模式为打开时,每个执行的SQL语句都会立即提交到数据库,这意味着每个SQL语句都被视为一个单独的事务。如果SQL语句执行成功,它们的结果将永久保存到数据库中。如果SQL语句执行失败,它们的结果将不会被保存,数据库不会回滚已执行的操作。
如果你想要将多个SQL语句作为一个逻辑单元进行提交或回滚,就需要关闭自动提交模式(即connection.setAutoCommit(false)
)。在关闭自动提交模式后,多个SQL语句将被组合成一个事务。只有在显式调用commit()
提交事务时,事务中的所有SQL语句才会一起提交到数据库。如果在事务中的任何一个SQL语句执行失败,你可以通过调用rollback()
回滚事务,将所有已执行的操作撤消。
# 3 设置事务隔离级别
事务隔离级别定义了多个事务之间的可见性和并发性。JDBC提供了设置事务隔离级别的方法,可以通过Connection
对象的setTransactionIsolation
方法来设置事务隔离级别。
// 示例代码
int isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
connection.setTransactionIsolation(isolationLevel);
# 存储过程和函数
# 1 使用JDBC调用存储过程
存储过程是预先编译的一组SQL语句,可以作为一个单元一起执行。通过JDBC可以调用存储过程并获取其执行结果。
// 示例代码
String sql = "{CALL my_procedure(?, ?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.setString(1, "John Doe");
callableStatement.setInt(2, 30);
callableStatement.execute();
# 2 使用JDBC调用数据库函数
数据库函数是在数据库中定义的可重用的SQL代码片段,可以通过JDBC调用数据库函数并获取返回值。
// 示例代码
String sql = "{? = CALL my_function(?, ?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.registerOutParameter(1, Types.INTEGER);
callableStatement.setString(2, "John Doe");
callableStatement.setInt(3, 30);
callableStatement.execute();
int result = callableStatement.getInt(1); // 获取返回值
# JDBC高级特性
# 1 批处理
批处理允许将一组SQL语句作为批量操作一起执行,可以提高执行效率。JDBC提供了批处理的支持,可以通过Statement
对象或PreparedStatement
对象来执行批处理操作。
// 示例代码
Statement statement = connection.createStatement();
statement.addBatch("INSERT INTO employees (name, age) VALUES ('John Doe', 30)");
statement.addBatch("UPDATE employees SET age = 35 WHERE name = 'Jane Smith'");
statement.executeBatch();
# 2 连接池
连接池是一组预先创建的数据库连接,可以重复使用,从而减少了连接和断开连接的开销。JDBC连接池可以通过使用第三方连接池库(如Apache Commons DBCP、C3P0)来实现。
// 示例代码(使用Apache Commons DBCP连接池)
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydatabase");
dataSource.setUsername("username");
dataSource.setPassword("password");
Connection connection = dataSource.getConnection();
连接池技术一般实现了DataSource
接口,它是一个JDBC资源的工厂,可以通过它获取数据库连接。
DataSource
提供了一种更灵活、可配置的方式来管理和获取连接。相对于直接使用DriverManager
来说,DataSource
更常用于企业级应用程序中。
# 3 元数据
元数据是关于数据库和其对象(表、列、索引等)的数据,可以通过JDBC的元数据API来访问。JDBC的元数据API提供了一组方法,用于获取和操作数据库的元数据信息。这些方法通过DatabaseMetaData
接口和ResultSetMetaData
接口来实现。
DatabaseMetaData
接口:DatabaseMetaData
接口提供了与数据库本身相关的元数据信息。通过Connection
对象的getMetaData()
方法获取DatabaseMetaData
实例。一些常见的DatabaseMetaData
方法包括:getDatabaseProductName()
:获取数据库产品名称。getDatabaseProductVersion()
:获取数据库产品版本。getTables()
:获取数据库中的表信息。getColumns()
:获取指定表的列信息。getIndexInfo()
:获取指定表的索引信息。getProcedures()
:获取数据库中的存储过程信息。
ResultSetMetaData
接口:ResultSetMetaData
接口提供了有关ResultSet
对象中列的元数据信息。通过ResultSet
对象的getMetaData()
方法获取ResultSetMetaData
实例。一些常见的ResultSetMetaData
方法包括:- getColumnCount(): 获取结果集中的列数。
- getColumnName(int column): 获取指定列索引的列名。
- getColumnLabel(int column): 获取指定列索引的列标签。
- getColumnType(int column): 获取指定列索引的列数据类型。
- getColumnTypeName(int column): 获取指定列索引的列数据类型名称。
- getColumnDisplaySize(int column): 获取指定列索引的列显示大小。
- isNullable(int column): 判断指定列索引的列是否可为空。
- isReadOnly(int column): 判断指定列索引的列是否为只读列。
- isAutoIncrement(int column): 判断指定列索引的列是否为自动增长列。
- isCaseSensitive(int column): 判断指定列索引的列是否区分大小写。
ParameterMetaData
接口:ParameterMetaData
接口提供了有关SQL语句参数的元数据信息,包括参数个数、参数类型、参数模式等。这对于动态生成SQL语句、处理可变参数的情况非常有用。一些常用的ParameterMetaData
方法包括:getParameterCount()
:获取SQL语句中的参数个数。getParameterType(int param)
:获取指定参数的SQL类型。getParameterTypeName(int param)
:获取指定参数的SQL类型名称。isNullable(int param)
:判断指定参数是否可为空。
通过使用
ParameterMetaData
,您可以获取关于SQL语句参数的信息,从而更好地构建和处理动态SQL语句。
通过使用元数据API,您可以获取有关数据库和查询结果的详细信息。这些信息对于动态生成SQL语句、进行结果集的处理和分析非常有用。您可以使用这些元数据信息来构建更灵活和可扩展的数据库操作逻辑。
ParameterMetaData
示例
...
// 创建PreparedStatement对象
String sql = "INSERT INTO employees (name, age, salary) VALUES (?, ?, ?)";
preparedStatement = connection.prepareStatement(sql);
// 获取ParameterMetaData
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
// 获取参数个数
int parameterCount = parameterMetaData.getParameterCount();
System.out.println("参数个数:" + parameterCount);
// 获取参数类型
for (int i = 1; i <= parameterCount; i++) {
String parameterType = parameterMetaData.getParameterTypeName(i);
System.out.println("参数 " + i + " 的类型:" + parameterType);
}
...
DatabaseMetaData
和ResultSetMetaData
示例
演示了如何获取employee表的信息(表名、注释)和字段信息(名称、类型、注释、是否注解、是否空、默认值):
...
// 获取DatabaseMetaData
DatabaseMetaData databaseMetaData = connection.getMetaData();
// 获取表信息
ResultSet tablesResultSet = databaseMetaData.getTables(null, null, "employee", new String[]{"TABLE"});
if (tablesResultSet.next()) {
String tableName = tablesResultSet.getString("TABLE_NAME");
String tableComment = tablesResultSet.getString("REMARKS");
System.out.println("表名:" + tableName);
System.out.println("注释:" + tableComment);
// 获取字段信息
ResultSet columnsResultSet = databaseMetaData.getColumns(null, null, tableName, null);
while (columnsResultSet.next()) {
String columnName = columnsResultSet.getString("COLUMN_NAME");
String columnType = columnsResultSet.getString("TYPE_NAME");
String columnComment = columnsResultSet.getString("REMARKS");
boolean isNullable = (columnsResultSet.getInt("NULLABLE") == 1);
String columnDefault = columnsResultSet.getString("COLUMN_DEF");
System.out.println("字段名:" + columnName);
System.out.println("类型:" + columnType);
System.out.println("注释:" + columnComment);
System.out.println("是否允许为空:" + isNullable);
System.out.println("默认值:" + columnDefault);
System.out.println("------------------------");
}
columnsResultSet.close();
}
tablesResultSet.close();
...
# ORM框架
当涉及到与数据库交互的开发任务时,ORM(对象关系映射)框架是非常有用的工具。
ORM框架提供了一种将对象模型与关系型数据库之间进行映射的机制,使开发人员可以使用面向对象的方式进行数据库操作,而不需要直接处理SQL语句和数据库连接。
两个常见的ORM框架是MyBatis和Spring Data JPA。
# 1. MyBatis
MyBatis是一个开源的Java持久层框架,它允许开发人员通过XML配置或注解来定义SQL语句和数据库映射关系。以下是MyBatis的一些关键特点和优势:
- 灵活性:MyBatis提供了完全的SQL控制权,允许开发人员编写原生SQL语句,并提供了丰富的映射机制,支持将查询结果映射到Java对象。
- 易于集成:MyBatis可以轻松集成到现有的Java应用程序中,不需要依赖复杂的容器或框架。
- 缓存支持:MyBatis提供了缓存机制,可以提高查询性能,减少对数据库的访问。
- 动态SQL:MyBatis支持动态SQL语句的构建,可以根据条件生成不同的SQL语句。
# 2. Spring Data JPA
Spring Data JPA是Spring框架提供的一个子项目,它简化了使用JPA(Java持久化API)进行数据访问的开发任务。以下是Spring Data JPA的一些关键特点和优势:
- 简化数据访问:Spring Data JPA通过提供通用的CRUD(创建、读取、更新、删除)操作方法,大大简化了数据访问层的开发。
- 自动化查询生成:Spring Data JPA根据方法命名约定和查询注解自动生成查询语句,无需手动编写SQL语句。
- 强大的查询功能:Spring Data JPA提供了丰富的查询功能,包括动态查询、分页和排序等。
- 集成Spring生态系统:Spring Data JPA与Spring框架及其他Spring项目(如Spring Boot)紧密集成,可以方便地使用Spring的特性和功能。
如果倾向于对SQL语句和映射具有更高的控制权,或者需要执行复杂的SQL查询和定制化的数据库操作,那么MyBatis可能是一个更适合的选择。
MyBatis允许您直接编写SQL语句,并提供了灵活的映射机制,可以将查询结果映射到自定义的Java对象上。您可以通过XML配置或注解定义SQL语句和映射关系,同时还可以使用动态SQL构建器来处理复杂的查询逻辑。MyBatis还提供了缓存机制,可以提高查询性能,减少对数据库的访问。
另一方面,如果希望简化数据访问层的开发,使用更高级的抽象和自动化的功能来处理常见的数据库操作,那么Spring Data JPA可能更适合。
Spring Data JPA提供了一组通用的CRUD方法,通过方法命名约定和查询注解,可以自动生成SQL查询语句,无需手动编写大量的SQL语句。它还提供了丰富的查询功能,包括动态查询、分页和排序等。
# 总结
本文中,我们深入学习了Java数据库连接(JDBC)技术。
JDBC是Java与关系型数据库之间进行通信和交互的标准API。通过学习和掌握JDBC,我们可以实现Java应用程序与各种关系型数据库的无缝集成。
同时,ORM框架作为JDBC的上层框架,提供了更丰富的功能,更便捷的开发体验,可以根据具体需求、团队技能和项目背景来进行合适的选择。
总之,作为Java语言访问数据库的标准API为开发人员提供了与关系型数据库进行交互的一致性和可靠性。通过学习和掌握JDBC,开发人员可以构建可靠、高效的数据库应用程序,并在不同的数据库环境中实现数据持久化和管理。
祝你变得更强!