目录

15JavaEE核心技术-JDBC和数据访问

15、JavaEE核心技术 - JDBC和数据访问

四、JDBC和数据访问

一、JDBC基础

1. 什么是JDBC?

JDBC(Java Database Connectivity)是Java用于与数据库交互的API,允许Java程序执行SQL语句,访问、处理数据库中的数据。

2. JDBC的作用

  • 提供统一的接口,连接不同的数据库。
  • 允许执行SQL语句,包括CRUD(增删改查)操作。
  • 支持事务管理,确保数据一致性。
  • 提供结果集处理,使得查询结果的处理更加灵活。

3. JDBC的主要组件

  • JDBC驱动(JDBC Driver) :用于连接特定数据库的软件组件,翻译JDBC调用为数据库特有的协议。
  • 连接(Connection) :代表应用程序与数据库之间的会话,用于执行SQL语句。
  • 语句(Statement) :用于执行SQL语句,返回结果集。
  • 结果集(ResultSet) :存储查询返回的数据,允许逐行遍历和数据访问。
  • 事务管理(Transaction Management) :管理数据库事务,确保操作的原子性、一致性、隔离性和持久性(ACID)。

4. JDBC驱动类型

  • 类型1:JDBC-ODBC桥接器
    • 通过ODBC驱动连接数据库,性能较差,主要用于旧系统。
  • 类型2:本地API进程间通信
    • 在客户端和数据库之间使用进程间通信,性能较好,依赖数据库特定的客户端库。
  • 类型3:网络-中间件
    • 通过中间件服务器连接数据库,适合Web应用,增强了安全性和管理能力。
  • 类型4:纯Java驱动
    • 直接通过TCP/IP连接数据库,纯Java实现,性能和安全性最佳。

5. 使用JDBC的基本步骤

  1. 注册JDBC驱动 :使用 Class.forName() 加载驱动类。
  2. 建立连接 :通过 DriverManager.getConnection() 获取连接。
  3. 创建语句 :使用 Connection.createStatement() 创建Statement对象。
  4. 执行SQL语句 :调用 Statement.execute()executeQuery() 执行查询或更新。
  5. 处理结果集 :遍历 ResultSet 获取查询结果。
  6. 关闭资源 :确保关闭Statement、ResultSet和Connection,释放资源。
import java.sql.*;  

public class JdbcExample {  
    public static void main(String[] args) {  
        // 数据库连接URL  
        String url = "jdbc:mysql://localhost:3306/testdb";  
        // 用户名  
        String username = "root";  
        // 密码  
        String password = "password";  

        // 注册JDBC驱动  
        try {  
            Class.forName("com.mysql.cj.jdbc.Driver");  
        } catch (ClassNotFoundException e) {  
            System.out.println("无法加载JDBC驱动:");  
            e.printStackTrace();  
            return;  
        }  
        // 执行SELECT查询  
        String query = "SELECT * FROM users";  
        // try-with-resources,资源会在以下情况下自动关闭:1、正常执行完 try 块。2、发生异常时,虚拟机会确保资源被关闭。
        try (Connection conn = DriverManager.getConnection(url, username, password);  
             Statement stmt = conn.createStatement();  
             ResultSet rs = stmt.executeQuery(query)) {  

            System.out.println("连接数据库成功,执行查询:");  

            // 遍历结果集  
            while (rs.next()) {  
                int id = rs.getInt("id");  
                String name = rs.getString("name");  
                String email = rs.getString("email");  
                System.out.println("User ID: " + id + ", Name: " + name + ", Email: " + email);  
            }  

        } catch (SQLException e) {  
            System.out.println("数据库操作错误:");  
            e.printStackTrace();  
        }  
    }  
} 
二、连接池

1. 为什么需要连接池?

  • 性能优化 :减少频繁创建和关闭连接的开销,提升响应速度。
  • 资源管理 :高效管理数据库连接,避免资源耗尽。
  • 扩展性 :支持更多的并发用户,提高系统吞吐量。

2. 常见的连接池实现

  • HikariCP :性能优异,配置简单,广泛使用。
  • Apache DBCP :稳定可靠,适合中小型应用。
  • C3P0 :功能丰富,适合复杂应用,但性能相对较差。

3. 使用HikariCP实现连接池

步骤

  1. 添加依赖 :在项目中引入HikariCP库。

    <dependency>  
        <groupId>com.zaxxer</groupId>  
        <artifactId>HikariCP</artifactId>  
        <version>5.0.1</version>  
    </dependency>  
  2. 配置属性文件 :创建 hikari.properties 文件,配置连接参数。

    dataSourceClassName= com.mysql.cj.jdbc.Driver  
    dataSource.url=jdbc:mysql://localhost:3306/testdb  
    dataSource.user=root  
    dataSource.password=secret  
  3. 初始化HikariDataSource :在应用启动时初始化连接池。

    import com.zaxxer.hikari.HikariConfig;  
    import com.zaxxer.hikari.HikariDataSource;  
    
    public class DatabaseConfig {  
        private static HikariDataSource dataSource;  
    
        static {  
            HikariConfig config = new HikariConfig("/hikari.properties");  
            dataSource = new HikariDataSource(config);  
        }  
    
        public static Connection getConnection() throws SQLException {  
            return dataSource.getConnection();  
        }  
    }  
  4. 获取连接 :在需要访问数据库时,从连接池获取连接。

    try (Connection conn = DatabaseConfig.getConnection()) {  
        // 执行数据库操作  
    } catch (SQLException e) {  
        e.printStackTrace();  
    }  
  5. 关闭连接池 :在应用关闭时,关闭连接池。

    dataSource.close();  

    完整代码:

import java.sql.*;  

public class HikariCPExample {  
    public static void main(String[] args) {  
        // 数据库连接信息  
        String DB_URL = "jdbc:mysql://localhost:3306/testdb";  
        String USERNAME = "root";  
        String PASSWORD = "password";  

        // HikariCP 配置  
        HikariConfig config = new HikariConfig();  
        config.setJdbcUrl(DB_URL);  
        config.setUsername(USERNAME);  
        config.setPassword(PASSWORD);  

        // 可选配置:连接池参数  
        config.setMinimumIdle(5);   // 最小空闲连接数  
        config.setMaximumPoolSize(10); // 最大连接池大小  
        config.setConnectionTimeout(30000); // 连接超时时间(毫秒)  
        config.setIdleTimeout(60000); // 空闲连接超时时间(毫秒)  

        // 创建 HikariDataSource  
        HikariDataSource dataSource = new HikariDataSource(config);  

        try {  
            // 获取连接  
            Connection conn = dataSource.getConnection();  

            // 执行查询  
            String query = "SELECT * FROM users";  
            try (PreparedStatement stmt = conn.prepareStatement(query)) {  
                try (ResultSet rs = stmt.executeQuery()) {  
                    System.out.println("连接数据库成功,执行查询:");  
                    // 遍历结果集  
                    while (rs.next()) {  
                        int id = rs.getInt("id");  
                        String name = rs.getString("name");  
                        String email = rs.getString("email");  
                        System.out.println("User ID: " + id + ", Name: " + name + ", Email: " + email);  
                    }  
                }  
            }  
        } catch (SQLException e) {  
            System.out.println("数据库操作错误:");  
            e.printStackTrace();  
        } finally {  
            // 关闭连接池  
            dataSource.close();  
        }  
    }  
}  

4. 连接池的常用配置参数

  • connectionTimeout :获取连接的超时时间,避免长时间等待。
  • maximumPoolSize :最大连接数,根据服务器能力和负载调整。
  • minimumIdle :保持的最少空闲连接数,提高响应速度。
  • idleTimeout :空闲连接的超时时间,避免资源浪费。
  • poolName :连接池名称,方便监控和管理。
三、事务管理与锁机制

1. 事务管理

事务的特性(ACID)

  • 原子性(Atomicity) :事务中的操作要么全部执行,要么不执行。
  • 一致性(Consistency) :事务前后,数据库保持一致的状态。
  • 隔离性(Isolation) :多个事务间互不影响,避免数据不一致。
  • 持久性(Durability) :事务提交后,结果永久保存。

JDBC中的事务管理

  1. 自动提交 :默认每个语句提交,可能导致性能问题。

    conn.setAutoCommit(true); // 默认值  
  2. 手动事务管理 :显式控制事务的提交和回滚。

    conn.setAutoCommit(false); // 开始事务  
    try {  
        // 执行多个SQL语句  
        conn.commit(); // 提交事务  
    } catch (SQLException e) {  
        conn.rollback(); // 回滚事务  
    } finally {  
        conn.setAutoCommit(true); // 恢复自动提交  
    }  
  3. 保存点(Savepoint) :在事务中设置保存点,部分回滚。

    Savepoint savePoint = conn.setSavepoint();  
    try {  
        // SQL操作1  
        // SQL操作2  
        conn.releaseSavepoint(savePoint);  
    } catch (SQLException e) {  
        conn.rollback(savePoint);  
    }  

2. 锁机制

锁的类型

  • 乐观锁 :假设多数情况下数据不会冲突,通过版本号或时间戳检测冲突。
  • 悲观锁 :假设数据会发生冲突,事先加锁,阻止其他事务访问。

JDBC中的锁机制

  1. 行级锁 :锁定特定行,允许并行访问。

    SELECT * FROM users WHERE id = 1 FOR UPDATE;  
  2. 表级锁 :锁定整个表,限制并行访问。

    LOCK TABLES users WRITE;  
  3. 事务隔离级别 :通过设置隔离级别,控制数据读取的一致性。

    conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);  
    // 隔离级别:  
    // TRANSACTION_READ_UNCOMMITTED:最低隔离  
    // TRANSACTION_READ_COMMITTED:提交读  
    // TRANSACTION_REPEATABLE_READ:可重复读  
    // TRANSACTION_SERIALIZABLE最高隔离  

4. 常见的并发问题

  • 脏读(Dirty Read) :事务读取了另一个未提交事务的修改。
  • 不可重复读(Non-repeatable Read) :同一事务中,相同查询返回不同结果。
  • 幻读(Phantom Read) :事务在特定条件下返回更多行,破坏一致性。
四、CRUD操作实践

1. 创建操作(Create)

示例:添加用户

public void createUser(String name, String email) {  
    final String SQL = "INSERT INTO users (name, email) VALUES (?, ?)";  
    
    try (Connection conn = DatabaseConfig.getConnection();  
         PreparedStatement pstmt = conn.prepareStatement(SQL)) {  
        
        pstmt.setString(1, name);  
        pstmt.setString(2, email);  
        pstmt.executeUpdate();  
        
    } catch (SQLException e) {  
        e.printStackTrace();  
        // 处理异常,可能回滚或记录日志  
    }  
}  

注意事项

  • 预编译语句(PreparedStatement) :防止SQL注入,提高安全性。
  • 资源管理 :使用try-with-resources自动关闭连接和语句。

2. 读取操作(Read)

示例:查询用户列表

public List<User> getAllUsers() {  
    final String SQL = "SELECT * FROM users";  
    List<User> users = new ArrayList<>();  
    
    try (Connection conn = DatabaseConfig.getConnection();  
         Statement stmt = conn.createStatement();  
         ResultSet rs = stmt.executeQuery(SQL)) {  
        
        while (rs.next()) {  
            User user = new User();  
            user.setId(rs.getInt("id"));  
            user.setName(rs.getString("name"));  
            user.setEmail(rs.getString("email"));  
            users.add(user);  
        }  
        
    } catch (SQLException e) {  
        e.printStackTrace();  
        // 处理异常  
    }  
    return users;  
}