JDBC(Java Database Connectivity)是Java语言中用来规范客户端程序如何访问数据库的标准API。作为Java开发者与数据库交互的桥梁,JDBC提供了一套统一的操作接口,使得我们可以用相同的方式操作不同的关系型数据库。
JDBC采用典型的接口-实现分离设计,主要包含四个核心层次:
这种分层设计的优势在于:
根据实现方式的不同,JDBC驱动主要分为四种类型:
| 驱动类型 | 名称 | 特点 | 适用场景 |
|---|---|---|---|
| Type 1 | JDBC-ODBC桥 | 通过ODBC连接数据库 | 已淘汰,不推荐使用 |
| Type 2 | 本地API驱动 | 部分Java实现,依赖本地库 | 性能较好但跨平台性差 |
| Type 3 | 网络协议驱动 | 纯Java实现,通过中间件访问 | 适用于分布式环境 |
| Type 4 | 纯Java驱动 | 直接与数据库通信 | 主流选择,性能最佳 |
目前MySQL Connector/J、Oracle JDBC驱动等都属于Type 4驱动,这也是我们日常开发中最常用的类型。
在开始JDBC编程前,我们需要完成以下准备工作:
sql复制CREATE DATABASE jdbc_demo;
USE jdbc_demo;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
email VARCHAR(100)
);
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
java复制public class JdbcInsertExample {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1. 注册驱动(可省略,自动加载)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取连接
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/jdbc_demo",
"root",
"password");
// 3. 创建Statement对象
stmt = conn.createStatement();
// 4. 执行SQL
String sql = "INSERT INTO users(username, password, email) " +
"VALUES('testuser', '123456', 'test@example.com')";
int affectedRows = stmt.executeUpdate(sql);
System.out.println("插入成功,影响行数: " + affectedRows);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5. 释放资源
try {
if(stmt != null) stmt.close();
if(conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
java复制public class JdbcQueryExample {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/jdbc_demo",
"root",
"password");
stmt = conn.createStatement();
String sql = "SELECT id, username, email FROM users";
rs = stmt.executeQuery(sql);
// 处理结果集
while(rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
System.out.printf("ID: %d, Username: %s, Email: %s%n",
id, username, email);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Connection是JDBC中最重要的对象之一,代表与数据库的物理连接。它的主要功能包括:
创建Statement对象:
createStatement():创建基本StatementprepareStatement(String sql):创建预编译StatementprepareCall(String sql):创建存储过程调用Statement事务管理:
java复制try {
conn.setAutoCommit(false); // 开启事务
// 执行多个SQL操作
stmt1.executeUpdate(sql1);
stmt2.executeUpdate(sql2);
conn.commit(); // 提交事务
} catch (SQLException e) {
try {
conn.rollback(); // 回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
连接池相关:
setNetworkTimeout():设置网络超时isValid():检查连接是否有效close():释放连接(在连接池中实际是归还连接)PreparedStatement是Statement的子接口,提供了更安全、高效的SQL执行方式。其核心优势包括:
防止SQL注入:
java复制String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
批量操作提升性能:
java复制String sql = "INSERT INTO users(username, password) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
for(int i = 0; i < 1000; i++) {
pstmt.setString(1, "user" + i);
pstmt.setString(2, "pass" + i);
pstmt.addBatch(); // 添加到批处理
if(i % 100 == 0) {
pstmt.executeBatch(); // 每100条执行一次
}
}
pstmt.executeBatch(); // 执行剩余的
元数据获取:
java复制PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSetMetaData metaData = pstmt.getMetaData();
int columnCount = metaData.getColumnCount();
for(int i = 1; i <= columnCount; i++) {
System.out.println("Column " + i + ": " + metaData.getColumnName(i));
}
JDBC支持标准的事务隔离级别,可以通过Connection对象设置:
java复制conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
各隔离级别对比:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 | 最高 |
| READ_COMMITTED | 不可能 | 可能 | 可能 | 高 |
| REPEATABLE_READ | 不可能 | 不可能 | 可能 | 中 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 低 |
实际开发中应根据业务需求选择合适的隔离级别,通常READ_COMMITTED是较好的平衡点。
直接使用DriverManager获取连接在实际项目中效率低下,推荐使用连接池。以下是手工实现简单连接池的示例:
java复制public class SimpleConnectionPool {
private static final int INITIAL_POOL_SIZE = 10;
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_demo";
private static final String USER = "root";
private static final String PASSWORD = "password";
private static BlockingQueue<Connection> pool = new LinkedBlockingQueue<>(INITIAL_POOL_SIZE);
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
for(int i = 0; i < INITIAL_POOL_SIZE; i++) {
pool.add(DriverManager.getConnection(URL, USER, PASSWORD));
}
} catch (Exception e) {
throw new RuntimeException("初始化连接池失败", e);
}
}
public static Connection getConnection() throws InterruptedException {
return pool.take();
}
public static void releaseConnection(Connection conn) {
if(conn != null) {
pool.offer(conn);
}
}
}
实际项目中推荐使用成熟的开源连接池,如HikariCP、Druid等。
Java 7引入的Try-With-Resources语法可以自动关闭资源,使JDBC代码更简洁:
java复制public void queryWithTryWithResources() {
String sql = "SELECT * FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 1);
try (ResultSet rs = pstmt.executeQuery()) {
while(rs.next()) {
System.out.println(rs.getString("username"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
驱动类找不到:
连接超时:
java复制String url = "jdbc:mysql://localhost:3306/db?connectTimeout=5000";
时区问题:
java复制jdbc:mysql://localhost:3306/db?serverTimezone=Asia/Shanghai
使用PreparedStatement代替Statement:
合理设置Fetch Size:
java复制Statement stmt = conn.createStatement();
stmt.setFetchSize(100); // 每次从数据库获取100条记录
批量操作减少网络往返:
java复制conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.addBatch("INSERT INTO users VALUES(...)");
stmt.addBatch("UPDATE products SET ...");
int[] counts = stmt.executeBatch();
conn.commit();
正确关闭资源:
基于原始代码的改进版本:
java复制public class JdbcUtils {
private static DataSource dataSource;
static {
try {
Properties props = new Properties();
InputStream is = JdbcUtils.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
props.load(is);
// 使用HikariCP连接池
HikariConfig config = new HikariConfig();
config.setJdbcUrl(props.getProperty("url"));
config.setUsername(props.getProperty("user"));
config.setPassword(props.getProperty("password"));
config.setMaximumPoolSize(20);
dataSource = new HikariDataSource(config);
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化连接池失败");
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection conn, Statement stmt, ResultSet rs) {
closeQuietly(rs);
closeQuietly(stmt);
closeQuietly(conn);
}
private static void closeQuietly(AutoCloseable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
// 记录日志但不要抛出异常
Logger.getLogger(JdbcUtils.class.getName())
.log(Level.WARNING, "关闭资源时出错", e);
}
}
}
// 查询模板方法
public static <T> List<T> query(String sql, ResultSetHandler<T> handler, Object... params) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
rs = pstmt.executeQuery();
return handler.handle(rs);
} catch (SQLException e) {
throw new RuntimeException("数据库查询错误", e);
} finally {
close(conn, pstmt, rs);
}
}
public interface ResultSetHandler<T> {
List<T> handle(ResultSet rs) throws SQLException;
}
}
定义通用DAO接口:
java复制public interface BaseDao<T> {
int save(String sql, Object... params);
int update(String sql, Object... params);
T findById(String sql, RowMapper<T> rowMapper, Object... params);
List<T> findAll(String sql, RowMapper<T> rowMapper, Object... params);
}
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
实现通用DAO:
java复制public class JdbcTemplateDao<T> implements BaseDao<T> {
@Override
public int save(String sql, Object... params) {
return JdbcUtils.executeUpdate(sql, params);
}
@Override
public T findById(String sql, RowMapper<T> rowMapper, Object... params) {
List<T> results = JdbcUtils.query(sql, rs -> {
List<T> list = new ArrayList<>();
while(rs.next()) {
list.add(rowMapper.mapRow(rs, rs.getRow()));
}
return list;
}, params);
return results.isEmpty() ? null : results.get(0);
}
}
虽然JDBC提供了底层的数据库访问能力,但在企业级应用中,我们通常会使用更高级的ORM框架:
MyBatis是半自动化的ORM框架,相比JPA提供了更多SQL控制权:
Spring框架提供的JDBC Template在原生JDBC上进行了良好封装:
java复制public class UserDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public User getUser(int id) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
return user;
}, id);
}
}
我们将实现一个简化版的ORM框架,具备以下功能:
java复制public class MiniORM<T> {
private final Class<T> entityClass;
private final String tableName;
private final Map<String, Field> fieldMap = new HashMap<>();
public MiniORM(Class<T> entityClass) {
this.entityClass = entityClass;
this.tableName = resolveTableName(entityClass);
resolveFields(entityClass);
}
public List<T> findAll(Connection conn) throws SQLException {
String sql = "SELECT * FROM " + tableName;
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
return mapResultSet(rs);
}
}
private List<T> mapResultSet(ResultSet rs) throws SQLException {
List<T> result = new ArrayList<>();
while (rs.next()) {
try {
T entity = entityClass.newInstance();
for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
String column = entry.getKey();
Field field = entry.getValue();
Object value = rs.getObject(column);
field.set(entity, value);
}
result.add(entity);
} catch (Exception e) {
throw new RuntimeException("映射结果集失败", e);
}
}
return result;
}
// 其他方法:insert, update, delete等
}
java复制public class User {
private int id;
private String username;
private String password;
// getters and setters
}
public class TestMiniORM {
public static void main(String[] args) throws SQLException {
MiniORM<User> userORM = new MiniORM<>(User.class);
try (Connection conn = JdbcUtils.getConnection()) {
List<User> users = userORM.findAll(conn);
users.forEach(System.out::println);
}
}
}
Java 8的Stream API可以与JDBC结合,实现更函数式的数据处理:
java复制public Stream<User> streamUsers(Connection conn) throws SQLException {
Statement stmt = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(100);
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<User>(
Long.MAX_VALUE, Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super User> action) {
try {
if (!rs.next()) {
rs.close();
stmt.close();
return false;
}
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
action.accept(user);
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}, false);
}
Java 14引入的Record类型非常适合作为数据库查询结果的载体:
java复制public record UserRecord(int id, String username, String email) {}
public List<UserRecord> getUsersAsRecords() {
String sql = "SELECT id, username, email FROM users";
return JdbcUtils.query(sql, rs -> {
List<UserRecord> users = new ArrayList<>();
while(rs.next()) {
users.add(new UserRecord(
rs.getInt("id"),
rs.getString("username"),
rs.getString("email")
));
}
return users;
});
}
除了使用PreparedStatement外,还应考虑:
输入验证:
java复制public boolean isValidUsername(String username) {
return username != null && username.matches("[a-zA-Z0-9_]{4,20}");
}
最小权限原则:数据库用户只授予必要权限
敏感数据加密:密码等敏感信息应加盐哈希存储
生产环境连接池应配置安全参数:
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost/db");
config.setUsername("app_user");
config.setPassword("secure_password");
config.setMaximumPoolSize(20);
config.setConnectionTimeout(30000);
config.setLeakDetectionThreshold(60000);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
通过JDBC驱动提供的拦截器接口监控SQL性能:
java复制public class TimingInterceptor implements StatementInterceptorV2 {
@Override
public ResultSetInternalMethods preProcess(String sql, Statement interceptedStatement,
Connection connection) throws SQLException {
long startTime = System.currentTimeMillis();
interceptedStatement.setAttribute("startTime", startTime);
return null;
}
@Override
public boolean executeTopLevelOnly() {
return false;
}
@Override
public void destroy() {}
@Override
public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement,
ResultSetInternalMethods originalResultSet, Connection connection, int warningCount,
boolean noIndexUsed, boolean noGoodIndexUsed, SQLException statementException)
throws SQLException {
Long startTime = (Long) interceptedStatement.getAttribute("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
if (duration > 100) { // 记录慢查询
Logger.getLogger("SQL_PERF").warning(
"Slow query took " + duration + "ms: " + sql);
}
}
return originalResultSet;
}
}
在连接URL中启用拦截器:
code复制jdbc:mysql://localhost/db?statementInterceptors=com.example.TimingInterceptor
HikariCP等连接池支持JMX监控:
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost/db");
config.setUsername("user");
config.setPassword("password");
config.setRegisterMbeans(true); // 启用JMX
然后可以通过JConsole或VisualVM监控连接池状态。
随着响应式编程的兴起,出现了R2DBC(Reactive Relational Database Connectivity)规范:
java复制ConnectionFactory connectionFactory = ConnectionFactories.get(
"r2dbc:mysql://user:password@localhost:3306/db");
Mono.from(connectionFactory.create())
.flatMapMany(connection ->
connection.createStatement("SELECT * FROM users")
.execute())
.flatMap(result ->
result.map((row, rowMetadata) ->
row.get("username", String.class)))
.subscribe(System.out::println);
现代云原生应用需要考虑:
Spring Cloud提供了相应的解决方案:
java复制@Configuration
public class DatabaseConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
在多年使用JDBC的开发实践中,我总结了以下宝贵经验:
连接管理黄金法则:
批量操作性能调优:
生产环境必备配置:
properties复制# MySQL连接参数
useSSL=false
allowPublicKeyRetrieval=true
serverTimezone=Asia/Shanghai
characterEncoding=UTF-8
useUnicode=true
rewriteBatchedStatements=true
监控关键指标:
官方文档:
书籍:
开源项目:
在线课程:
通过本文的全面讲解,我们深入探讨了JDBC技术的各个方面:
JDBC作为Java生态中数据库访问的基石,虽然现在有各种ORM框架的封装,但深入理解JDBC原理仍然是成为Java高级开发者的必备技能。希望本文能帮助你在JDBC学习和使用过程中少走弯路,构建高性能、可靠的数据库应用。