记:JDBC动态切换数据源,连接不同的数据库

项目中遇到一个需求,要求动态实现切换数据源

  • 起因
  • 经过
    • 首先我们先来回忆一下JDBC连接。(没错,我已经忘了怎么写了。手动捂脸)
    • 改造代码
    • 问题浮现
  • 结果
  • 写在最后
  • 相关源码

起因

八月初突然接到一个需求,要实现一个工具类。可以动态切换数据源,根据不同的数据源访问不同的数据库。
脑海中第一想法使用JDBC去实现,那么说干就干。

经过

首先我们先来回忆一下JDBC连接。(没错,我已经忘了怎么写了。手动捂脸)

1.加载数据驱动
2.建立数据连接对象
3.创建Statement对象
4.执行sql,得到ResultSet对象
5.获取数据
6.关闭连接

’Talk is cheap,show me the code’

		String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/data_test";
        String username = "root";
        String password = "123456";

        String sql = "SELECT * FROM S_STUDENT;";

        try {
            // 1.加载数据库驱动
            Class.forName(driver);
            // 2.建立数据库连接对象
            Connection connection = DriverManager.getConnection(url, username, password);
            // 3.创建Statement对象
            Statement statement = connection.createStatement();
            // 4.执行sql,得到ResultSet对象
            ResultSet resultSet = statement.executeQuery(sql);
            // 5.获取数据,操作数据
            while (resultSet.next()) {
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                System.out.println("name = " + name + "; age = " + age);
            }
            // 6.释放数据
            resultSet.close();
            statement.close();
            connection.close();
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

以上就是一个简单的JDBC连接小例子。好了,下面我们开始改造。

改造代码

核心要求:动态切换数据源
拿上面的例子来说,要动态切换数据库驱动,同时动态的切换数据库连接信息。

思路分析:
1.动态传入数据库驱动和数据库连接信息。
2.定义构造方法为对应的属性进行初始化,从而来实现动态属性的赋值。
3.其他操作无大变动。

代码改造~~~

改造的代码就不贴出来了,会在最后贴一个最终版本。

问题浮现

改造完代码之后进行测试发现,并没有动态的切换数据库驱动。

排查代码以及设计逻辑,无果。

打开浏览器,开始面向Google编程。

直到搜到这篇博客,深入浅出的讲解了一下JDBC的驱动加载。链接奉上,感谢博主分享。

在搜索的时候下面这篇博客写的也不错,这里我也分享出来。大家可以看一看。
链接奉上,感谢博主分享

结果

看过上面的博客之后,了解到DriverManager负责注册和注销数据库驱动。使用DriverManager.getConnection()获取连接对象时,会遍历其维护的Driver信息。然后调用acceptsURL(url)方法判断当前驱动是否打开该URL连接,如果该方法返回true,则返回对应的Driver。 好的了解到这,明白了原来可以使用static代码块将数据库驱动全部交由DriverManager维护。连接时只需要传入对应数据源的url、username、password就可以了。改造代码如下:

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JDBC 工具类
 *
 * @author 山楂罐头
 * @date 2019/8/6
 */
public class JdbcUtil implements Serializable {
    private static final long serialVersionUID = 1382377502744233953L;
    private static final Logger logger = LoggerFactory.getLogger(JdbcUtil.class);

    // 数据库连接对象
    private Connection connection = null;
    // 执行静态SQL语句
    private Statement statement = null;
    // 执行动态SQL语句
    private PreparedStatement preparedStatement = null;
    // 结果集
    private ResultSet resultSet = null;


    // 数据库连接URL
    private String url;
    // 用户名
    private String username;
    // 密码
    private String password;

    static {
        // 加载数据库驱动。注:我将数据源驱动放入到了一个枚举类中,此处是从枚举类中获取。
        for (DB_DRIVER_ENUM dbDriverEnum : DB_DRIVER_ENUM.values()) {
            try {
                // 加载数据驱动,需确保引入相关的jar包
                Class.forName(dbDriverEnum.getDataSourceDriver());
            } catch (ClassNotFoundException e) {
                logger.error("\r\n 初始化 JdbcUtil 数据驱动失败!失败的数据驱动类型:" + dbDriverEnum.getDataSourceDriver());
                e.printStackTrace();
            }
        }
    }

    public JdbcUtil(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    /**
     * 获取数据库连接
     *
     * @return 数据库连接
     */
    private Connection getConnection() {
        try {
            // 建立数据库连接对象
            connection = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            logger.error("\r\n JdbcUtil 连接数据库失败!失败原因:" + ExceptionUtils.getStackTrace(e));
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 执行查询操作
     *
     * @param sql SQL语句
     * @return 返回值是一个结果集
     */
    private ResultSet executeQuery(String sql) {
        try {
            connection = this.getConnection();
            if (null != connection) {
                statement = connection.createStatement();
                if (null != statement) {
                    resultSet = statement.executeQuery(sql);
                }
            }
        } catch (SQLException e) {
            logger.error("\r\n JdbcUtil 执行查询SQL失败!失败原因:" + ExceptionUtils.getStackTrace(e));
            e.printStackTrace();
        }
        return resultSet;
    }

    /**
     * 关闭数据库连接
     */
    private void close() {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 关闭结果集失败!失败原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 关闭执行静态SQL实例对象失败!失败原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 关闭执行动态SQL实例对象失败!失败原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 关闭数据库连接失败!失败原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
    }

    /**
     * 处理resultSet结果集对象,并将结果集对象封装成 List> 对象
     *
     * @param resultSet 结果集对象
     * @return 结果集合
     * @throws SQLException SQL异常
     */
    private static List> getDates(ResultSet resultSet) throws SQLException {
        List> dates = new ArrayList<>();
        // 获取结果集的数据结构对象
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            Map rowMap = new HashMap<>();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                rowMap.put(metaData.getColumnName(i), resultSet.getObject(i));
            }
            dates.add(rowMap);
        }
        return dates;
    }


    /* *************************** 以下为提供外部调用者的调用方法 ************************************/

    /**
     * 测试数据源是否可以连通
     *
     * @return 连通结果
     */
    public Boolean testDBConnect() {
        if (this.getConnection() != null) {
            // 关闭相关连接
            this.close();
            return true;
        } else {
            return false;
        }
    }

    /**
     * 执行查询语句 - 无参数
     *
     * @param querySql 查询语句
     * @return 结果集合
     */
    public List> executeQuerySql(String querySql) {
        try {
            // 执行查询语句
            ResultSet rs = this.executeQuery(querySql);
            if (null != rs) {
                // 处理查询结果
                return getDates(rs);
            }
        } catch (SQLException e) {
            logger.error("\r\n JdbcUtil 执行executeQuerySql()失败!失败原因:" + ExceptionUtils.getStackTrace(e));
            e.printStackTrace();
        } finally {
            // 关闭相关连接
            this.close();
        }
        return null;
    }
}

至此,工具类就初步成型了。调用方式为:

// 检查数据源是否可以连通
JdbcUtil jdbcUtil = new JdbcUtil(url, userName, password);
jdbcUtil.testDBConnect();

写在最后

在面向Google编程的时候,我发现了好多coder都写了JDBC的连接工具。这里给大家推荐一个个人感觉比较好的工具类。有需要的小伙伴猛戳这里。

相关源码

以下是DriverManager的相关源码,以对上面内容的补充和加深理解。

    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
    
    // ...省略部分源码...

    @CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info,Reflection.getCallerClass()));
    }
    
    //  ...省略部分源码...
    
    @CallerSensitive
    public static Driver getDriver(String url)
        throws SQLException {

        println("DriverManager.getDriver(\"" + url + "\")");

        Class callerClass = Reflection.getCallerClass();

        // Walk through the loaded registeredDrivers attempting to locate someone
        // who understands the given URL.
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerClass)) {
                try {
                    if(aDriver.driver.acceptsURL(url)) {
                        // Success!
                        println("getDriver returning " + aDriver.driver.getClass().getName());
                    return (aDriver.driver);
                    }

                } catch(SQLException sqe) {
                    // Drop through and try the next driver.
                }
            } else {
                println("    skipping: " + aDriver.driver.getClass().getName());
            }

        }

        println("getDriver: no suitable driver");
        throw new SQLException("No suitable driver", "08001");
    }
    
    //  ...省略部分源码...
    
    private static Connection getConnection(
        String url, java.util.Properties info, Class caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

你可能感兴趣的:([Java])