记: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<Map<String, Object>> 对象
     *
     * @param resultSet 结果集对象
     * @return 结果集合
     * @throws SQLException SQL异常
     */
    private static List<Map<String, Object>> getDates(ResultSet resultSet) throws SQLException {
        List<Map<String, Object>> dates = new ArrayList<>();
        // 获取结果集的数据结构对象
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            Map<String, Object> 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<Map<String, Object>> 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<DriverInfo> 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");
    }
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
本课程详细讲解了以下内容:    1.jsp环境搭建及入门、虚拟路径和虚拟主机、JSP执行流程    2.使用Eclipse快速开发JSP、编码问题、JSP页面元素以及request对象、使用request对象实现注册示例    3.请求方式的编码问题、response、请求转发和重定向、cookie、session执行机制、session共享问题     4.session与cookie问题及application、cookie补充说明及四种范围对象作用域     5.JDBC原理及使用Statement访问数据库、使用JDBC切换数据库以及PreparedStatement的使用、Statement与PreparedStatement的区别     6.JDBC调用存储过程和存储函数、JDBC处理大文本CLOB及二进制BLOB类型数据     7.JSP访问数据库、JavaBean(封装数据和封装业务逻辑)     8.MVC模式与Servlet执行流程、Servlet25与Servlet30的使用、ServletAPI详解与源码分析     9.MVC案例、三层架构详解、乱码问题以及三层代码流程解析、完善Service和Dao、完善View、优化用户体验、优化三层(加入接口和DBUtil)    1 0.Web调试及bug修复、分页SQL(Oracle、MySQL、SQLSERVER)     11.分页业务逻辑层和数据访问层Service、Dao、分页表示层Jsp、Servlet     12.文件上传及注意问题、控制文件上传类型和大小、下载、各浏览器下载乱码问题     13.EL表达式语法、点操作符和中括号操作符、EL运算、隐式对象、JSTL基础及set、out、remove     14.过滤器、过滤器通配符、过滤器链、监听器     15.session绑定解绑、钝化活化     16.以及Ajax的各种应用     17. Idea环境下的Java Web开发

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值