`
wxyfighting
  • 浏览: 199518 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

jdbc实现篇-源码(Oracle、MySQL)-第一部分-Dirver注册和获取及Connection获取原理

 
阅读更多

简单说下,本文是说源码的,但是不会一篇文章就说得很深入,本文是【jdbc源码入口篇】,分别会说明一些源码和使用细节,所提及的源码可能相对于jdbc的源码还是初级看源码,看个大概,细节上还有很多东西,后续有时间会跟进;


文章会以oracle、mysql jdbc的实现的源码作为说明的依据来参考;

首先,我们要创建一个链接(连接池是在内部做的),会操作:

Class.forName("xxx.xxx.xxxx.xxx");//类名通常为jdbc的Dirver;

oracle的一般是:

oracle.jdbc.driver.OracleDriver

而mysql通常是:

com.mysql.jdbc.Driver

sql server通常是(本人很少使用SQL Server,所以文章中不会出现SQL server的Driver细节):

com.microsoft.sqlserver.jdbc.SQLServerDriver

然后我们才能用:

DriverManager.getConnection()

方法来获取链接;我们知道这个是获取一个Cnnection,那么我们首先来看看DriverManager的getConnection到底做了什么?为什么必须要Class.forName才行;

跟踪DriverManager类进去发现有四个getConnection方法:

1、所有参数全部通过URL传递过去

getConnection(String url);

2、用户名和密码单独传递:

getConnection(String url , String user , String password);

3、传递多个参数的K-V结构

getConnection(String url , Properties properties);

4、传递多个参数后,还加上指定的ClassLoader,很少用,当跨ClassLoader访问的时候需要使用到,默认使用当前线程的ClassLoader;

getConnection(String url , Properties properties , ClassLoader callerCL);

其实无论如何,会调用到最后一个方法,最后一个方法主要代码为:

private static Connection getConnection(
      String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {

      java.util.Vector drivers = null;
      synchronized(DriverManager.class) {
          if(callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();//如果没传递CLassLoader用当前线程的
          }    
      } 
 
      if(url == null) {
           throw new SQLException("The url cannot be null", "08001");//没有传递URL,则抛出异常
      }

      //注意这个日志,默认是打印不出来的,程序内部会去判定logWriter是否为空,若为空则不会输出,默认也没有值
      println("DriverManager.getConnection(\"" + url + "\")");
    
      if (!initialized) {//初始化,不过几乎可以忽略
        initialize();
      }


      synchronized (DriverManager.class){ 
          drivers = readDrivers; // readDrivers为一个类变量,下面的部分我们看看是如何被注册的
      }

      SQLException reason = null;
      for (int i = 0; i < drivers.size(); i++) {//循环扫描所有的Drivers
            DriverInfo di = (DriverInfo)drivers.elementAt(i);
            if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {//如果类和类的ClassLoader不匹配,跳过
               println("    skipping: " + di);
               continue;
            }
            try {
                 println("    trying " + di);
                 Connection result = di.driver.connect(url, info);//调用对应Driver的connect方法,得到Connection对象
                 if (result != null) {//如果得到了,则返回connection
                      // Success!
                      println("getConnection returning " + di);
                      return (result);
                 }
           } catch (SQLException ex) {
                 if (reason == null) {
                    reason = ex;
                 }
          }
     }
    
     // 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");//没有获取到链接会抛出一个SQL Exception
}



先不用看其他的代码,我们首先可以看出一点DriverManager中注册不止一个Driver驱动,如果一些系统中有多个驱动的时候,然后它循环扫描所有的驱动程序,然后通过connect方法获取是否来调用,这个connect方法每次去循环扫描,是不是会造成一些不必要的开销;为此,我们想看看里面到底有那些Driver,以及得到对应的Driver,输出Driver信息:

我们可以通过以下方式输出循环了那些内容:

方法1:

Enumeration<Driver> enumeration = DriverManager.getDrivers();

然后遍历这个迭代器,就可以得到这些Driver相关信息,可以自己根据实际情况作其他的操作也可以,可以看出Driver就是一个普通java类的实例,只是他有一些基于标准规范,类似于驱动链接的功能而已;

方法2:

设置将Log输出,将数据输出到控制台,我们这里简单输出到控制台:

DriverManager.setLogWriter(new PrintWriter(System.out));

运行这个getConnection方法、getDriver等方法的时候,就会打印出相应的日志信息在控制台上面;

例如运行getConnection方法:

DriverManager.getConnection("jdbc:oracle:thin:@10.20.149.82:1521:fuck")
    trying driver[className=sun.jdbc.odbc.JdbcOdbcDriver,sun.jdbc.odbc.JdbcOdbcDriver@14b9a74]
*Driver.connect (jdbc:oracle:thin:@10.20.149.82:1521:fuck)
    trying driver[className=com.mysql.jdbc.Driver,com.mysql.jdbc.Driver@1779e93]
    trying driver[className=oracle.jdbc.OracleDriver,oracle.jdbc.OracleDriver@1871e77]
getConnection returning driver[className=oracle.jdbc.OracleDriver,oracle.jdbc.OracleDriver@1871e77]



其实DirverManager里面还有一个方法是:

getDriver(String url)方法,其实更加确切的意思应该叫findDriverByUrl,入口参数为url,也就是jdbc url,它是负责匹配url是不是这个dirver可以接受的,每个driver都需要实现一个方法叫:acceptsURL来返回,当前这个Dirver是否可以接受这个URL;而acceptsURL方法是各个厂商提供的驱动程序自己编写的,也就是自己编写这个方法说明我是否支持这个URL,类似Oracle、Mysql、sql server等等数据库都会有不同的jdbc驱动包,所以他们自然就区分开了;而对应到类里面,就是前面在Class.forName("xxx")所对应的类。


getConection方法中,会不断尝试的connect方法中,传入的URL也将会被先判定,然后再执行,jdbc通常认为connect方法本身需要判定一次,就不需要再调用acceptsURL判定一次再调用connection方法了,判定成功就直接返回connection,否则就返回null;只是有个问题是,如果有很多Driver,这里需要逐个遍历,所以文章后面我们建议是将自己的Driver保存起来;


接下来看看每个Driver的connect方法的细节,因为他是负责返回connection的,不过我们可以先看看其中调用解析URL是在哪里调用的,如下:


对于oracle的connect方法相关部分的源码为(OracleDirver类中):




mysq相关的源码为(为com.mysql.jdbc.Dirver(你注册的MySQL驱动)类的父类的:NonRegisteringDriver(同一包)中):




注意parseURL方法就是acceptURL方法调用的下一个目标,内部被隐藏了,例如Mysql jdbc的源码中:

public booleanacceptsURL(String url) throws SQLException {
return (parseURL(url, null) != null);
}


我们这里分别来说下parseURL的一些细节:

OracleDriver中:


private Hashtable parseUrl(String s)
        throws SQLException
    {
        Hashtable hashtable = new Hashtable(5);
        int i = s.indexOf(':', s.indexOf(':') + 1) + 1;//第二个冒号(注意里面还有一个indexOf,所以是第二个)
        int j = s.length();
        if(i == j)
            return hashtable;
        int k = s.indexOf(':', i);//第三个冒号
        if(k == -1)
            return hashtable;
        hashtable.put("protocol", s.substring(i, k));//第二个冒号和第三个冒号之间的认为是协议,比如thin或oci等等
        int l = k + 1;
        int i1 = s.indexOf('/', l);//解析反斜杠
        int j1 = s.indexOf('@', l);//解析@符号的位置
        if(j1 > l && l > i && i1 == -1)//如果既没有反斜杠也没有@则返回null,解析失败
            return null;
        if(j1 == -1)
            j1 = j;
        if(i1 == -1)
            i1 = j1;
        if(i1 < j1)
        {//如果有反斜杠(在@前面),则认为在jdbc URL上传递了用户名和密码,当然可以不传递
            hashtable.put("user", s.substring(l, i1));
            hashtable.put("password", s.substring(i1 + 1, j1));
        }
        if(j1 < j)//如果传递了数据库信息,则将数据库连接串放进去,可以是IP:PORT:SID、describe、TNS等多种格式
            hashtable.put("database", s.substring(j1 + 1));
        return hashtable;
    }



它将jdbc url解析为一个Hash table,并且将协议、db信息解析出来,并且可以放用户名和密码,这样估计很少有人在oracle jdbc url上方用户名和密码,但是的确是可行的,例如你可以这样写你的oracle jdbc:

jdbc:oracle:thin:<user>/<passowrd>@ip:port:sid

然后在DriverManager.getConnection的参数时候就【无需传入用户名和密码】了;

通常的写法更多是:

jdbc:oracle:thin:@ip:port:sid

然后在getConnection的时候,带上用户名和密码

其实MYSQL也是这样,URL上可以传递,只是Oracle在URL上最多就加这些了,不能再加其他的了,而MySQL很麻烦的就在解析URL上面;


MySQL的解析部分:

mysql的解析部分比较复杂,以为内mysql大部分参数都可以通过URL来设置,所以解析很复杂,这里就简单列举部分代码:


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;
        }


        //这几个常量就是mysql jdbc 的前缀特征,如果一个都不符合,则返回null,标示解析失败
        //REPLICATION_URL_PREFIX "jdbc:mysql:replication://"
        //URL_PREFIX   "jdbc:mysql://"
        //MXJ_URL_PREFIX  "jdbc:mysql:mxj://";
        //LOADBALANCE_URL_PREFIX "jdbc:mysql:loadbalance://"; 集群下使用
        if (!StringUtils.startsWithIgnoreCase(url ,URL_PREFIX) 
              && !StringUtils.startsWithIgnoreCase(url ,MXJ_URL_PREFIX)
              && !StringUtils.startsWithIgnoreCase(url , LOADBALANCE_URL_PREFIX)
              && !StringUtils.startsWithIgnoreCase(url , REPLICATION_URL_PREFIX)) { //$NON-NLS-1$
             return null;
       }

       int beginningOfSlashes = url.indexOf("//");//分析content部分,后面将会是IP、PORT、库名以及扩展串

       if (StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)) {
            urlProps.setProperty("socketFactory" , "com.mysql.management.driverlaunched.ServerLauncherSocketFactory");
       }
       int index = url.indexOf("?"); //参数分隔符号
       if (index != -1) {//如果带有非默认参数,则循环
           String paramString = url.substring(index + 1, url.length());
           url = url.substring(0, index);


           StringTokenizer queryParams = new StringTokenizer(paramString, "&");//拆分每个参数,放在迭代器上


           while (queryParams.hasMoreTokens()) {//迭代每个参数
               String parameterValuePair = queryParams.nextToken();
               int indexOfEquals = StringUtils.indexOfIgnoreCase(0 , parameterValuePair, "=");//拆分K-V,即参数和值

               String parameter = null;
               String value = null;

               if (indexOfEquals != -1) {
                   parameter = parameterValuePair.substring(0, indexOfEquals);

                   if (indexOfEquals + 1 < parameterValuePair.length()) {
                       value = parameterValuePair.substring(indexOfEquals + 1);
                   }
              }

              //将数据放在urlProps中,为一个当前Driver全局参数配置Properties类型
              if ((value != null && value.length() > 0) && (parameter != null && parameter.length() > 0)) {
                 try {
                       urlProps.put(parameter, URLDecoder.decode(value , "UTF-8"));
                 } catch (UnsupportedEncodingException badEncoding) {
                       // punt
                       urlProps.put(parameter, URLDecoder.decode(value));
                 } catch (NoSuchMethodError nsme) {
                       // punt again
                       urlProps.put(parameter, URLDecoder.decode(value));
                 }
          }
       }
}
......



好了,还有些代码没贴,太多了,上面的URL解析知道是那个Driver去解析后,然后getConection就是newXXXConnection的问题,当然如果自己设置Properties这些参数会做相关的设置,例如Oracle的Properties参数中除了正常的参数外,还可以设置(一般情况下保持默认就可以了,后续的文章中我会提到一些参数,并根据源码说明它的默认值):

database、server、protocol、dll、prefetch、rowPrefetch、defaultRowPrefetch、batch、executeBatch、defaultExecuteBatch、remarks、remarksReporting、synonyms、includeSynonyms、restrictGetTables、fixedString、dataSizeUnits、AccumulateBatchResult

等等参数;

如源码所示(还是在connect方法中):


同时oracle还可能是:oracle.jdbc.ultra.client.Driver(一般用不到,知道有就可以,在源码中有体现)。也有可能是OCI接口(OracleOCIConnection);如果是在我们大多数用thin的模式下,最终会调用:

neworacle.jdbc.driver.OracleConnection(dbaccess, s1, s2, s3, s4, properties);

这些参数细节,下一篇文章说,这里要说进去很多。


mysql的参数都在parserURL里面被设置到Driver的全局变量中(PropertiesurlProps),在解析后,只需要解析后直接new一个com.mysql.jdbc.Connection就可以了;


我们现在知道connection通过【对应driver】的【connect】方法如何new出来了,也可以看出每次请求DirverManger.getConnection都会new 一个Connection出来(所以要注意连接池的用途,需要对connection提供调度等策略);

调用getConection的时候,还会遍历所有【被注册的Dirver】,所以我们也想办法让它尽量【不要被反复遍历】,顺便提及下,大家别被Dirver吓怕了,其实我们看看源码,其实就是一个对象嘛,只是它的功能有点像驱动而已,他提供一些注册和链接的方法,被我们成为Driver;


回过头来,Driver是在什么时候被注册到DriverManager里面的呢?奇怪,如果未被注册,肯定是获取不到的,看看自己的代码:

Class.forName("dddd");

为什么要写这一句,这一句话简单来讲仅仅是用当前的CLassLoader将一个类进行装载的代码,若已经装载,什么也不会做;

但是DriverManager里面怎么知道的呢?难道所有的类都在里面,那要遍历起来太吓人了,而且我们开始输出过所有的Driver这是错误的,而且Dirver都要求有相应的方法,难道是都implements Driver?有这种可能性,在方法实现上可以做到;

到底是怎么样的呢?

其实在Oracle JDBC或MySQL JDBC里面都一个【static匿名块】,static 匿名块是当你【第一次访问这个Class(类)的时候】,会被调用,以后不会被重新初始化,分别如下:

Orale的oracle.jdbc.driver.OracleDriver类里面:


可见,他自己调用了DriverManager.registerDirever方法,注册进去,然后new了一个自己;使得自己是全局单例的;

再看看MySQL的:

在类:com.mysql.jdbc.Driver类中:


同样的方式,进行注册了,原来是这样,忽悠了很久,不是什么人名字,而是提供了registerDriver(Dirver driver)方法来注册;

我们再来看看注册到底做了什么:

DriverManager.registerDriver()我们看看:


说明什么,就是做了一个类似于List的东西(内部可以看出它是用Vector实现的,java用vector其实是很早的写法,不过由于这部分代码实际应用中一般不会被经常调用,所以先关的代码也没有做多少修改),然后将数据写进去,原来注册这么简单,当你有一天要来注册某种链接的时候,你要基于一个统一标准来写的时候,就可以用这种方式注册了,例如,你自己写一个某种数据库库的链接对象,或对某些NoSQL做二次包装,这样上面的程序就可以变得比较通用了;


通过上面的可以看出,放在static匿名块中的代码是在第一次访问这个类的时候被调用,所以不一定非要用Class.forName()因为这样就是访问了这个类;你可以调用这个类里面的一个静态方法,或new一个出来都会访问到这个类,当然driver为了避免重复创建,所以我们一般不用new而已,因为他自己会内部创建一个注册到Driver中,除非调用DriverManager.deregisterDriver(Driver driver)来取消注册,将Driver中被注册的那个对象去掉;


原理上我们说得差不多了,有些地方其实我们可以节约一些开销,当然不是特别关键的;

其一:

我们发现每次通过DriverManager.getConnection都需要去遍历获取Driver然后去调用connect方法,其实我们认为Driver获取一次就可以了,对于相同类型的driver,一般我们的程序就可以认出来是mysql还是oracle;

所以我们可以通过DriverManager.getDriver(url)只获取一次,你可以放入一个模型就可以,分别表示:

oracleDriver、mySqlDriver,这样就直接调用对应的driver了;

例如:

private static OracleDriver oracleDriver = null;


static {
   try {
      //Class.forName("oracle.jdbc.driver.OracleDriver");
      OracleDriver.getCompileTime();//随便访问一个静态方法即可,其实只是想访问下这个类,Class.forName是最简单的访问。
      oracleDriver = (OracleDriver) DriverManager.getDriver("jdbc:oracle:thin:@ip:port:sid");
    } catch (Exception e) {
      e.printStackTrace();
   }
}


这样这个OracleDriver就被保存起来,以后就可以直接使用oracleDriver.connect来获取链接了。


其二:

你也可以new一个driver出来,但是注意这里请保持单利,这样会比较好,一些全局设置可以再全局生效,上面也说了,不推荐,只是说他是一种方法而已;

例如:

private final static Driver oracleDriver = new OracleDriver();


最后DriverManager里面还有些其他的方法,比如一些去掉注册的方法之类的,如果对于注册较多的情况下,需要将一些不需要的去掉,可以再这进行参考;


下一篇文章,会提及到Connection获取的一些细节,就是具体某个DriverConnection类的实现体,以及PrepareStatement实现,setTimeout实现机制、fetchSize内部运行

后面还会继续介绍,和数据库交互过程中,反解析数据的过程,中断链接的原理等具体的实现。

不过肯定不会将所有的源码全部说到,因为有很多不常用,例如存储过程的调用之类的,暂时不是提到。



分享到:
评论

相关推荐

    JDBC访问Mysql或Oracle要用的JAR包

    标题中的“JDBC访问Mysql或Oracle要用的JAR包”指的是Java Database Connectivity(JDBC)驱动程序,这些驱动程序允许Java应用程序与MySQL和Oracle数据库进行交互。在Java编程中,JDBC是Java标准版(Java SE)的一...

    Mysql JDBC源码 官网版

    MySQL JDBC源码是Java开发人员与MySQL数据库进行交互的重要组件,它实现了Java Database Connectivity (JDBC) API,允许Java应用程序连接到MySQL服务器并执行SQL语句。MySQL Connector/J是官方提供的JDBC驱动程序,...

    jdbc接连数据库:oracle/derby/mysql

    标题中的“jdbc连接数据库:oracle/derby/mysql”是指使用Java Database Connectivity (JDBC) API来与三种不同的数据库系统——Oracle、Apache Derby和MySQL进行交互。JDBC是Java平台的标准接口,它允许Java应用程序...

    java连接mysql-oracle源码整理

    3. **创建Connection和Statement**:过程与MySQL相似,但需注意Oracle可能需要设置额外的属性,如` oracle.jdbc.driver.T4CConnection.logon()` 方法中的参数。 4. **执行PL/SQL块**:Oracle支持PL/SQL,因此可以...

    JAVA连mysql源码包JDBC

    本资源“JAVA连mysql源码包JDBC”提供了一个使用JDBC连接MySQL数据库的源码示例,这对于理解和实践Java数据库操作至关重要。 JDBC的核心在于它的四大接口:Connection、Statement、PreparedStatement和ResultSet。...

    oracle jdbc mysql 等数据连接所有的配置

    jTDS 是一个开放源码的 JDBC 驱动程序,支持 SQL Server 和 Sybase 数据库。 ```java Class.forName("net.sourceforge.jtds.jdbc.Driver"); String url = "jdbc:jtds:sqlserver://localhost:1433/samples;tds=8.0";...

    eclipse-jdbc连接数据库源码

    JDBC是Java平台的一部分,它为Java应用程序提供了一种标准的方法来访问关系型数据库,无论是Oracle、MySQL、SQL Server还是其他任何支持JDBC的数据库。通过JDBC,开发者可以执行SQL语句、处理结果集、事务管理等操作...

    JDBC连接ORACLE数据库源码

    JDBC(Java Database Connectivity)是Java语言中用来与数据库交互的标准API,它为开发者提供了一种标准的方式来访问各种类型的数据库,如MySQL、Oracle、SQL Server等。JDBC由一组接口和类组成,这些接口和类定义了...

    JDBC数据连接源码.rar

    **JDBC数据连接源码详解** Java Database Connectivity (JDBC) 是Java编程语言与各种数据库交互的一种标准接口。...通过分析和学习这些源码,你可以更好地理解和运用JDBC技术,实现高效、安全的数据库应用。

    JDBC底层架构的模拟实现

    为了深入理解JDBC,我们可以阅读源码,如`java.sql.DriverManager`和具体的数据库驱动实现,这将有助于我们更好地掌握JDBC的工作原理,以及如何优化数据库操作,提高性能。同时,实践是检验真理的唯一标准,通过编写...

    数据库访问层源码(oracle,mysql,mssql,oledb)

    本资源包含针对四种常见数据库——Oracle、MySQL、MSSQL(Microsoft SQL Server)和OLEDB的源码实现,这为我们提供了一个深入理解数据库访问层设计和实现的良好机会。 Oracle数据库访问层源码通常会包含连接管理、...

    JDBC数据库实现源码

    JDBC提供了一组API,使得Java开发者能够在应用程序中执行SQL语句,从而实现数据的存取、查询、更新和删除等操作。在Java项目中,JDBC被广泛应用于各种数据库系统的连接,如MySQL、Oracle、SQL Server等。 JDBC的...

    mysql,sqlServer,oracle 驱动包 for java

    - 对于SQL Server,Java应用通常使用Microsoft提供的JDBC驱动,如`sqljdbc_auth.dll`和`mssql-jdbc.jar`(适用于SQL Server 2017及以上版本),或早期的`jtds.jar`第三方驱动。 - SQL Server JDBC驱动支持身份验证...

    mysql8.0.11驱动包,兼容支持MySQL 5.5, 5.6, 5.7,8.0.zip

    其源码软件的特性意味着用户可以自由地下载、修改和分发MySQL,这促进了社区的繁荣和技术创新。MySQL数据库因其高效、稳定和易于管理而在全球范围内被广泛采用,特别是在Web应用中。 MySQL 8.0版本带来了许多重要的...

    JDBC数据库操作

    JDBC提供了一种标准化的方式来访问各种类型的数据库,无论底层数据库是MySQL、Oracle还是SQL Server等。在本篇中,我们将深入探讨JDBC的核心概念、使用步骤以及相关工具。 **1. JDBC核心概念** - **Driver Manager...

    java JDBC编程源码

    在Java编程中,JDBC是不可或缺的一部分,它允许开发者编写与数据库交互的代码,支持多种数据库,如MySQL、Oracle、SQL Server等。 本压缩包中的"java JDBC编程源码"很可能包含以下几个方面的内容: 1. **JDBC连接...

    jsp 连接数据库 mysql oracle sqlserver

    在IT行业中,JSP(JavaServer ...对于不同的数据库,如MySQL、Oracle和SQL Server,它们的JDBC驱动和连接URL会有所差异,需要根据具体情况进行调整。同时,掌握Servlet和JavaBean的使用能进一步提升Web应用的开发能力。

    商业编程-源码-数据库连接演示.zip

    在"商业编程-源码-数据库连接演示.zip"这个压缩包中,我们很显然会找到一个关于如何在商业环境中建立和操作数据库连接的源代码示例。这通常是用某种编程语言实现的,可能是Java、Python、C#或PHP等。下面我们将深入...

    JDBC数据源连接池的配置和使用示例

    本篇文章将详细讲解JDBC数据源连接池的配置和使用,以帮助你更好地理解和应用这一关键技术。 ### 1. 什么是数据源连接池 数据源连接池(DataSource或Connection Pool)是一种管理数据库连接的技术,它预先创建并...

Global site tag (gtag.js) - Google Analytics