package com.cn.util;

import java.io.FileReader;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * @Description 连接数据库的工具类，主要负责加载数据库连接相关配置信息、注册驱动以及提供获取数据库连接的方法，
 *              通过使用连接池（`ComboPooledDataSource`）来管理数据库连接，提高连接的复用性和系统性能，同时具备日志记录功能，方便排查异常情况。
 * @author ljy
 * @since 1.0
 * @version 1.0
 * @date 2019年8月14日
 */
public class JDBCUtil {

    // 用于存储数据库驱动名称的静态变量，通过读取配置文件获取其具体值，后续用于注册驱动或配置数据源时指定驱动类。
    private static String driver;
    // 用于存储数据库连接地址的静态变量，从配置文件中读取相应配置，代表要连接的数据库的具体位置和相关参数等信息。
    private static String url;
    // 用于存储数据库用户名的静态变量，从配置文件获取，用于在建立数据库连接时进行身份验证。
    private static String user;
    // 用于存储数据库密码的静态变量，同样从配置文件读取，配合用户名完成数据库连接的身份验证过程。
    private static String password;

    // `ComboPooledDataSource` 是 `c3p0` 连接池的数据源对象，用于管理数据库连接，在这里作为静态变量，在类加载时进行初始化配置，
    // 后续通过它来获取数据库连接，提高连接的复用性和系统整体性能，避免频繁创建和销毁数据库连接带来的开销。
    private static ComboPooledDataSource dataSource = null;

    // 使用 `Logger` 记录日志，这里获取了名为 `JDBCUtil` 类的日志记录器，用于记录在数据库连接相关操作过程中出现的各种信息（如错误信息等），方便调试和排查问题。
    private static Logger logger = Logger.getLogger(JDBCUtil.class.getName());

    static {
        // 静态代码块，在类加载时执行，主要用于读取数据库连接相关的配置文件（`jdbc.properties`），获取配置信息，注册驱动以及配置数据源。
        try {
            // 1. 创建 `Properties` 集合类，用于加载和存储配置文件中的键值对数据，常用于读取配置文件中的配置信息，方便获取特定的属性值。
            Properties pro = new Properties();
            // 获取 `src` 路径下的文件，通过类加载器（`ClassLoader`）来定位资源文件（`jdbc.properties`）的路径，
            // 这样可以确保在不同的运行环境（如打包后的项目结构等）下都能正确找到配置文件。
            ClassLoader classLoader = JDBCUtil.class.getClassLoader();
            URL resource = classLoader.getResource("jdbc.properties");
            String path = resource.getPath();
            // 2. 加载文件，将配置文件（`jdbc.properties`）中的键值对数据读取到 `Properties` 对象中，以便后续通过属性名获取相应的值。
            pro.load(new FileReader(path));
            // 3. 获取数据，从 `Properties` 对象中根据指定的属性名获取对应的数据库连接配置信息，分别赋值给相应的静态变量。
            url = pro.getProperty("jdbc.url");
            user = pro.getProperty("jdbc.user");
            password = pro.getProperty("jdbc.password");
            driver = pro.getProperty("jdbc.driver");

            // 4. 注册驱动，以下这行代码被注释掉了，原本是通过 `Class.forName(driver)` 的方式显式注册数据库驱动，
            // 在较新版本的 JDBC 中，很多数据库驱动在加载时会自动注册，所以不一定需要手动注册，不过有些情况还是可能需要，具体取决于使用的驱动和环境。
            // Class.forName(driver);

            // 5. 配置数据源，创建 `ComboPooledDataSource` 对象并进行一系列配置，设置数据库连接相关的各种参数，以初始化连接池。
            dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass(driver);
            dataSource.setJdbcUrl(url);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            // 设置连接池初始化时创建的连接数量，这里设置为10个连接，即连接池启动时会预先创建10个数据库连接备用。
            dataSource.setInitialPoolSize(10);
            // 设置连接池允许的最大连接数量，防止过多的连接请求导致系统资源耗尽等问题，这里限制为50个连接。
            dataSource.setMaxPoolSize(50);
            // 设置连接池允许的最小连接数量，当连接池中连接数量低于这个值时，连接池可能会自动补充连接，这里设为1个连接。
            dataSource.setMinPoolSize(1);
            // 设置当连接池中的连接不够用时，每次增加的连接数量，这里每次增加3个连接，以满足更多连接请求的情况。
            dataSource.setAcquireIncrement(3);
        } catch (Exception e) {
            // 如果在上述读取配置文件、注册驱动或配置数据源等操作过程中出现异常，通过日志记录器记录错误信息，同时打印异常栈追踪信息，方便排查问题。
            logger.error("注册Driver驱动发生异常。", e);
            e.printStackTrace();
        }
    }

    /**
     * 返回数据库链接的方法，通过连接池（`dataSource`）获取数据库连接，若获取过程中出现 `SQLException` 异常，
     * 则会记录错误日志并打印异常栈追踪信息，最后返回获取到的数据库连接（可能为 `null`，如果获取失败的话）。
     * 
     * @return 成功获取到的数据库连接对象，若出现异常则返回 `null`。
     */
    public static Connection getConnection() {
        Connection conn = null;
        try {
            // 通过连接池（`dataSource`）获取数据库连接，这种方式利用了连接池对连接的管理机制，提高连接的复用性和性能，
            // 相较于直接使用 `DriverManager.getConnection(url, user, password)`（注释掉的方式）更便于管理连接资源。
            conn = dataSource.getConnection();
            // 以下这行代码被注释掉了，原本是直接通过 `DriverManager` 获取数据库连接的方式，这种方式相对简单直接，但缺乏连接池的优势，
            // 在实际应用中通常优先使用连接池来管理连接。
            // conn = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            // 如果在获取数据库连接过程中出现 `SQLException` 异常，使用日志记录器记录错误信息，同时打印异常栈追踪信息，方便排查问题。
            logger.error("获取Connection连接发生异常。", e);
            e.printStackTrace();
        }
        return conn;
    }
}