JDBC(Java Database Connectivity)是Java语言中用于执行SQL语句的API规范,它为Java开发人员提供了一套标准的数据库访问接口。简单来说,JDBC就是Java程序与各种关系型数据库进行通信的桥梁。
JDBC API主要由两个包组成:
在实际开发中,我们通常使用JDBC来完成以下操作:
JDBC的设计遵循了"面向接口编程"的原则,这意味着我们编写的代码只依赖于JDBC接口,而不依赖于具体的数据库实现。这种设计使得我们的应用程序可以轻松切换不同的数据库系统,只需更换对应的JDBC驱动即可。
JDBC采用分层架构设计,主要包含以下四个关键组件:

这种分层设计带来了几个显著优势:
JDBC规范定义了四种类型的驱动,每种类型有不同的实现方式和适用场景:
| 驱动类型 | 名称 | 描述 | 性能 | 适用场景 |
|---|---|---|---|---|
| Type 1 | JDBC-ODBC桥 | 通过ODBC连接数据库 | 较差 | 已淘汰 |
| Type 2 | 本地API驱动 | 调用数据库本地API | 一般 | 特定环境 |
| Type 3 | 网络协议驱动 | 通过中间件访问 | 较好 | 分布式系统 |
| Type 4 | 纯Java驱动 | 直接与DB通信 | 最佳 | 主流选择 |
目前最常用的是Type 4驱动,它完全用Java实现,不依赖任何本地代码,具有最好的跨平台性和性能。MySQL Connector/J就是典型的Type 4驱动。
在开始编码前,我们需要获取与MySQL版本匹配的JDBC驱动。版本对应关系如下:
驱动版本与数据库版本的大版本号必须一致,小版本号可以不同。例如MySQL 5.7.32可以使用5.1.49的驱动,但不能使用8.0.25的驱动。
有三种主要方式获取MySQL JDBC驱动:
官网下载:
Maven中央仓库:
xml复制<!-- MySQL 5.7 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- MySQL 8.0 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
Gradle:
gradle复制// MySQL 5.7
implementation 'mysql:mysql-connector-java:5.1.49'
// MySQL 8.0
implementation 'mysql:mysql-connector-java:8.0.25'
如果手动下载了jar包,需要将其添加到项目的classpath中:
JDBC连接MySQL的URL格式如下:
code复制jdbc:mysql://[host][:port]/[database][?参数1=值1&参数2=值2...]
常见参数说明:
示例URL:
java复制String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
JDBC提供了两种建立连接的方式:
DriverManager方式:
java复制Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, username, password);
DataSource方式(推荐):
java复制MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setURL(url);
dataSource.setUser(username);
dataSource.setPassword(password);
Connection conn = dataSource.getConnection();
两种方式的区别:
| 特性 | DriverManager | DataSource |
|---|---|---|
| 连接管理 | 每次新建物理连接 | 支持连接池 |
| 性能 | 较低 | 较高 |
| 资源消耗 | 较大 | 较小 |
| 适用场景 | 简单测试 | 生产环境 |
生产环境强烈建议使用DataSource方式,因为它支持连接池,可以显著提高性能。常见的连接池实现有HikariCP、Druid等。
JDBC提供了三种执行SQL的接口:
Statement:
PreparedStatement(推荐):
CallableStatement:
插入数据:
java复制String sql = "INSERT INTO users(id, name, age) VALUES(?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
pstmt.setString(2, "张三");
pstmt.setInt(3, 25);
int affectedRows = pstmt.executeUpdate();
查询数据:
java复制String sql = "SELECT * FROM users WHERE age > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 20);
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println(id + ", " + name + ", " + age);
}
更新数据:
java复制String sql = "UPDATE users SET age = ? WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 26);
pstmt.setInt(2, 1);
int affectedRows = pstmt.executeUpdate();
删除数据:
java复制String sql = "DELETE FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
int affectedRows = pstmt.executeUpdate();
当需要执行大量SQL时,使用批处理可以显著提高性能:
java复制String sql = "INSERT INTO users(name, age) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
for(int i=0; i<1000; i++) {
pstmt.setString(1, "user" + i);
pstmt.setInt(2, i % 50 + 18);
pstmt.addBatch(); // 添加到批处理
if(i % 100 == 0) { // 每100条执行一次
pstmt.executeBatch();
}
}
pstmt.executeBatch(); // 执行剩余的
conn.commit(); // 提交事务
JDBC默认是自动提交模式,每条SQL作为一个独立事务执行。要手动控制事务:
java复制try {
conn.setAutoCommit(false); // 关闭自动提交
// 执行多个SQL操作
updateAccount(conn, "A", -100);
updateAccount(conn, "B", 100);
conn.commit(); // 提交事务
} catch(SQLException e) {
conn.rollback(); // 回滚事务
e.printStackTrace();
} finally {
conn.setAutoCommit(true); // 恢复自动提交
}
事务隔离级别:
java复制// 设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// 获取当前隔离级别
int level = conn.getTransactionIsolation();
生产环境建议使用连接池,以下是HikariCP的配置示例:
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("password");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);
关键配置参数:
正确的资源管理方式:
java复制// Java 7+ try-with-resources写法
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
while(rs.next()) {
// 处理结果
}
} catch(SQLException e) {
// 处理异常
}
常见SQL异常处理:
使用PreparedStatement:
合理设置FetchSize:
java复制stmt.setFetchSize(100); // 每次从数据库获取100条记录
使用连接池:
关闭自动提交:
合理使用批处理:
问题1:连接超时
&connectTimeout=5000问题2:认证失败
&allowPublicKeyRetrieval=true问题3:时区错误
&serverTimezone=Asia/Shanghai问题1:SQL语法错误
问题2:参数类型不匹配
问题3:主键冲突
问题1:查询慢
问题2:连接泄漏
问题3:内存溢出
java复制public class JdbcCrudExample {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) {
// 初始化数据源
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setURL(URL);
dataSource.setUser(USER);
dataSource.setPassword(PASSWORD);
// 测试CRUD操作
try (Connection conn = dataSource.getConnection()) {
// 创建表
createTable(conn);
// 插入数据
insertUser(conn, 1, "张三", 25);
insertUser(conn, 2, "李四", 30);
// 查询数据
List<User> users = findAllUsers(conn);
users.forEach(System.out::println);
// 更新数据
updateUserAge(conn, 1, 26);
// 删除数据
deleteUser(conn, 2);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void createTable(Connection conn) throws SQLException {
String sql = "CREATE TABLE IF NOT EXISTS users (" +
"id INT PRIMARY KEY, " +
"name VARCHAR(50) NOT NULL, " +
"age INT)";
try (Statement stmt = conn.createStatement()) {
stmt.execute(sql);
}
}
private static void insertUser(Connection conn, int id, String name, int age) throws SQLException {
String sql = "INSERT INTO users(id, name, age) VALUES(?, ?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
pstmt.setString(2, name);
pstmt.setInt(3, age);
pstmt.executeUpdate();
}
}
private static List<User> findAllUsers(Connection conn) throws SQLException {
List<User> users = new ArrayList<>();
String sql = "SELECT * FROM users";
try (PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
users.add(new User(
rs.getInt("id"),
rs.getString("name"),
rs.getInt("age")
));
}
}
return users;
}
private static void updateUserAge(Connection conn, int id, int newAge) throws SQLException {
String sql = "UPDATE users SET age = ? WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, newAge);
pstmt.setInt(2, id);
pstmt.executeUpdate();
}
}
private static void deleteUser(Connection conn, int id) throws SQLException {
String sql = "DELETE FROM users WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
pstmt.executeUpdate();
}
}
}
class User {
private int id;
private String name;
private int age;
// 构造方法、getter、setter、toString等
}
java复制public class JdbcTransactionExample {
public static void transferMoney(DataSource dataSource, String from, String to, int amount) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 开始事务
// 扣款
withdraw(conn, from, amount);
// 存款
deposit(conn, to, amount);
conn.commit(); // 提交事务
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true); // 恢复自动提交
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
private static void withdraw(Connection conn, String account, int amount) throws SQLException {
String sql = "UPDATE accounts SET balance = balance - ? WHERE account_no = ? AND balance >= ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, amount);
pstmt.setString(2, account);
pstmt.setInt(3, amount);
int rows = pstmt.executeUpdate();
if (rows == 0) {
throw new SQLException("扣款失败,余额不足或账户不存在");
}
}
}
private static void deposit(Connection conn, String account, int amount) throws SQLException {
String sql = "UPDATE accounts SET balance = balance + ? WHERE account_no = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, amount);
pstmt.setString(2, account);
pstmt.executeUpdate();
}
}
}
java复制public class JdbcConnectionPoolExample {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
}
public static void main(String[] args) {
// 使用连接池执行查询
String sql = "SELECT * FROM users WHERE age > ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 20);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("id") + ", " +
rs.getString("name") + ", " +
rs.getInt("age"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在实际项目中,我们通常会封装一个JDBC工具类来简化操作:
java复制public class JdbcUtils {
private static DataSource dataSource;
static {
// 初始化数据源
MysqlDataSource ds = new MysqlDataSource();
ds.setURL("jdbc:mysql://localhost:3306/test");
ds.setUser("root");
ds.setPassword("123456");
dataSource = ds;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static int executeUpdate(String sql, Object... params) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
return pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
close(conn, pstmt, null);
}
}
public static <T> List<T> executeQuery(String sql, RowMapper<T> rowMapper, Object... params) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
rs = pstmt.executeQuery();
while (rs.next()) {
list.add(rowMapper.mapRow(rs));
}
return list;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
close(conn, pstmt, rs);
}
}
public interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
}
使用示例:
java复制// 插入数据
JdbcUtils.executeUpdate("INSERT INTO users(name, age) VALUES(?, ?)", "王五", 28);
// 查询数据
List<User> users = JdbcUtils.executeQuery(
"SELECT * FROM users WHERE age > ?",
rs -> new User(rs.getInt("id"), rs.getString("name"), rs.getInt("age")),
20
);
当处理大量数据时,需要注意内存使用:
流式查询:
java复制String sql = "SELECT * FROM large_table";
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY)) {
stmt.setFetchSize(Integer.MIN_VALUE); // MySQL流式读取
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
// 处理每一行数据
}
}
}
分批处理:
java复制int batchSize = 1000;
int offset = 0;
List<User> batch;
do {
batch = findUsersByBatch(offset, batchSize);
processBatch(batch);
offset += batchSize;
} while (!batch.isEmpty());
private List<User> findUsersByBatch(int offset, int limit) {
String sql = "SELECT * FROM users ORDER BY id LIMIT ? OFFSET ?";
return JdbcUtils.executeQuery(sql,
rs -> new User(rs.getInt("id"), rs.getString("name"), rs.getInt("age")),
limit, offset);
}
监控连接池:
java复制HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();
System.out.println("活跃连接: " + poolMXBean.getActiveConnections());
System.out.println("空闲连接: " + poolMXBean.getIdleConnections());
System.out.println("等待线程: " + poolMXBean.getThreadsAwaitingConnection());
SQL性能分析:
java复制// 开启MySQL慢查询日志
String sql = "SET GLOBAL slow_query_log = 'ON'";
String sql2 = "SET GLOBAL long_query_time = 1"; // 超过1秒的查询
// 使用EXPLAIN分析SQL
String explainSql = "EXPLAIN SELECT * FROM users WHERE age > 20";
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(explainSql)) {
ResultSetMetaData metaData = rs.getMetaData();
while (rs.next()) {
for (int i = 1; i <= metaData.getColumnCount(); i++) {
System.out.println(metaData.getColumnName(i) + ": " + rs.getString(i));
}
}
}
虽然JDBC是Java操作数据库的基础,但在实际企业开发中,我们通常会使用ORM框架来简化开发:
| 框架 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| MyBatis | 灵活、SQL可控 | 需要手写SQL | 复杂SQL、需要精细控制 |
| Hibernate | 全自动、开发快 | 学习曲线陡峭 | 快速开发、简单CRUD |
| JPA | 标准规范、可移植 | 功能有限 | 需要切换不同实现 |
| Spring JDBC | 轻量、简单 | 功能较少 | 小型项目、简单需求 |
MyBatis配置示例:
xml复制<!-- mybatis-config.xml -->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
Mapper接口:
java复制public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(int id);
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(User user);
@Update("UPDATE users SET name=#{name}, age=#{age} WHERE id=#{id}")
void update(User user);
@Delete("DELETE FROM users WHERE id=#{id}")
void delete(int id);
}
尽管ORM框架很方便,但在以下场景中直接使用JDBC可能更合适:
掌握JDBC的核心原理,才能更好地使用各种ORM框架,并在需要时灵活切换方案。