跳至主要內容

Java SQL

大约 22 分钟

Java SQL

一:SQL包的接口设计

1.1 java.sql包和javax.sql包

Java提供的与数据库操作相关的包主要有两个,它们是java.sql包和javax.sql包。java.sql和javax.sql共同为Java提供了强大的JDBC能力。我们接下来会介绍几个日常工作中常接触到的类,它们都由java.sql或javax.sql提供。

1.1 java.sql包

java.sql通常被称为JDBC核心API包,它为Java提供了访问数据源中数据的基础功能。基于该包能实现将SQL语句传递给数据库、从数据库中以表格的形式读写数据等功能。

java.sql提供了一个Driver接口作为数据库驱动的接口。不同种类的数据库厂商只需根据自身数据库特点开发相应的Driver实现类,并通过DriverManager进行注册即可。这样,基于JDBC便可以连接不同公司不同种类的数据库。

除此之外,java.sql还为数据库连接、SQL语句、结果集等提供了众多的类,如表示数据库连接的Conection类、表示数据库操作语句的Statement类、表示数据库操作结果的ResultSet类等。

基于java.sql包,Java程序能够完成各种数据库操作。通常完成一次数据库操作的流程如下所示。

  • 建立DriverManager对象。
  • 从DriverManager对象中获取Connection对象。
  • 从Connection对象中获取Statement对象。
  • 将SQL语句交给Statement对象执行,并获取返回的结果,结果通常放在ResultSet中。

1.2 Javax.sql包

javax.sql通常被称为JDBC扩展API包,它扩展了JDBC核心API包的功能,提供了对服务器端的支持,是Java企业版的重要部分。

例如,javax.sql提供了DataSource接口,通过它可以获取面向数据源的Connection对象,与java.sql中直接使用DriverManager建立连接的方式相比更为灵活(实际上,DataSource接口的实现中也是通过DriverManager对象获取Connection对象的),除此之外,javax.sql还提供了连接池、语句池、分布式事务等方面的诸多特性。

使用javax.sql包扩展了java.sql包之后,建议使用DataSource来获取Connection对象,而不是直接使用DriverManager对象。于是,一条SQL语句的执行过程如下,

  • 建立DataSource对象。
  • 从DataSource对象中获取Connection对象。
  • 从Connection对象中获取Statement对象。
  • 将SQL语句交给Statement对象执行,并获取返回的结果,结果通常放在ResultSet中。

1.2 DriverManager

DriverManager 接口位于java.sql,它是JDBC驱动程序管理器,可以管理一组JDBC驱动程序。DriverManager的一个重要功能是能够给出一个面向数据库的连接对象Connection,该功能是由 DriverManager 中的 getConnection 方法提供的。

当调用 getConnection 方法时,DriverManager 会尝试在已经加载的驱动程序中找出合适的一个,并用找出的驱动程序建立一个面向指定数据库的连接,最后将建立的连接返回。

DriverManager 中主要有下面几个方法。这些方法都是静态方法,不需要建立DriverManager对象便可以直接调用。

  • void registerDriver :向 DriverManager 中注册给定的驱动程序。
  • void deregisterDriver:从 DriverManager 中删除给定的驱动程序。
  • Driver getDriver:查找能匹配给定URL路径的驱动程序。
  • Enumeration getDrivers:获取当前调用者可以访问的所有已加载的JDBC驱动程序。
  • Connection getConnection:建立到给定数据库的连接。

1.3 DataSource

DataSource 是 javax.sql的一个接口。顾名思义,它代表了一个实际的数据源,其功能是作为工厂提供数据库连接。

DataSource 接口中只有以下两个接口方法,都用来获取一个Connection 对象:

  • getConnection():从当前的数据源中建立一个连接。
  • geConnection(String,Sring):从当前的数据源中建立一个连接,输入的参数为数据源的用户名和密码。

javax.sal 中的 DataSoure 仅仅是一个接口,不同的数据库可以为其提供多种实现,常见的实现有以下几种。

  • 基本实现:生成基本的到数据库的连接对象 Connection。
  • 连接池实现:生成的 Connection 对象能够自动加到连接池中。
  • 分布式事务实现:生成的Connection 对象能够参与分布式事务。

正因为 DataSource 接口可以有多种实现,与直接使用 DriverManager 获得连接Connection 的方式相比更为灵活。在日常的开发过程中,建议使用 DataSource 来获取数库连接。

而实际上,在 DataSource 的具体实现中,最终也是基于 DriverManager 获Connection,因此 DataSource 只是 DriverManager 的进一步封装。

1.4 Connection

Connection 接口位于java.sql中,它代表对某个数据库的连接。基于这个连接,可以完成SOL语句的执行和结果的获取等工作。

Connection 中常用的方法如下:

  • Statement createStatement:创建一个 Statement 对象,通过它能将 SOL 语句发送到数据库。
  • CallableStatement prepareCall: 创建一个CallableStatement对象,通过它能调用存储过程。
  • PreparedStatement prepareStatement: 创建一个PreparedStatement 对象,通过它能将参数化的 SOL语句发送到数据库。
  • String nativeSQL:将输入的 SQL 语句转换成本地可用的 SQL语句。
  • void commit:提交当前事务。
  • void rollback:回滚当前事务。
  • void close:关闭当前的 Connection 对象。
  • boolean isClosed:查询当前 Connection 对象是否关闭
  • boolean isValid:查询当前 Connection 是否有效。
  • void setAutoCommit:根据输入参数设定当前 Connection对象的自动提交模式
  • int getTransactionIsolation:获取当前 Connection 对象的事务隔离级别。
  • void setTransactionIsolation:设定当前 Connection 对象的事务隔离级别
  • DatabaseMetaData getMetaData:获取当前 Cnnection 对象所连接的数据库的所有元数据信息。

上述方法主要用来完成获取 Statement 对象、设置 Connection 属性等功能。

同时,Connection 中存在事务管理的方法,如 commit、roliback 等。通过调用这些事务管理方法可以控制数据库完成相应的事务操作。

1.5 Statement

Statement 接口位于java.sql 中,该接口中定义的一些抽象方法能用来执行静态 SOL语句并返回结果。通常 Statement 对象会返回一个结果集对象 ResultSet

Statement接口中的主要方法有:

  • void addBatch:将给定的SQL命令批量添加到Statement对象的SOL命令列表中。
  • void clearBatch:清空 Statement 对象的SOL命令列表。
  • int [] executeBatch:让数据库批量执行多个命令。如果执行成功,则返回一个数组数组中的每个元素都代表了某个命令影响数据库记录的数目。
  • boolean execute:执行一条 SOL 语句。
  • ResultSet executeQuery:执行一条SOL语句,并返回结果集 ResultSet对象。
  • int executeUpdate:执行给定 SQL 语句,该语句可能为 INSERT、UPDATEDELETE或DDL语句等。
  • ResultSet getResultSet:获取当前结果集 ResultSet 对象。
  • ResultSet getGeneratedKeys:获取当前操作自增生成的主键
  • boolean isClosed:获取是否已关闭了此 Statement 对象。
  • void close:关闭 Statement 对象,释放相关的资源。
  • Connection getConnection:获取生成此 Statement 对象的 Connection 对象

上述方法主要用来完成执行 SOL 语句、获取SOL 语执行结果等功能。

二、ServiceLoader 技术原理

2.1 简单介绍

ServiceLoader是jdk6里面引进的一个特性。它用来实现SPI(Service Provider Interface),一种服务发现机制,很多框架用它来做来做服务的扩展发现。 系统里抽象的各个模块一般会有很多种不同的实现,如JDBC、日志等。通常模块之间我们均是基于接口进行编程,而不是对实现类进行硬编码。这时候就需要一种动态替换发现的机制,即在运行时动态地给接口添加实现,而不需要在程序中指明。

引用自JDK文档对于java.util.ServiceLoader的描述:

服务是一个熟知的接口和类(通常为抽象类)集合。服务提供者是服务的特定实现。提供者中的类通常实现接口,并子类化在服务本身中定义的子类。服务提供者可以以扩展的形式安装在 Java 平台的实现中,也就是将 jar 文件放入任意常用的扩展目录中。也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用。

为了加载,服务由单个类型表示,也就是单个接口或抽象类。一个给定服务的提供者包含一个或多个具体类,这些类扩展了此服务类型,具有特定于提供者的数据和代码。提供者类通常不是整个提供者本身而是一个代理,它包含足够的信息来决定提供者是否能满足特定请求,还包含可以根据需要创建实际提供者的代码。提供者类的详细信息高度特定于服务;任何单个类或接口都不能统一它们,因此这里没有定义任何这种类型。此设施唯一强制要求的是,提供者类必须具有不带参数的构造方法,以便它们可以在加载中被实例化。

当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录里同时创建一个以服务接口命名的该服务接口具体实现类文件。当外部程序装配该模块时,通过该jar包META-INF/services/里的配置文件找到具体的实现类名,从而完成模块的注入,而不需要在代码里定制。

在JDBC中使用了ServiceLoader对不同数据库驱动进行加载。

在这里插入图片描述

DriverManager通过ServiceLoader加载数据库驱动:

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

2.2 ServiceLoader 使用小栗子

首先定义一个SPIService接口

package com.study;

public interface SPIService {
 
  void excute();
}

再定义两个实现类

package com.study.impl;
import com.study.SPIService;

public class SPIServiceImpl1 implements SPIService {
 
  @Override
  public void excute() {
    System.out.println("SPIServiceImpl1");
  }
}

public class SPIServiceImpl2 implements SPIService {
 
  @Override
  public void excute() {
    System.out.println("SPIServiceImpl2");
  }
}

在ClassPath路径下配置添加文件,META-INF/services/com.study.SPIService,文件名为接口的全限定类名。在配置文件中加入两个实现类的全限定类名。

com.study.impl.SPIServiceImpl1
com.study.impl.SPIServiceImpl2

写一个测试类SPITest.java

public class SPITest {
  public static void main(String[] args) {
    ServiceLoader<SPIService> loaders = ServiceLoader.load(SPIService.class);
    Iterator<SPIService> it = loaders.iterator();
    while (it.hasNext()) {
      SPIService spiSer= it.next();
      spiSer.excute();
    }
  }
}

运行结果:

SPIServiceImpl1
SPIServiceImpl2

ServiceLoader的load方法将在META-INF/services/com.study.SPIService中配置的子类都进行了加载。

2.3 ServiceLoader的核心源码分析

public final class ServiceLoader<S> implements Iterable<S>{
    // 需要加载的资源的配置文件路径
    private static final String PREFIX = "META-INF/services/";
    // 加载的服务类或接口
    private Class<S> service;
    // 类加载时用到的类加载器
    private ClassLoader loader;
    // 基于实例的已加载的顺序缓存类,其中Key为实现类的全限定类名
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // "懒查找"迭代器,ServiceLoader的核心
    private LazyIterator lookupIterator;
 
 
    public void reload() {
        // 清空缓存
        providers.clear();
        // 构造LazyIterator实例
        lookupIterator = new LazyIterator(service, loader);
    }
     
    // 私有构造方法
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = svc;
        loader = cl;
        reload();
    }
}

ServiceLoader只有一个私有的构造函数,也就是它不能通过构造函数实例化,但是要实例化ServiceLoader必须依赖于它的静态方法调用私有构造去完成实例化操作。

来看ServiceLoader的提供的静态方法,这几个方法都可以用于构造ServiceLoader的实例。

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取当前线程的线程上下文类加载器实例,确保通过此classLoader也能加载到项目中的资源文件
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
 
 
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}
 
 
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
        prev = cl;
        cl = cl.getParent();
    }
    return ServiceLoader.load(service, prev);
}

load(Class<S> service, ClassLoader loader)是典型的静态工厂方法,直接调用ServiceLoader的私有构造器进行实例化,除了需要指定加载类的目标类型,还需要传入类加载器的实例。load(Class<S>service)实际上也是委托到load(Class<S> service, ClassLoader loader),不过它使用的类加载器指定为线程上下文类加载器,一般情况下线程上下文类加载器获取到的就是应用类加载器(系统类加载器)。loadInstalled(Class<S> service)方法又看出了"双亲委派模型"的影子,它指定类加载器为最顶层的启动类加载器,最后也是委托到load(Class<S> service, ClassLoader loader)

这里重点关注为什么类加载器使用线程上下文类加载器?以JDBC加载MySQL驱动举例。

在这里插入图片描述

Java类加载的过程通常是遵循双亲委派模型的。但是对于SPI接口实现类的加载就需要破坏双亲委派模型。

首先java.sql.DriverManager是由启动类加载器加载的,创建真正的Dirver对象时需要使用到mysql提供的实现:com.mysql.jdbc.Dirver,即要初始化该类。但是启动类加载器加载DirverManager的时候,使用到了启动类加载器无法加载的类,这时候就需要由系统类加载器来加载。com.mysql.jdbc.Dirver通常放在类路径下的(其实不一定)。到这里和线程上下文类加载器没由任何关系。在DriverManager中使用系统类加载的时候,可以直接使用静态方法ClassLoader.getSystemClassLoader(),但是这种情况的前提是com.mysql.jdbc.Dirver类在类路径下。如果不在类路径下,而且在系统环境中有其他的类加载器,在通过其他类加载器可能出现无法正确加载扩展点的情况。比如某个类的字节码是在数据库中存储,这时我们需要自定义一个类加载器去加载它,这个类加载器会告诉DriverManager去我们指定放的地方取。因此Thread.currentThread().setContextClassLoader(自定义加载器/默认是系统类加载器); 这个就是线程上下文类加载器起到的介质作用。线程上下文中默认放的是系统类加载器。

ServiceLoader实现了Iterable接口。

public Iterator<S> iterator() {
    // Iterator的匿名实现
    return new Iterator<S>() {
        // 基于实例的已加载的顺序缓存类Map的Entry迭代器实例
        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
         
        // 先从缓存中判断是否有下一个实例,否则通过懒加载迭代器LazyIterator去判断是否存在下一个实例
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
         
        // 如果缓存中判断是否有下一个实例,如果有则从缓存中的值直接返回,否则通过懒加载迭代器LazyIterator获取下一个实例
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
         
        // 不支持移除
        public void remove() {
            throw new UnsupportedOperationException();
        }
 
    };
}

LazyIterator本身也是一个Iterator接口的实现,它是ServiceLoader的一个私有内部类。

private class LazyIterator implements Iterator<S>
{
 
    Class<S> service;
    ClassLoader loader;
    // 加载的资源的URL集合
    Enumeration<URL> configs = null;
    // 所有需要加载的实现类的全限定类名的集合
    Iterator<String> pending = null;
    // 下一个需要加载的实现类的全限定类名
    String nextName = null;
 
    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }
 
    public boolean hasNext() {
        // 如果下一个需要加载的实现类的全限定类名不为null,则说明资源中存在内容
        if (nextName != null) {
            return true;
        }
        // 如果加载的资源的URL集合为null则尝试进行加载
        if (configs == null) {
            try {
                // 资源的名称,META-INF/services/ + '需要加载的类的全限定类名'
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    // 从ClassPath加载资源
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        // 从资源中解析出需要加载的所有实现类的全限定类名
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        // 获取下一个需要加载的实现类的全限定类名
        nextName = pending.next();
        return true;
    }
 
    public S next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        String cn = nextName;
        nextName = null;
        try {
            // 反射构造Class<S>实例,同时进行初始化,并且强制转化为对应的类型的实例
            S p = service.cast(Class.forName(cn, true, loader).newInstance());
            // 添加进缓存,Key为实现类的全限定类名,Value为实现类的实例
            providers.put(cn, p);
            return p;
        } catch (ClassNotFoundException x) {
            fail(service, "Provider " + cn + " not found");
        } catch (Throwable x) {
            fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
        }
        throw new Error();          // This cannot happen
    }
 
    public void remove() {
        throw new UnsupportedOperationException();
    }
 
}

2.4 ServiceLoader总结

JDK提供了一种帮第三方实现者加载服务的便捷方式,如JDBC、日志等,第三方实现者需要遵循约定把具体实现的类名放在/META-INF里。当JDK启动时会去扫描所有jar包里符合约定的类名,再调用forName进行加载,如果JDK的ClassLoader无法加载,就使用当前执行线程的线程上下文类加载器。 但是在通过SPI查找服务的具体实现时,会遍历所有的实现进行实例化,并在循环中找到需要的具体实现。

三、JDBC 技术原理

JDBC,即Java数据库连接,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC制定了统一访问各类关系数据库的标准接口,为各个数据库厂商提供了标准接口的实现。

3.1 JDBC操作数据库的步骤

1.加载数据库驱动; 2.建立数据库连接; 3.创建数据库操作对象; 4.定义操作的SQL语句并执行; 5.获取并操作结果集; 6.关闭资源:关闭顺序是结果集–>数据库操作对象–>连接。

3.2 JDBC API类图

在这里插入图片描述

在Java1.6之前我们加载注册数据库驱动以及获得数据库连接(如mysql)的代码步骤:

(1)Class.forName(“com.mysql.jdbc.Driver”);

(2)Connection con = DriverManager.getConnection(url, user, password);

但是在Java1.6开始,由于SPI机制的出现,不需要显示的调用Class.forName(“com.mysql.jdbc.Driver”);,就可以完成所有步骤。

3.3 JDBC核心源码分析

3.3.1 Driver

每个数据库驱动都必须实现的接口。

public interface Driver {
    // 获取Connection,参数包括数据库的url和info,其中info至少包含user和password
    Connection connect(String url, java.util.Properties info)
        throws SQLException;
 
    // 检测驱动是否是可以打开的连接
    boolean acceptsURL(String url) throws SQLException;
 
 
    // 获得驱动的属性
    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;
     
    // 获得驱动的主要版本
    int getMajorVersion();
 
 
    // 获得驱动的次要版本
    int getMinorVersion();
 
 
    // 判断是否是一个真正的JDBC
    boolean jdbcCompliant();
 
 
    //------------------------- JDBC 4.1 -----------------------------------
 
 
    // 返回父日志
    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

3.3.2 DriverManager

管理一组JDBC驱动的基本服务。它的方法全是静态方法。

public class DriverManager {
    // 已经注册的JDBC驱动列表
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
 
 
    // 防止类被初始化
    private DriverManager(){}
 
 
    // 静态代码块,初始化加载驱动
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    ......
 
}

现在我们以下述代码的方式获取数据库连接进行分析:

Connection con = DriverManager.getConnection(url, user, password);

为什么通过这句代码就可以完成数据库驱动的加载初始化呢。我们知道在调用一个类的静态方法时(getConnection())时会首先初始化该类,进而执行其静态代码块。


// 静态代码块,初始化加载驱动
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

静态代码块中只做了一个事情就是调用loadInitialDrivers()方法加载初始化驱动,该方法利用了ServiceLoader加载驱动,


// 初始化加载驱动核心代码
private static void loadInitialDrivers() {
    String drivers;
    try {
        // AccessController.doPrivileged()是让不受信任的代码能够通过具有权限的中间方法调用需要权限的方法(如System.getProperty())。
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                // 获取系统的jdbc.drivers属性
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
 
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // 通过ServiceLoader加载所有驱动,以便可以实例化
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator driversIterator = loadedDrivers.iterator();
 
            try{
                // 构造实例并进行初始化
                while(driversIterator.hasNext()) {
                    // driversIterator.next(),驱动加载的核心
                    println(" Loading done by the java.util.ServiceLoader :  "+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);
        }
    }
}

DriverManager加载Driver的步骤顺序依次是:

  • 通过SPI方式,读取 META-INF/services 下文件中的类名,使用线程上下文类加载器加载;
  • 通过System.getProperty(“jdbc.drivers”)获取设置,然后通过系统类加载器加载。

driversIterator.next()中通过Class.forName(cn, true, loader).newInstance();对驱动进行初始化并实例化,放入缓存列表中。通常Class.forName(cn, true, loader)就可以触发初始化执行静态代码块。但是从mysql的官方文档中可以看到关于newInstance的相应解释:

The newInstance() call is a work around for some broken Java implementations. For some JVMs doing a forName does not call the static initializer - but creating a new instance does force the static initializer to be called.

这里,在初始化数据库驱动的时候会执行驱动类其各自的静态代码块,比如com.mysql.jdbc.Driver如下:


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

可以看出MySQL的驱动Driver实现了Java的Driver,其中只有一个静态代码块,目的是注册驱动。通过Class.forName()初始化的时候执行静态代码化对驱动进行注册。静态代码块中直接调用了DriverManager的静态方法registerDriver()对驱动进行注册。


// 注册驱动
public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {
 
    // 判断driver是否已经被注册到registeredDrivers,没有就注册进去
    if(driver != null) {
        // 将driver封装成DriverInfo【包装类】进行注册
        registeredDrivers.addIfAbsent(new DriverInfo(driver));
    } else {
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);
}

至此,当获取一个数据库的连接时,会将全部的驱动进行注册。

// 注销驱动
public static synchronized void deregisterDriver(Driver driver) throws SQLException {
    if (driver == null) {
        return;
    }
    // 【native方法】获得调用此方法代码的类加载器
    ClassLoader callerCL = DriverManager.getCallerClassLoader();
    println("DriverManager.deregisterDriver: " + driver);
 
    DriverInfo aDriver = new DriverInfo(driver);
    if(registeredDrivers.contains(aDriver)) {
        // 判断调用方是否有加载驱动程序的权限
        if (isDriverAllowed(driver, callerCL)) {
             registeredDrivers.remove(aDriver);
        } else {
            throw new SecurityException();
        }
    } else {
        println("couldn't find driver to unload");
    }
}
 
// 判断调用方的类加载器是否有加载驱动程序的权限
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
    boolean result = false;
    if(driver != null) {
        Class<?> aClass = null;
        try {
            // 传入的classLoader为调用getConnetction的当前类加载器,从中寻找driver的Class对象
            aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
        } catch (Exception ex) {
            result = false;
        }
        // 只有同一个类加载器中的Class使用==比较时才会相等,此处就是校验用户注册Driver时该Driver所属的类加载器与调用时的是否同一个
         result = ( aClass == driver.getClass() ) ? true : false;
    }
 
    return result;
}

接着,客户便可以通过DriverManager.getConnection()方法获得数据库连接,DriverManager提供了3个对外的getConnection方法。

// 参数包括数据库的url和info,其中info至少包含user和password
public static Connection getConnection(String url,
    java.util.Properties info) throws SQLException {
 
    ClassLoader callerCL = DriverManager.getCallerClassLoader();
 
    return (getConnection(url, info, callerCL));
}
 
 
// 参数包括数据库的url
public static Connection getConnection(String url)
    throws SQLException {
 
    java.util.Properties info = new java.util.Properties();
 
    ClassLoader callerCL = DriverManager.getCallerClassLoader();
 
    return (getConnection(url, info, callerCL));
}
 
 
// 参数包括数据库的url,user和password
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();
 
    ClassLoader callerCL = DriverManager.getCallerClassLoader();
 
    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }
 
    return (getConnection(url, info, callerCL));
}
 
 
//private方法,上述三个方法实际调用的都是下面的getConnection()方法
private static Connection getConnection(String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
    // 如果类加载器为null,如果为空则通过Thread.currentThread().getContextClassLoader()去加载,以保证rt.jar包外的驱动可以被加载
    synchronized(DriverManager.class) {
      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;
     
    // 遍历已经注册的JDBC驱动列表尝试获得一个连接
    for(DriverInfo aDriver : registeredDrivers) {
        // 如果调用方没有权限加载这个驱动则跳过
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                // 真正的获取connection的方法,通过Driver接口中的connect方法实现
                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 (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");
}

DriverManager中还有getDriver()和getDrivers()方法,用于返回特定的驱动以及所有调用方有权限加载的驱动列表。

3.3.3 Collection

代表与数据库的连接,提供Statement对象的创建以及数据库事务管理功能。

public interface Connection  extends Wrapper, AutoCloseable {
    // 创建Statement对象
    Statement createStatement() throws SQLException;
    // 创建PreparedStatement对象
    PreparedStatement prepareStatement(String sql) throws SQLException;
    // 创建CallableStatement对象
    CallableStatement prepareCall(String sql) throws SQLException;
    // 将SQL转换为本地执行SQL
    String nativeSQL(String sql) throws SQLException;
    // 设置自动提交
    void setAutoCommit(boolean autoCommit) throws SQLException;
    // 获得自动提交状态
    boolean getAutoCommit() throws SQLException;
    // 提交事务
    void commit() throws SQLException;
    // 回滚事务
    void rollback() throws SQLException;
    // 关闭连接
    void close() throws SQLException;
    // 判断连接是否关闭
    boolean isClosed() throws SQLException;
    ......
    // 还有很多新增的方法
}

3.3.4 Statement、PreparedStatement 和CallableStatement

  • Statement:执行静态SQL
  • PreparedStatement :执行预编译SQL
  • CallableStatement:执行存储过程

这里举例PreparedStatement的几个重要方法:

public interface PreparedStatement extends Statement {
    // 执行SQL查询语句,返回单个结果集
    ResultSet executeQuery() throws SQLException;
    // 执行SQL更新语句,如INSERT,UPDATE,DELETE
    int executeUpdate() throws SQLException;
    // 设置参数为NULL,必须指定参数的类型
    void setNull(int parameterIndex, int sqlType) throws SQLException;
    // 设置String类型的参数
    void setString(int parameterIndex, String x) throws SQLException;
    // 清空参数
    void clearParameters() throws SQLException;
    // 执行SQL组合语句,返回多个结果集
    boolean execute() throws SQLException;
}

3.3.5 ResultSet

ResultSet是数据库查询结果返回的一种对象,是一个存储查询结果的对象,但结果集并不仅仅具有存储的功能,还具有操纵数据的功能,可以完成对数据的更新等。

public interface ResultSet extends Wrapper, AutoCloseable {
    // 判断是否有下一个值
    boolean next() throws SQLException;
    // 关闭
    void close() throws SQLException;
    // 是否为空
    boolean wasNull() throws SQLException;
    // 获得第几columnIndex列的String类型数据
    String getString(int columnIndex) throws SQLException;
    .......
    // 还有很多新增的方法
}
上次编辑于:
贡献者: xuliang,诗人都藏在水底