JDBC解析2

173 篇文章 16 订阅
86 篇文章 18 订阅

接上一篇JDBC解析:http://blog.csdn.net/x_iya/article/details/70670342

一个简单的Demo:

package cn.bjut.test;

import java.sql.*;
import java.io.PrintWriter;

/**
 * Created by N3verL4nd on 2017/4/18.
 */
public class jdbc {
    public static void main(String[] args) {
        String driverClass = "com.mysql.jdbc.Driver";
        String jdbcUrl = "jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8&useSSL=true";
        String user = "root";
        String password = "lgh123";

        String sql = "SELECT * FROM persons";

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        /*
		try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
	*/	
        try {
            conn = DriverManager.getConnection(jdbcUrl, user, password);
            System.out.println(conn);
			stmt = conn.createStatement();
            rs = stmt.executeQuery(sql);
            while (rs.next()) {
                System.out.println(rs.getInt(1)+ " " + rs.getString(2) + " " + rs.getInt(3));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
到底需不需要添加加载驱动的代码:

try {
          Class.forName(driverClass);
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
我们先来看一下输出。为了方便查看,执行SQL语句的给注释掉了。

不加:

DriverManager.getConnection("jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8&useSSL=true")
    trying com.mysql.jdbc.Driver
getConnection returning com.mysql.jdbc.Driver
com.mysql.jdbc.JDBC4Connection@5ef04b5
加:

DriverManager.getConnection("jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8&useSSL=true")
    trying com.mysql.fabric.jdbc.FabricMySQLDriver
    trying com.mysql.jdbc.Driver
getConnection returning com.mysql.jdbc.Driver
com.mysql.jdbc.JDBC4Connection@5ef04b5

如上所示,加和不加Class.forName()我们都获得到了JDBC Connection对象。

加载驱动的代码导致了以上输出顺序的不同。

分析:

conn = DriverManager.getConnection(url, username, password);
导致DriverManager的静态代码块的执行。

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}


System.getProperty("jdbc.drivers")
需要配合java -Djdbc.drivers="driver1:driver2"使用。

所以核心的代码只有:

AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            //通过读取META-INF/services的java.sql.Driver来加载驱动
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();//也就是执行new Driver();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

查看DriverManager的API:

DriverManager 类的方法 getConnection 和 getDrivers 已经得到提高以支持 Java Standard Edition Service Provider 机制。 JDBC 4.0 Drivers 必须包括 META-INF/services/java.sql.Driver 文件。此文件包含 java.sql.Driver 的 JDBC 驱动程序实现的名称。例如,要加载 my.sql.Driver 类,META-INF/services/java.sql.Driver 文件需要包含下面的条目: 
my.sql.Driver
应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。当前使用 Class.forName() 加载 JDBC 驱动程序的现有程序将在不作修改的情况下继续工作。 


也就是说Service Provider(java.util.ServiceLoader)机制会自动加载java.sql.Driver文件中的驱动程序。

是不是有点IOC的味道了?

通过如下调试:


我们发现ServiceLoader创建了com.mysql.driver.Driver对象,也导致了Driver静态代码块的执行:

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

再做个测试:

当我们把该文件(java.sql.Driver)删除掉(mysql.jar)



不使用SPI机制,则需要我们使用Class.forName来加载驱动。

而最开始的输出差异,我认为是数据库驱动多次装入导致的。class.forName装载一次驱动,SPI机制装载一次驱动。

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();


SPI介绍:

http://www.cnblogs.com/javaee6/p/3714719.html

http://blog.csdn.net/hintcnuie/article/details/37922089

http://blog.csdn.net/kokojhuang/article/details/8273303

http://singleant.iteye.com/blog/1497259

http://blog.csdn.net/fenglibing/article/details/7083071

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

N3verL4nd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值