1、回顾
上一文中解读了 MyBatis 中的事务模块,其实事务操作无非就是提交和回滚。整个事务模块采用了抽象工厂模式进行设计,将具体的事务实例的生成对使用方隐藏,使用工厂中提供的生产方法来获取。
事务模块分为两类:JDBC 类型和 MANAGED 类型,前者多用于单独使用 MyBatis 框架的情况下(比如测试学习 MyBatis 功能),后者则表示托管于其他框架,比如 Spring 来完成事务功能。
这一回我们来看看环境设置中的另一项内容:数据源 DataSource。
(其实应该先解读 DataSource,在解读 Transaction 的,但是... 失误啊...)
2、数据源模块
数据源模块位于 org.apache.ibatis.datasource 包下,其架构结构为:
- org.apache.ibatis.datasource
- ----org.apache.ibatis.datasource.jndi
- --------JndiDataSourceFactory.java
- ----org.apache.ibatis.datasource.pooled
- --------PooledConnection.java
- --------PooledDataSource.java
- --------PooledDataSourceFactory.java
- --------PoolState.java
- ----org.apache.ibatis.datasource.unpooled
- --------UnpooledDataSource.java
- --------UnpooledDataSourceFactory.java
- ----DataSourceException.java
- ----DataSourceFactory.java
和事务模块类似,数据源模块同样采用了工厂模式。
2.1 数据源接口
org.apache.ibatis.datasource 包下的 DataSourceFactory 是数据源工厂接口,至于数据源接口沿用 JDK 中给出的 javax.sql 包下的数据源接口 DataSource,下面给出 JDK 中 DataSource 接口的源码:
- 1 package javax.sql;
- 2 import java.sql.Connection;
- 3 import java.sql.SQLException;
- 4 import java.sql.Wrapper;
- 5 public interface DataSource extends CommonDataSource,
- Wrapper {
- 6 Connection getConnection() throws SQLException;
- 7 Connection getConnection(String username, String password) 8 throws SQLException;
- 9
- }
从源码中可以看出,在数据源接口中定义了两个获取数据库连接 Connection 的方法,可见数据源的目的所在,不论其使用什么原理实现,其目的就是为了对外提供数据库连接的获取接口。我们千方百计创建数据源实例,就是为外方通过 getConnection() 方法来获取数据库连接做基础准备工作。下面看数据源工厂接口源码:
数据源工厂接口源码:
- 1 package org.apache.ibatis.datasource;
- 2 import java.util.Properties;
- 3 import javax.sql.DataSource;
- 4
- /**
- 5 * 数据源工厂
- 6 * 有三种内建的数据源类型 UNPOOLED POOLED JNDI
- 7 */
- 8 public interface DataSourceFactory {
- 9 //设置属性,被XMLConfigBuilder所调用
- 10 void setProperties(Properties props);
- 11 //生产数据源,直接得到javax.sql.DataSource
- 12 DataSource getDataSource();
- 13
- }
数据源工厂接口中定义了两个方法,一个是设置属性的方法,与事务模型中的设置属性方法类似,也是需要在构建 Configuration 配置类时由 XMLConfigBuilder 来进行调用的,调用的目的是为了将配置文件中配置的数据源属性信息填充到 DataSource 中(然后在填充到 Environment 中,在填充到 Configuration 中,这是后话)。
这些 Properties 属性配置正是第二节列举过的 Configuration 配置文件中的一部分:
- 1 <dataSource type="POOLED">
- 2 <property name="driver" value="com.mysql.jdbc.Driver"/>
- 3 <property name="url" value="jdbc:mysql://localhost:3306/mbtest"/>
- 4 <property name="username" value="root"/>
- 5 <property name="password" value="123456"/>
- 6 </dataSource>
这一段配置内容中 <dataSource> 标签表明这是数据源的配置信息,type 属性表示这个数据源是 POOLED(池型)类型的数据源,然后在其内部设置 property 子标签用于指定数据源的具体信息:驱动器、数据库 URL、用户名、密码四项内容。
另一个方法是 DataSource 实例的生产方法,这也是工厂的主要作用:用来生产对应类的实例。
这里插播一条信息:Connection 与 DataSource 的关系,Connection(连接)是包含在 DataSource(数据源)之内的,我们可以通过 DataSource 来得到其中的 Connection,反之不可能,所以我们需要先创建 DataSource 实例,以此来获取 Connection 数据库连接,所有 DataSource 是基础,Connection 是目的。
2.2 Mybatis 数据源类型
MyBatis 为我们提供了三种具体的数据源类型:
unpooled:非池型数据源
pooled:池型数据源
jndi:托管型
2.3 非池型数据源及其工厂
2.3.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.3.2 非池型数据源:UnpolledDataSource
该类实现了 DataSource 数据源接口,实现了其中的所有抽象方法。
非池型是相对池型而言的,池型数据源统筹管理着一个数据库连接池,在这个池中拥有指定数量的数据库连接实例 connection 可供使用,其内部采用一定的规则指定数据连接对象的使用、创建、销毁规则,来为多线程数据库访问提供及时有效的数据库连接。
非池型数据源,即保持有一个数据库连接实例的数据源。下面我们来看看这种数据源的实现方式:
首先我们来看看字段:
- 1 private ClassLoader driverClassLoader;
- 2 private Properties driverProperties;
- 3 private static Map registeredDrivers = new ConcurrentHashMap();
- 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 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 的,但是这里我说反了......
(在此做个记录)
2.4 池型数据源
现在的 Java 项目中多采用池型数据源,C3P0,DBCP 之类的也都提供了池型数据源,在 MyBatis 中也自定义了一种池型数据源 PooledDataSource,这个 pooled 正好与之前的 Configuration 配置文件中配置的数据源的类型 "POOLED" 对应。
- <dataSource type="POOLED">
2.4.1 池型数据源工厂
首先我们来看看池型数据源的数据源工厂:PooledDataSourceFactory
- 1 package org.apache.ibatis.datasource.pooled;
- 2 import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
- 3 public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
- 4 public PooledDataSourceFactory() {
- 5 this.dataSource = new PooledDataSource();
- 6
- }
- 7
- }
代码很简单,内部只有一个方法,用于获取吃型数据源的实例。果然好简单,你想错了......
从类结构就可以看出,这个类继承了 UnpooledDataSourceFactory 工厂类,也就是说,PooledDataSourceFactory 也拥有 UnpooledDataSourceFactory 中罗列的诸多方法功能,最主要继承的功能就是设置属性的功能,这个功能可以将被读取到内存中的数据源配置信息保存到数据源实例中。
这个功能之前已经有过介绍,这里不再赘述。下面看看 PooledDataSource 数据源。
这里在介绍 PooledDataSource 之前需要先对 MyBatis 自定义的池连接辅助类进行介绍。
2.4.2 池型连接:PooledConnedtion
- class PooledConnection implements InvocationHandler {
不看不知道,一看就明白,这是一个动态代理,采用的是 JDK 动态代理,说明 PooledConnection 池连接就是为了创建一个真实连接的代理,使用连接代理来调用真实连接来进行其他操作。
同时这个代理对连接的功能进行了扩充,将其转化为池型连接,即池型的概念与实现有一部分就在这个类中进行,这个类将一个普通的连接包装成为一个池型化的连接,使其适用于 MyBatis 自定义的池型数据源。当然这并不是包装器模式,明显的代理模式(动态代理模式)。
下面让我们来仔细看看如何将一个简单的连接包装成为一个池型连接。
首先我们来看看池型连接拥有哪些属性?
- 1 private static final String CLOSE = "close";
- 2 private static final Class[] IFACES = new Class[] {
- Connection.class
- };
- 3 private int hashCode = 0;
- 4 private PooledDataSource dataSource;
- 5 //真正的连接
- 6 private Connection realConnection;
- 7 //代理的连接
- 8 private Connection proxyConnection;
- 9 private long checkoutTimestamp;
- 10 private long createdTimestamp;
- 11 private long lastUsedTimestamp;
- 12 private int connectionTypeCode;
- 13 private boolean valid;
这里我们一一介绍:
String CLOSE = "close":这个很好理解,这只是下方代码中需要使用到的一个静态字符串常量而已,表示关闭。
Class<?>[] IFACES = new Class<?>[] {Connection.class}:这个和上面类似,也是下方要使用的一个静态数组常量,表示连接类型,这里明显使用到了多态的概念。Connection 是所有连接的祖类,
int hashCode = 0:这是数据库连接(真实连接)的 HashCode 值,默认为 0,表示当真实连接不存在即为 null 时的值。
PooledDataSource dataSource:池型数据源,为什么在池型连接中会需要池型数据源实例呢?在下面你会看到它的应用,它的主要目的还是为了方便调用其内部定义的部分方法来辅助完成池型连接的一些功能判断。
Connection realConnection:表示真实的数据库连接,属于被代理的对象
Connection proxyConnection:表示使用 JDK 动态代理创建的代理真实连接的代理连接实例
long checkoutTinmestamp:表示数据库连接被检出的时间戳,这个用于计算具体的检出时间
long createdTimestamp:表示数据库连接被创建的时间戳,用于计算数据库连接被创建的时间
long lastUsedTimestamp:表示连接被最后使用的时间戳,用于计算数据库连接被最后使用的时间
int connectionTypeCode:数据库连接的类型编码,格式为:url+username+password
boolean valid:表示连接是否可用的逻辑值
以上的属性大多就是在原有的数据库连接的基础上做的再包装,为其赋予更多的属性,使其成为一个不折不扣的池型连接,这就像为一个人穿上各种衣服,装饰,学习各种知识能力,最后将其包装成一个某一方面的专业人士。呵呵,这里是真正的专业人士,真正拥有专业能力的人士。当然最核心的还是这个人了,在池型连接中也一样,最核心的当然还是这个真实连接了。
下面看看构造器:
- 1 public PooledConnection(Connection connection, PooledDataSource dataSource) {
- 2 this.hashCode = connection.hashCode();
- 3 this.realConnection = connection;
- 4 this.dataSource = dataSource;
- 5 this.createdTimestamp = System.currentTimeMillis();
- 6 this.lastUsedTimestamp = System.currentTimeMillis();
- 7 this.valid = true;
- 8 this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
- 9
- }
在这个构造器中需要传递两个参数,分别为:数据库连接实例与池型数据源实例,将其分别赋值给对应的属性,同时初始化其余属性的值,最重要的还是最后一项,调用 Proxy 的 newProxyInstance() 方法来生成代理连接实例,其参数分别为:Connection 类的类加载器、接口数组、当前类的实例。(这是固定格式,套路,详情可参见《》、《》)
在这个动态代理实现中最重要的还是 invoke 方法的实现:
- 1@Override 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- 3 String methodName = method.getName();
- 4 //如果调用close的话,忽略它,反而将这个connection加入到池中
- 5
- if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
- 6 dataSource.pushConnection(this);
- 7
- return null;
- 8
- } else {
- 9
- try {
- 10
- if (!Object.class.equals(method.getDeclaringClass())) {
- 11 // issue #579 toString() should never fail
- 12 // throw an SQLException instead of a Runtime
- 13 //除了toString()方法,其他方法调用之前要检查connection是否还是合法的,不合法要抛出SQLException
- 14 checkConnection();
- 15
- }
- 16 //其他的方法,则交给真正的connection去调用
- 17
- return method.invoke(realConnection, args);
- 18
- } catch(Throwable t) {
- 19
- throw ExceptionUtil.unwrapThrowable(t);
- 20
- }
- 21
- }
- 22
- }
- 23 24 private void checkConnection() throws SQLException {
- 25
- if (!valid) {
- 26
- throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
- 27
- }
- 28
- }
我们一句句来看:
首先获取要调用方法的名称 methodName,然后对这个名称进行验证,如果是 close 方法的话,那么忽略关闭的实现,转而将这个连接加到连接池中(推入 pushConnection),这表示在池型了数据源中,当连接不再使用后是要返回池中备用的,而不是直接被关闭销毁。
如果调用的方法是不是 close 时,则首先进行该方法申明处的判断,如果这个方法不是来自 Object 类(剔除 toString() 方法),那么对当前的连接的可用性进行判断,如果不可用(valid 值为 false),则抛出 SqlException,否则继续执行下一步,由真实连接进行方法调用。
原理也很简单,总的来说就是实现方法调用(这也是代理的目的所在),外面看起来是由代理类执行方法,其实内部是由真实连接类来执行方法。
2.4.3 池状态类:PoolState
池状态,顾名思义,用于描述连接池的状态(或称为属性也行)的类,这个属性不同于池连接的属性,这是出于连接池的属性,这个属性是作用于整个连接池的,而连接池中包含有限个池连接,一定要明白其中的关系。
在这个类中定义了诸多属性,但是这些属性多用于统计信息的输出,什么是统计信息呢,就是将池连接的一些信息做一番统计并输出。所以其中重要的属性并不多,如下:
- 1 protected PooledDataSource dataSource;
- 2 //空闲的连接
- 3 protected final List idleConnections = new ArrayList();
- 4 //活动的连接
- 5 protected final List activeConnections = new ArrayList();
- 6 //----------以下是一些统计信息----------
- 7 //请求次数
- 8 protected long requestCount = 0;
- 9 //总请求时间
- 10 protected long accumulatedRequestTime = 0;
- 11 protected long accumulatedCheckoutTime = 0;
- 12 protected long claimedOverdueConnectionCount = 0;
- 13 protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
- 14 //总等待时间
- 15 protected long accumulatedWaitTime = 0;
- 16 //要等待的次数
- 17 protected long hadToWaitCount = 0;
- 18 //坏的连接次数
- 19 protected long badConnectionCount = 0;
- 20 //构造器
- 21 public PoolState(PooledDataSource dataSource) {
- 22 this.dataSource = dataSource;
- 23
- }
上面代码注释中解释的很明白,第一个是数据源实例,这个实例在通过构造器创建实例的时候传入并赋值,然后及时两个 ArrayList 集合分别用于保存空闲的池连接实例与活动(使用)中的池连接实例。其余的就是一些统计信息,对于这些统计信息,不再详述,可自行翻看源码。
这里做简单介绍:类中重写了 toString() 方法,用于输出这些信息,在类中用于获取这些信息的方法都是同步方法,保证线程安全性,保证获得的是正确的信息。
下面就是重点:池型数据源
2.4.4 池型数据源:PooledDataSource
这是一个同步的线程安全的数据库连接池。
其实对于一个有连接池的数据源来说,针对池中数据连接的操作就是定义这个池型数据源的重点所在,那么针对池型连接的操作有哪些呢?
获取池连接(推出池连接)
收回池连接(推入池连接)
关闭池连接
在加上一个池连接的可用性判断,而我们的重点也就集中在这几点。
首先我们来看看池型数据源拥有那些属性:
- 1 public class PooledDataSource implements DataSource {
- 2
- 3 private static final Log log = LogFactory.getLog(PooledDataSource.class);
- 4
- 5 //有一个池状态
- 6 private final PoolState state = new PoolState(this);
- 7
- 8 //里面有一个UnpooledDataSource
- 9 private final UnpooledDataSource dataSource;
- 10
- 11 // OPTIONAL CONFIGURATION FIELDS
- 12 //正在使用连接的数量
- 13 protected int poolMaximumActiveConnections = 10;
- 14 //空闲连接数
- 15 protected int poolMaximumIdleConnections = 5;
- 16 //在被强制返回之前,池中连接被检查的时间
- 17 protected int poolMaximumCheckoutTime = 20000;
- 18 //这是给连接池一个打印日志状态机会的低层次设置,还有重新 尝试获得连接, 这些情况下往往需要很长时间 为了避免连接池没有配置时静默失 败)。
- 19 protected int poolTimeToWait = 20000;
- 20 //发送到数据的侦测查询,用来验证连接是否正常工作,并且准备 接受请求。默认是"NO PING QUERY SET" ,这会引起许多数据库驱动连接由一 个错误信息而导致失败
- 21 protected String poolPingQuery = "NO PING QUERY SET";
- 22 //开启或禁用侦测查询
- 23 protected boolean poolPingEnabled = false;
- 24 //用来配置 poolPingQuery 多次时间被用一次
- 25 protected int poolPingConnectionsNotUsedFor = 0;
- 26
- 27 private int expectedConnectionTypeCode;
- 28 ......
- 29 }
结合源码中的注释内容可知:
PoolState state = new PoolState(this):拥有一个池状态属性,无可厚非,池状态正是用于描述整个连接池整体的,这里将当前数据源实例作为参数赋予池状态形成一个不变的实例(final 修饰的作用),这里的不变指的是这个池状态的实例是不变的,但不并意味着池状态中的各属性的值也不变,这个要看池状态类中属性是如何定义的,查看源码发现,这两个集合也是 final 修饰的,这表明这两个集合实例也是不会变的,但这同样无法保证集合中属性值的不变性,所以,final 修饰所针对的就是最外层,他并不会对其内部的定义产生影响。
UnpooledDataSource dataSource:拥有一个非池型数据源。可以这么说,一个池型数据源就是一个非池型数据源加上一个连接池,也就是说,池型数据源是在非池型数据源基础上发展而来,是以非池型为基础的。
int poolMaximumActiveConnections = 10:连接池中最多可拥有的活动连接数,这是最大值,池中保存的活动连接数不能超过这个值(10 个),当要超过时,在没有空闲连接的基础下,不能在新建连接,而是从活动链接中取最老的那个连接进行使用。(这个发生在推出连接时)
int poolMaximumIdleConnections = 5:连接池中最多可拥有的空闲连接数,这是最大值,池中保存的空闲连接数不能超过这个值(5 个),当要超过时,将多出的真实连接直接关闭,池连接置为无效。(这个发生在推入连接时)
int poolMaximumCheckoutTime = 20000:连接池最大检出时间(可以理解为验证时间),如果一个连接验证时间超过设定值,则将这个连接设置为过期(发生在推出连接时)
int poolTimeToWait = 20000:池等待时间,当需要从池中获取一个连接时,如果空闲连接数量为 0,而活动连接的数量也达到了最大值,那么就针对那个最早取出的连接进行检查验证(check out),如果验证成功(即在上面 poolMaximumCheckoutTime 限定的时间内验证通过),说明这个连接还处于使用状态,这时取出操作暂停,线程等待限定时间,这个限定时间就是这个参数的使用位置。
String poolPingQuery = "NO PING QUERY SET":在验证连接是否有效的时候,对数据库执行查询,查询内容为该设置内容。整个目的就是为了得知这个数据库连接还是否能够使用(未关闭,并处于正常状态),这是一个侦测查询。
boolean poolPingEnabled = false:这是一个开关,表示是否打开侦测查询功能,默认为 false,表示关闭该功能。
int poolPingConnectionsNotUsedFor = 0:如果一个连接在限定的时间内一直未被使用,那么就要对该连接进行验证,以确定这个连接是否处于可用状态(即进行侦测查询),这个限定的时间就使用 poolPingConnectionsNotUsedFor 来设定,默认值为 0。
int expectedConnectionTypeCode:连接的类型编码,这个类型编码在创建池型数据源实例的时候会被组装,他的组装需要从数据源中获取连接的 url、username、password 三个值,将其按顺序组合在一起,这个类型编码可用于区别连接种类。
上面说到了属性字段,下面紧接着就说说针对属性的操作方法:
state:它只有 get 方法,用于获取池状态值,因为他是 final 的,所以不设置 set 方法
dataSource:它的值在构造器中创建并进行赋值,是 final 的,不存在 get 与 set 方法。
poolMaximumActiveConnections:拥有 set 与 get 方法,可设置新值,也能获取其值。
poolMaximumCheckoutTime:拥有 set 与 get 方法,可设置新值,也能获取其值。
poolTimeToWait:拥有 set 与 get 方法,可以设置新值,也能获取其值。
poolPingQuery:拥有 set 与 get 方法,可以设置新值,也能获取其值。
poolPingEnabled:拥有 set 与 get 方法,可以设置新值,也能获取其值。
poolPingConnectionsNotUsedFor:拥有 set 与 get 方法,可以设置新值,也能获取其值。
这里有个注意点,在上述的每个 set 方法中,其中还包括针对 UnpooledDataSource 中的属性的 set 方法重写之中,都在最后拥有这么一个方法:forceCloseAll()
- 1 public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
- 2 this.poolPingConnectionsNotUsedFor = milliseconds;
- 3 forceCloseAll();
- 4
- }
这是什么意思呢?让我们来看看 forceCloseAll() 这个方法就明白了:
- 1
- /*
- 2 * Closes all active and idle connections in the pool
- 3 */
- 4 public void forceCloseAll() {
- 5 synchronized(state) {
- 6 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
- 7 //关闭所有的activeConnections和idleConnections
- 8
- for (int i = state.activeConnections.size(); i > 0; i--) {
- 9
- try {
- 10 PooledConnection conn = state.activeConnections.remove(i - 1);
- 11 conn.invalidate();
- 12 13 Connection realConn = conn.getRealConnection();
- 14
- if (!realConn.getAutoCommit()) {
- 15 realConn.rollback();
- 16
- }
- 17 realConn.close();
- 18
- } catch(Exception e) {
- 19 // ignore
- 20
- }
- 21
- }
- 22
- for (int i = state.idleConnections.size(); i > 0; i--) {
- 23
- try {
- 24 PooledConnection conn = state.idleConnections.remove(i - 1);
- 25 conn.invalidate();
- 26 27 Connection realConn = conn.getRealConnection();
- 28
- if (!realConn.getAutoCommit()) {
- 29 realConn.rollback();
- 30
- }
- 31 realConn.close();
- 32
- } catch(Exception e) {
- 33 // ignore
- 34
- }
- 35
- }
- 36
- }
- 37
- if (log.isDebugEnabled()) {
- 38 log.debug("PooledDataSource forcefully closed/removed all connections.");
- 39
- }
- 40
- }
注释很明白:关闭池中所有活动的和空闲的连接。代码也很简洁明了,就是将池状态中的两个集合中保存的池连接全部值为无效,所有的真是连接全部关闭。但是重要的不是这个做法,而是为什么要这么做?当我们呢重新设置一个参数时,就需要将所有的连接全部关闭。
其实也很好理解:连接是在数据源完全设置完整的情况下才生成的,数据源就是连接生成的基础,是连接存在的田地,当我们要修改数据源的基础属性的时候,原有设置上产生的连接必定不再适合新的设置,需要全部推倒重来,这里就是这个意思。
下面我们来看看构造器:
- 1 public PooledDataSource() {
- 2 dataSource = new UnpooledDataSource();
- 3
- }
- 4 5 public PooledDataSource(String driver, String url, String username, String password) {
- 6 dataSource = new UnpooledDataSource(driver, url, username, password);
- 7 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
- 8
- }
- 9 10 public PooledDataSource(String driver, String url, Properties driverProperties) {
- 11 dataSource = new UnpooledDataSource(driver, url, driverProperties);
- 12 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
- 13
- }
- 14 15 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
- 16 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
- 17 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
- 18
- }
- 19 20 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
- 21 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
- 22 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
- 23
- }
从上面列出的构造器中可以看出,池型数据源的根本还是非池型数据源,池型就是在非池型的基础上加上池型的概念与实现。我们在创建池型数据源实例
来源: http://www.cnblogs.com/V1haoge/p/6634880.html