MyBatis源码解析(五)——DataSource数据源模块之非池型数据源

时间:2023-12-15 12:15:14

原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675633.html

1 回顾

  上一篇中我解说了数据源接口DataSource与数据源工厂接口DataSourceFactory,这二者是MyBatis数据源模块的基础,包括本文中的非池型非池型数据源(UnpooledDataSource)和之后的池型数据源(PooledDataSource)、托管型数据源(JndiDataSourceFactory)都是在这两个接口上产生的。

  本文解读一下MyBatis中的非池型数据源,这是基础的数据源,之后要解读的池型数据源又是以此数据源为基础产生的。

2 非池型数据源及其工厂

2.1 非池型数据源工厂:UnpooledDataSourceFactory

  该数据源工厂实现了DataSourceFactory接口,源码如下:

 1 package org.apache.ibatis.datasource.unpooled;
2 import java.util.Iterator;
3 import java.util.Properties;
4 import java.util.Set;
5 import javax.sql.DataSource;
6 import org.apache.ibatis.datasource.DataSourceException;
7 import org.apache.ibatis.datasource.DataSourceFactory;
8 import org.apache.ibatis.reflection.MetaObject;
9 import org.apache.ibatis.reflection.SystemMetaObject;
10
11 public class UnpooledDataSourceFactory implements DataSourceFactory{
12 private static final String DRIVER_PROPERTY_PREFIX = "driver.";//属性前缀
13 private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();//属性前缀的长度
14 protected DataSource dataSource;
15 /*
16 * 在工厂的构造器中创建具体数据源的实例并赋值,这将用于供getDataSource()方法获取数据源实例
17 */
18 public UnpooledDataSourceFactory()
19 {
20 this.dataSource = new UnpooledDataSource();
21 }
22 /*
23 * 设置数据源驱动器属性,有两种情况
24 */
25 public void setProperties(Properties properties)
26 {
27 Properties driverProperties = new Properties();
28 MetaObject metaDataSource = SystemMetaObject.forObject(this.dataSource);
29 for (Iterator i$ = properties.keySet().iterator(); i$.hasNext(); ) { Object key = i$.next();
30 String propertyName = (String)key;
31 if (propertyName.startsWith("driver.")) {//第一种情况,以driver.开头的属性名
32 String value = properties.getProperty(propertyName);
33 driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
34 } else if (metaDataSource.hasSetter(propertyName)) {//元对象中拥有针对属性名的set设置方法
35 String value = (String)properties.get(propertyName);
36 Object convertedValue = convertValue(metaDataSource, propertyName, value);
37 metaDataSource.setValue(propertyName, convertedValue);
38 } else {
39 throw new DataSourceException("Unknown DataSource property: " + propertyName);
40 }
41 }
42 if (driverProperties.size() > 0)
43 metaDataSource.setValue("driverProperties", driverProperties);
44 }
45
46 public DataSource getDataSource()
47 {
48 return this.dataSource;
49 }
50 /*
51 * 值类型强转方法
52 */
53 private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
54 Object convertedValue = value;
55 Class targetType = metaDataSource.getSetterType(propertyName);
56 if ((targetType == Integer.class) || (targetType == Integer.TYPE))
57 convertedValue = Integer.valueOf(value);
58 else if ((targetType == Long.class) || (targetType == Long.TYPE))
59 convertedValue = Long.valueOf(value);
60 else if ((targetType == Boolean.class) || (targetType == Boolean.TYPE)) {
61 convertedValue = Boolean.valueOf(value);
62 }
63 return convertedValue;
64 }

  以上源码中,数据源工厂可通过无参构造器创建具体的数据源实例,然后可通过getDataSource()方法,来获取之前在构造器中创建的数据源实例,getDataSource()方法是对外的。

  该工厂类的重点是设置属性的方法:setProperties(Properties properties),我们继续简单解析:

  第27行:创建局部变量driverProperties,用于存放参数属性列表中经过过滤的属性

  第28行:在我们创建该工厂实例的基础上(即字段dataSource已被赋值的基础上),调用MyBatis提供的元对象MetaObject来生成针对dataSource实例的元实例metaDataSource。

    解析:元实例,可以看成是针对具体实例的一批装饰器,在包装器核心是具体实例,外围是多个装饰器(包装器),用于增强功能。

  第29行:遍历获取的属性列表Properties(参数)

  第31-33行:针对参数列表中属性名是以driver.为前缀的属性,获取其值,保存在driverProperties中以备后用。

  第34-37行:针对参数列表中属性名在dataSource实例中存在set方法的属性,获取其值,将值经过必要的转换后,将其保存到metaDataSource元实例中

    这个其实是最常使用的,我们在配置文件中配置的driver、url、username、password四个参数,全部都是以此种方式保存到dataSource实例中的。

  第42-44行:最后对driverProperties变量进行null判断,即判断有无通过前缀方式配置的属性,如果有则将这些配置同样保存到metaDataSource元实例中

  这样到最后其实所有的配置信息都保存到了metaDataSource元实例中,这样说其实不对,其实最后通过MetaObject的setValue()方法,将所有这些经过过滤的属性设置保存到了元实例的核心:dataSource中了(内部原理,荣后禀明),对应于UnpolledDataSource中的driver、url、username、password、driverProperties这五个字段(见下文)。

  总结来看看:数据源工厂的目的就是讲配置文件中的配置内容配置到通过自己的构造器创建的具体数据源实例中,再使用getDataSource()方法返回给调用方。

2.2 非池型数据源:UnpolledDataSource

  该类实现了DataSource数据源接口,实现了其中的所有抽象方法。

  非池型是相对池型而言的,池型数据源统筹管理着一个数据库连接池,在这个池中拥有指定数量的数据库连接实例connection可供使用,其内部采用一定的规则指定数据连接对象的使用、创建、销毁规则,来为多线程数据库访问提供及时有效的数据库连接。

  非池型数据源,即保持有一个数据库连接实例的数据源。下面我们来看看这种数据源的实现方式:

  首先我们来看看字段:

1   private ClassLoader driverClassLoader;
2 private Properties driverProperties;
3 private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
4 private String driver;
5 private String url;
6 private String username;
7 private String password;
8 private Boolean autoCommit;
9 private Integer defaultTransactionIsolationLevel;

  我们一一进行解析:

    ClassLoader driverClassLoader:这个是数据库的驱动类加载器:目的正是用于从磁盘中加载数据库驱动类到内存

    Properties driverProperties:驱动器属性,这个用于保存我们手动设置的数据库驱动器属性,如果我们不进行设置,则会使用默认值。这个属性设置的键一般都会是以“driver.”为前缀。

    Map<String,Driver> registeredDrivers = new ConcurrentHashMap<String,Driver>():表示数据库驱动注册器,其内部保存着所有已注册的数据库驱动类实例,这个字段是static修饰的,表示在数据源类加载的时候就会创建,这个时候创建的其实是个空集合。再者使用ConcurrentHashMap集合,这是一个线程安全的键值对型集合,它几乎可以与HashTable通用(二者都是线程安全的集合类)。这个静态字段其实是和之后要提到的一个静态代码块配合工作的(容后介绍)。

    String driver:数据库驱动类名

    String url:数据库服务器URL地址

    String username:数据库服务器连接用户名

    String password:数据库服务器连接密码

    Boolean autoCommit:是否自动提交,这是一个逻辑值,真表示使用自动提交,假表示关闭自动提交。上一文中提到了自动提交,其实那里的自动提交值就是在这里设置的。

    Integer defaultTransactionIsolationLevel:表示默认的事务级别

静态代码块:

1   static {
2 Enumeration<Driver> drivers = DriverManager.getDrivers();
3 while (drivers.hasMoreElements()) {
4 Driver driver = drivers.nextElement();
5 registeredDrivers.put(driver.getClass().getName(), driver);
6 }
7 }

  这个静态块中的内容需要结合之前的静态字段registeredDrivers来进行解析。

  DriverManager是JDK提供的驱动器管理类,在该类加载时会执行其中的静态块,静态块中调用了一个loadInitialDrivers()方法,这是一个加载原始驱动器的方法,他将JDK中的原始配置jdbc.drivers中的驱动器名表示的驱动类加载到内存,其会调用本地方法进行驱动类进行加载,然后调用DriverManager中的registerDriver()方法将驱动类实例一一保存到DriverManager中的registeredDrivers集合中。

  我们这里调用DriverManager中getDrivers()方法,将会获取DriverManager中在集合registeredDrivers中的保存的驱动器实例,在获取的时候会进行类加载验证,验证的目的是确保使用本类加载器获取的驱动器类与在registeredDrivers中保存的对应驱动类实例的类是同一类型(==)。最后获取到的是驱动实例的枚举。

  对这个枚举进行遍历,将其中所有驱动器实例保存到我们当前的UnpooledDataSource中的静态集合registeredDrivers(区别于DriverManager中的同名集合,二者类型都不同)中。

  这样保证在该类加载的时候就将默认的驱动器实例加载到静态集合中以备用。该静态代码块的作用就是为registeredDrivers集合赋值。

下面我们来看看实现的DataSource中的构造方法:

 1   public UnpooledDataSource() {
2 }
3 public UnpooledDataSource(String driver, String url, String username, String password) {
4 this.driver = driver;
5 this.url = url;
6 this.username = username;
7 this.password = password;
8 }
9 public UnpooledDataSource(String driver, String url, Properties driverProperties) {
10 this.driver = driver;
11 this.url = url;
12 this.driverProperties = driverProperties;
13 }
14 public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
15 this.driverClassLoader = driverClassLoader;
16 this.driver = driver;
17 this.url = url;
18 this.username = username;
19 this.password = password;
20 }
21 public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
22 this.driverClassLoader = driverClassLoader;
23 this.driver = driver;
24 this.url = url;
25 this.driverProperties = driverProperties;
26 }

  可见UnpooledDataSource提供了五个构造器,第一个为无参构造器,其余皆带有赋值功能:以驱动器类全限定名driver、数据库服务器地址url、数据库登录用户名及密码为参数是最为常用的数据源构建方式,也可以将用户名与密码整合为以驱动器参数driverProperties的形式传参,甚至还可以指定驱动类的类加载器driverClassLoader。

  这里最常用的还是无参构造器,这个构造器在数据源工厂的无参构造器中被调用,用于创建一个空的UnpolledDataSource实例,然后使用工厂类中的setProperties()方法,为这个空实例中的各个字段进行赋值(采用上面提到的第二种方式进行读取配置信息并保存到实例中),从而创建一个饱满的实例,

  下面看看实现自接口的方法

 1   public void setLoginTimeout(int loginTimeout) throws SQLException
2 {
3 DriverManager.setLoginTimeout(loginTimeout);
4 }
5 public int getLoginTimeout() throws SQLException
6 {
7 return DriverManager.getLoginTimeout();
8 }
9 public void setLogWriter(PrintWriter logWriter) throws SQLException
10 {
11 DriverManager.setLogWriter(logWriter);
12 }
13 public PrintWriter getLogWriter() throws SQLException
14 {
15 return DriverManager.getLogWriter();
16 }
17 public Logger getParentLogger()
18  {
19  return Logger.getLogger("global");
20 }

  这五个方法继承自CommonDataSource通用数据源接口,其中各方法的意义如下:

    setLoginTimeout():表示设置数据源连接数据库的最长等待时间,以秒为单位

    getLoginTimeout():表示获取数据源连接到数据库的最长等待时间

    setLogWriter():设置数据源的日志输出者(log writer)为给定的一个PrintWriter实例

    getLogWriter():获取数据源的日志输出者。

    通过上面的源码可以发现,数据源类中的这些方法的实现都是直接调用的DriverManager的对应方法。

    getParentLogger():获取这个DataSource所使用的所有Logger的父Logger。

  下面是UnpooledDataSource中最重要的方法:

1   @Override
2 public Connection getConnection() throws SQLException {
3 return doGetConnection(username, password);
4 }
5 @Override
6 public Connection getConnection(String username, String password) throws SQLException {
7 return doGetConnection(username, password);
8 }

  这是两个获取数据源连接的方法,第一个是无参方法,它内部使用默认的用户名与面目进行数据库连接,第二个提供了指定的用户名与密码,使用指定的用户名与密码进行数据库连接,并将该连接返回。二者内部都调用了同一个方法doGetConnection(),这是真正执行数据库连接并获取这个连接的方法。

 1   private Connection doGetConnection(String username, String password) throws SQLException {
2 Properties props = new Properties();
3 if (driverProperties != null) {
4 props.putAll(driverProperties);
5 }
6 if (username != null) {
7 props.setProperty("user", username);
8 }
9 if (password != null) {
10 props.setProperty("password", password);
11 }
12 return doGetConnection(props);
13 }

  解析:内部定义一个Properties属性变量用于存储传递的参数,先对driverProperties(里面保存的是以driver.为前缀设置的配置信息)进行判断,如果有值直接将其内部的值全部转移到新的properties中,再判断传递的username与password,并将其保存到properties中,其实这时,properties中保存的是有关数据源连接的基础信息。

  上面的方法最后调用了重载的同名方法:

1   private Connection doGetConnection(Properties properties) throws SQLException {
2 initializeDriver();
3 //属性的前缀是以“driver.”开 头的,它 是 通 过 DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动
4 Connection connection = DriverManager.getConnection(url, properties);
5 configureConnection(connection); 
6 return connection;
7 }

  在这个重载方法中首先调用initializeDriver()方法进行驱动器初始化:

 1   private synchronized void initializeDriver() throws SQLException {
2 //这里便是大家熟悉的初学JDBC时的那几句话了 Class.forName newInstance()
3 if (!registeredDrivers.containsKey(driver)) {
4 Class<?> driverType;
5 try {
6 if (driverClassLoader != null) {
7 driverType = Class.forName(driver, true, driverClassLoader);
8 } else {
9 driverType = Resources.classForName(driver);
10 }
11 // DriverManager requires the driver to be loaded via the system ClassLoader.
12 // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
13 Driver driverInstance = (Driver)driverType.newInstance();
14 DriverManager.registerDriver(new DriverProxy(driverInstance));
15 registeredDrivers.put(driver, driverInstance);
16 } catch (Exception e) {
17 throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
18 }
19 }
20 }

  这是驱动器初始化的方法,该方法使用synchronized进行修饰,表示这是一个同步方法,同一时刻只能被一处调用,这个方法的作用是加载驱动器类,并将其实例注册到DriverManager中,同时将其保存到本实例的registeredDrivers中。

  这里有个问题就是DriverProxy,这是一个驱动代理,这个类是以静态代理类的方式定义的,其实现了Driver接口,实现了Driver接口中的所有抽象方法,是一个真正的代理类,代理Driver真正的实现类,即真正起作用的驱动类实例,代理类将驱动类的所有方法全部保护起来,这里真正的目的还请大家商酌讨论。

  然后返回上一步方法中,通过调研DriverManager的getConnection方法来获取数据库连接connection,最后调用configureConnection()方法进行数据库连接的最后配置,配置的内容如下:

1   private void configureConnection(Connection conn) throws SQLException {
2 if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
3 conn.setAutoCommit(autoCommit);
4 }
5 if (defaultTransactionIsolationLevel != null) {
6 conn.setTransactionIsolation(defaultTransactionIsolationLevel);
7 }
8 }

  配置内容为:自动提交(boolean值)与事务级别

  最后将获取到的数据库连接返回。

  为什么我会说失误呢?因为应该是现有数据源,再有数据库连接,再有事务操作,所以本应该先介绍DataSource,再介绍Transaction的,但是这里我说反了......

  (在此做个记录)