java maintains multiple database connections

1. Driver Manager maintains a driver list

Take our familiar Mysql Driver for example:

package com.mysql.jdbc;

import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

When we execute the following statement, the content of the static block will be executed, so com.mysql.jdbc.Driver successfully registered itself in the Driver Manager driver list.

Class.forName("com.mysql.jdbc.Driver");

Take a look at the registration implementation of DriverManager:

 private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

The code means that if the current Driver does not exist, it will be added, otherwise nothing will be executed.

So there's our Mysql driver class in the DriverManager class.

The same is true for Oracle, where the loaded driver automatically registers itself with Driver Manager in the static block when it is loaded.

So we understand that Driver Manager maintains a list of database drivers, and there is only one database connection of the same type in this list. For example, mysql and oracle are used in our system, and there are only two types of database drivers in our Driver Manager, regardless of our actual situation. With multiple mysql databases, the drivers are the same.

2. The acquisition logic is driven by the specific driver.

See how DriverManager gets database connections:

Step 1: Construct user information

  @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()));
    }

Step 2: Get the connection

//  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        // Thread synchronization to prevent and issue problems
        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 + "\")");

       SQLException reason = null;
                // Loop the current database driver to get the database connection
        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());
                    // This place is driven by a specific database.
                    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");
    }

For the above code, we don't need to pay all attention to it. We just need to know that the connection acquisition process is done by looping the existing drivers and then by each driver itself. Let's look at the driver implementation of mysql:

 public java.sql.Connection connect(String url, Properties info) throws SQLException {
        if (url == null) {
            throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);
        }
                // First of all, it is judged whether the current url is a load-balanced url or not, if it is a load-balanced acquisition logic.
        if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
            return connectLoadBalanced(url, info);
        } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
            return connectReplicationConnection(url, info);
        }

        Properties props = null;
                // This place will determine whether the current url belongs to the prefix of the mysql connection or not.
        if ((props = parseURL(url, info)) == null) {
            return null;
        }

        if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
            return connectFailover(url, info);
        }
                // In short, after a series of judgments, our program began to really get the connection we wanted.
        try {
            Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);

            return newConn;
        } catch (SQLException sqlEx) {
            // Don't wrap SQLExceptions, throw
            // them un-changed.
            throw sqlEx;
        } catch (Exception ex) {
            SQLException sqlEx = SQLError.createSQLException(
                    Messages.getString("NonRegisteringDriver.17") + ex.toString() + Messages.getString("NonRegisteringDriver.18"),
                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);

            sqlEx.initCause(ex);

            throw sqlEx;
        }
    }

Let's look at the parseURL method implementation:

 private static final String URL_PREFIX = "jdbc:mysql://";
@SuppressWarnings("deprecation")
    public Properties parseURL(String url, Properties defaults) throws java.sql.SQLException {
        Properties urlProps = (defaults != null) ? new Properties(defaults) : new Properties();

        if (url == null) {
            return null;
        }
// Determine if the current url starts with "jdbc:mysql:/";
        if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)
                && !StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {

            return null;
        }

       ...There's a whole bunch of logic.

        return urlProps;
    }

For different databases, because the connection URLs used are different, for example, the connection format of mysql is as follows

jdbc:mysql://localhost:3306/test?characterEncoding=utf-8

oracle's connection string is as follows:

jdbc:oracle:thin:@127.0.0.1:1521:news

So by prefixing different connection strings, it can be distinguished whether the current driver is the target driver or not. If not, DriverManager then cycles the next driver to try to get the connection. In this way, the connection of different types of databases can be obtained through the url of Driver Manager. At this point, we find that Driver Manager maintains only the driver. It is up to us to get the connection of the type of database and the connection of the database, because when we get the connection of the database, the connection information is specified by ourselves.

3. How to maintain multiple database connections

From the above analysis, we know that we can get the connection of the database by providing the url, username and password of the connection. If we want to maintain multiple database connections, we can provide multiple sets of urls, usernames and passwords. And if you want to manage these connections manually, it's very simple. In fact, it's just how to manage multiple sets of database connection information. Examples are as follows:

1. Database information

There are two databases: jdbc:mysql://localhost:3306/test and jdbc:mysql://localhost:3306/demo.

2. Table structure information

CREATE TABLE `user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

3. Data Information

3.1. user table information of test library

id username password
1 u3 p3

3.2. user table information of demo Library

id username password
1 u1 p1
2 u2 p2

4. Sample code for managing multiple data sources

This is just a simple use of map to maintain our multiple data sources, you can completely transform it into the way you want, such as master-slave structure of the database. Of course, we don't have to maintain these data sources by ourselves, just let you know the principle of multi-source maintenance, and we use the corresponding framework to achieve the true multi-source.

package com.bsx.test;

import lombok.Data;
import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: Analog Multi-Data Source Management
 * @author: ztd
 * @date 2019/7/8 4:41 p.m.
 */
public class MultiConnTest {

    /**
     * Multiple Data Source Processing
     * 1.insert Use a data source
     * 2.query Use another data source
     *
     * @throws Exception
     */
    @Test
    public void testMultiDB() throws Exception {
        DBConf test = new DBConf("root", "12345678", "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8");
        DBConf demo = new DBConf("root", "12345678", "jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8");
        Map<String, DBConf> dbConfMap = new HashMap<>();
        dbConfMap.put("test", test);
        dbConfMap.put("demo", demo);
        Connection connection = getConn(dbConfMap.get("test"));
        System.out.println("======print test user info======");
        printUserInfo(connection);
        connection = getConn(dbConfMap.get("demo"));
        System.out.println("======print demo user info======");
        printUserInfo(connection);
    }

    public static void printUserInfo(Connection connection) throws Exception {
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("SELECT * FROM user");
        while (resultSet.next()) {
            System.out.println("id:" +resultSet.getInt(1) + " name: " + resultSet.getString(2) + " password: " + resultSet.getString(3));
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

    public static Connection getConn(DBConf dbConf) {
        return initMysql(dbConf.getUrl(), dbConf.getUser(), dbConf.getPassword());
    }

    /**
     * @description Connect mysql
     * @author ztd
     * @date 2019/7/8 5:06 p.m.
     */
    public static Connection initMysql(String url, String user, String password) {
        Connection conn = null;
        try{
            //jdbc: database type: / / host IP: port / database name? characterEncoding = encoding
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(url, user, password);
        }catch(Exception e){
            System.out.println("Database connection exception!");
            e.printStackTrace();
        }
        return conn;
    }

    @Data
    class DBConf {
        private String user;
        private String password;
        private String url;

        public DBConf(String user, String password, String url) {
            this.user = user;
            this.password = password;
            this.url = url;
        }
    }


}

Operation results:

======print test user info======
id:1 name: u3 password: p3
======print demo user info======
id:1 name: u1 password: p1
id:2 name: u2 password: p2

Keywords: MySQL Database Java JDBC

Added by rinjani on Fri, 16 Aug 2019 10:13:12 +0300