Mybatis源码学习记录(数据源篇)

前言

我们知道数据源是一个非常重要的基础组件,它的性能直接关系到数据持久层的性能,尽管市面上有很多第三方数据源组件,比如阿里的druid,Apache的DBCPc3p0,不管是哪种数据源,最终都需要实现javax.sql.DataSource接口

Mybatis框架自身也提供了数据源的实现,分别是PooledDataSourceUnpooledDataSource

学习Mybatis提供的数据源实现之前我们先看下javax.sql.DataSource接口,通过它来直观的了解作为一个数据源应该具有哪些功能?

DataSource接口

public interface DataSource extends CommonDataSource, Wrapper {
    
    // 尝试通过数据源建立一个连接
    Connection getConnection() throws SQLException;
    
    // 重载的方法,传入用户名以及密码
    Connection getConnection(String username, String password) throws SQLException;
}

通过DataSource接口我们可以知道,数据源本身就是就算是一个连接工厂,当你需要连接时,就问工厂要(调用getConnection方法)一个就行了

一般DataSource接口是由数据库驱动商实现,且基本上会有三种实现形式

  • 基础实现:每次需要连接对象都是单纯的生产一个标准的新连接对象返回

  • 连接池实现:生产的连接对象自动加入连接池中以便复用连接对象,该实现方式需要与一个中间层连接管理器合作

  • 分布式事务方式实现:此种实现较为复杂,本文不会涉及

Mybaits中的数据源实现就是针对以上前两点实现的,UnpooledDataSource对应基础实现方式,而PooledDataSource针对连接池方式的实现,下面我们直接看源码

UnpooledDataSource

非池化数据源,即每次都是创建一个新的数据库连接对象返回

public class UnpooledDataSource implements DataSource {
  
  private ClassLoader driverClassLoader;
  // 驱动相关属性配置,在下面的工厂方法模式的setProperties方法中会将该属性赋值上(如果有驱动相关属性配置的化)
  private Properties driverProperties;
  // 已注册到驱动管理器的驱动集合,不同的数据库对应不同的驱动,比如mysql驱动,oracle驱动等...
  private static Map registeredDrivers = new ConcurrentHashMap();
  // 数据库驱动名称
  private String driver;
  // 数据库url
  private String url;
  // 用户名
  private String username;
  // 密码
  private String password;
  // 自动提交
  private Boolean autoCommit;
  // 默认隔离级别
  private Integer defaultTransactionIsolationLevel;

  static {
    // 利用static块在UnpooledDataSource类初始化阶段,将已经注册到驱动管理器中的驱动考一份存入registeredDrivers集合中,以驱动的className为key保存
    // 实际上基于SPI,只要classpath下有驱动的jar包,这里通过DriverManager.getDrivers()方法就可以直接获取到注册号的驱动了,因为这里会触发DriverManager类加载,并执行static块的loadInitialDrivers方法,根据ServiceLoader去加载数据库驱动jar中META-INF/services/下指定的文件(java.sql.Driver)
    Enumeration drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }
  
  // 省略各种重载的构造函数...
  
  // 省略各种属性的getter/setter方法...
  
  @Override
  public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
  }

  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return doGetConnection(username, password);
  }
  
  // getConnection方法都会转到doGetConnection方法上
  private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    if (username != null) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    // 最终调用的是下面的重载方法
    return doGetConnection(props);
  }

  private Connection doGetConnection(Properties properties) throws SQLException {
    // 1. 初始化驱动
    initializeDriver();
    // 2. 通过驱动管理器获取连接,注意如果1.中没有合适的驱动注册到驱动管理器中,这里的getConnection方法内,根据指定的url前缀(如:`jdbc:mysql:xxx`)就找不到合适的JDBC驱动,也就获取不到连接对象
    Connection connection = DriverManager.getConnection(url, properties);
    // 3. 配置连接对象
    configureConnection(connection);
    // 4. 返回连接对象
    return connection;
  }
  
  // 只有不符合JDBC Driver SPI的驱动才可能会进入if内
  private synchronized void initializeDriver() throws SQLException {
    // 假设这里传入的driver为某种不符号SPI的驱动商驱动,第一次会进入if内
    if (!registeredDrivers.containsKey(driver)) {
      Class driverType;
      try {
        if (driverClassLoader != null) {
          // 如果driverClassLoader在配置文件中配置了,就进入这里
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          // 没有配置单独的driverClassLoader,则执行这里,加载driver驱动类
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        // 驱动类加载完成,开始实例化驱动
        Driver driverInstance = (Driver)driverType.newInstance();
        // 注册驱动类,注意这里是一个静态代理类
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        // 存入已注册驱动集合中
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }
  
  private void configureConnection(Connection conn) throws SQLException {
    // 配置连接对象,就是在已经获取到的连接上配置自动提交和默认事务的隔离级别
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }
  
  // 静态内部类,Driver的静态代理类
  private static class DriverProxy implements Driver {
    private Driver driver;

    DriverProxy(Driver d) {
      this.driver = d;
    }

    @Override
    public boolean acceptsURL(String u) throws SQLException {
      return this.driver.acceptsURL(u);
    }

    @Override
    public Connection connect(String u, Properties p) throws SQLException {
      return this.driver.connect(u, p);
    }

    @Override
    public int getMajorVersion() {
      return this.driver.getMajorVersion();
    }

    @Override
    public int getMinorVersion() {
      return this.driver.getMinorVersion();
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
      return this.driver.getPropertyInfo(u, p);
    }

    @Override
    public boolean jdbcCompliant() {
      return this.driver.jdbcCompliant();
    }

    // @Override only valid jdk7+
    public Logger getParentLogger() {
      return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }
  }
Mybatis源码学习记录(数据源篇)_第1张图片
image.png
Mybatis源码学习记录(数据源篇)_第2张图片
image.png
Mybatis源码学习记录(数据源篇)_第3张图片
image.png

以上就是非池化数据源的全部源码,代码比较简单,对于熟悉JDBC编程的同学几乎没有难度,此外我们额外提一下关于该非池化数据源合的创建过程,以上源码我们省略了其构造函数,实际上Mybatis采用了工厂方法模式来创建非池化以及池化数据源

工厂方法模式

Mybatis定义了一个DataSourceFactory接口来作为工厂方法模式中的工厂接口

public interface DataSourceFactory {

  // 为DataSource设置相关属性
  void setProperties(Properties props);
  // 获取数据源对象
  DataSource getDataSource();
}

那既然采用了工厂方法模式来创建不同的数据源实例,那么自然针对不同的产品(数据源)就会存在对应的工厂实现类,针对UnpooledDataSource产品的工厂类实现就是UnpooledDataSourceFactory

利用工厂方法模式,Mybatis就可以直接面向工厂接口以及产品接口编程,而不用去管具体的工厂类和具体的产品类,与之带来的优点就是开闭原则对扩展开放对修改关闭

如果我们需要增加一种数据源(产品,比如增加一种第三方数据源),Mybatis只要再额外增加一种对应的工厂类就可以了

UnpooledDataSourceFactory

public class UnpooledDataSourceFactory implements DataSourceFactory {
  // 以“driver”开头的属性
  private static final String DRIVER_PROPERTY_PREFIX = "driver.";
  private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();

  protected DataSource dataSource;

  // 利用工厂的构造函数直接new一个数据源对象出来
  public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }

  @Override
  public void setProperties(Properties properties) {
    // 抽出属性配置中的驱动相关配置,并保存到driverProperties中
    Properties driverProperties = new Properties();
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    for (Object key : properties.keySet()) {
      String propertyName = (String) key;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        // 获取驱动相关属性名(去除前缀driver.的),保存
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) {
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        // dataSource相关的配置属性,直接set进去
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      // 最后,如果有驱动相关的属性配置的化,将其设置到dataSource对象的driverProperties属性上去
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

  @Override
  public DataSource getDataSource() {
    return dataSource;
  }
}

至此我们解析了整个Mybatis提供的非池化数据源的创建过程,可见底层还是调用了Java JDBC相关的代码的

总结

  1. 调用非池化数据源工厂类UnpooledDataSourceFactory的构造函数,创建工厂对象
  2. 工厂对象的构造函数中调用非池化数据源类UnpooledDataSource的构造函数,创建非池化数据源对象
  3. 调用非池化数据源工厂对象setProperties(...)方法,根据入参Properties,设置非池化数据源对象的基本属性(如urlusernamepassword等)以及赋值driverProperties属性
  4. 最后就可以调用非池化数据源工厂对象的getDataSource方法获取数据源对象实例啦

下文会分析Mybatis提供的另一种数据源实现,即池化数据源PooledDataSource的源码

最后附上Mybatis针对数据源的配置文件写法,帮助同学回忆一下哈

  
  
  
      
      
          
              
             
                
                  
                  
                  
                  
              
          
      
      
      
      
          
      

如果是mysql驱动的化上面的value值就是com.mysql.jdbc.Driver,另外url属性值就是类似这样的jdbc:mysql://xxx:3306/wilson?serverTimezone=UTC,另外一点,以上配置没有单独配置driverClassLoader驱动类加载器

最后关于JDBC的Driver驱动类加载可以看看xwiz大神的这篇文章

你可能感兴趣的:(Mybatis源码学习记录(数据源篇))