MyBatis源码解析之基础模块—DataSource
前文回顾
上一章节我们一起学习了Mapper接口绑定的源码逻辑。本次我们学习MyBatis的DataSource数据源模块。
为解决这种操作方式的弊端,在mybatis版本中提供了binding模块。从而能够在编译期就能够发现问题。同时通过采用jdk动态代理模式,开发者只需要要编写对应的接口即可完成持久层的开发工作。即降低工作量,有大大降低出错概率。
接下来,我们将通过源码详细介绍binding的执行逻辑。
背景知识
因为常见的数据源都会基于javax.sql.Datasource实现。Mybatis的数据源实现也是基于实现javax.sql.Datasource来设计的,也是在介绍MyBatis数据源实现之前,咱们先了解下JDK的DataSource。
关于jdk中对DataSource在Oracle官网DataSource介绍有如下一段描述:
A factory for connections to the physical data source that this
DataSource
object represents. An alternative to theDriverManager
facility, aDataSource
object is the preferred means of getting a connection. An object that implements theDataSource
interface will typically be registered with a naming service based on the Java™ Naming and Directory (JNDI) API.The
DataSource
interface is implemented by a driver vendor. There are three types of implementations:
- Basic implementation – produces a standard
Connection
object- Connection pooling implementation – produces a
Connection
object that will automatically participate in connection pooling. This implementation works with a middle-tier connection pooling manager.- Distributed transaction implementation – produces a
Connection
object that may be used for distributed transactions and almost always participates in connection pooling. This implementation works with a middle-tier transaction manager and almost always with a connection pooling manager.A
DataSource
object has properties that can be modified when necessary. For example, if the data source is moved to a different server, the property for the server can be changed. The benefit is that because the data source’s properties can be changed, any code accessing that data source does not need to be changed.A driver that is accessed via a
DataSource
object does not register itself with theDriverManager
. Rather, aDataSource
object is retrieved though a lookup operation and then used to create aConnection
object. With a basic implementation, the connection obtained through aDataSource
object is identical to a connection obtained through theDriverManager
facility.An implementation of
DataSource
must include a public no-arg constructor.
翻译过来就是:
一个用于连接到此DataSource对象表示的物理数据源的工厂。作为DriverManager工具的替代方法,DataSource对象是获取连接的首选方法。通常将基于Java™命名和目录(JNDI)API向实现命名服务的对象注册实现DataSource接口的对象。
DataSource接口由驱动程序供应商实现。共有三种类型的实现:
基本实现-产生一个标准的Connection对象
连接池实现-产生一个Connection对象,该对象将自动参与连接池。此实现与中间层连接池管理器一起使用。
分布式事务实现-产生一个Connection对象,该对象可用于分布式事务,并且几乎总是参与连接池。此实现与中间层事务管理器一起使用,并且几乎总是与连接池管理器一起使用。
DataSource对象具有可以在必要时修改的属性。例如,如果将数据源移动到其他服务器,则可以更改服务器的属性。好处在于,因为可以更改数据源的属性,所以不需要更改访问该数据源的任何代码。通过DataSource对象访问的驱动程序不会在DriverManager中注册自身。而是通过查找操作检索DataSource对象,然后将其用于创建Connection对象。通过基本实现,通过DataSource对象获得的连接与通过DriverManager工具获得的连接相同。
DataSource的实现必须包括一个公共的无参数构造函数。
根据 DataSource的实现必须包括一个公共的无参数构造函数的描述。
这也是下面分析源码时看到的为什么池化数据源PoolDataSource与非池化数据源UnpooledDataSource都有显性定义无参构造函数的原因。
关于DataSource就简单介绍到这里,有兴趣的同学可以查阅相关资料及jdk源码等。
架构设计
DataSource模块所在包路径为org.apache.ibatis.datasource
,其具体划分如下:
datasource
- jndi
- JndiDataSourceFactory
- pooled
- PooledConnection
- PooledDataSource
- PooledDataSourceFactory
- PoolState
- unpooled
- UnpooledDataSource
- UnpooledDataSourceFactory
- DataSourceException
- DataSourceFactory
对应的类架构设计图如下:
从架构图中,我们很显然发现,该架构采用经典的工厂方法设计模式(关于设计模式的介绍各位可以参阅本人的另一个设计模式专题,或者其他资料)
源码解读
DataSourceFactory
DataSourceFactory
接口只提供两个方法:setProperties()
设置数据源相关属性;getDataSource()
获取数据源。
/**
* 数据源工厂接口类,只提供两个方法:
* 1.设置相关配置属性
* 2.获取数据源
* 3.该接口有三个实现类:PooledDataSourceFactory,UnpooledDataSourceFactory,JndiDataSourceFactory
*/
public interface DataSourceFactory {
//设置属性(其目的是位datasource填充配置属性)
void setProperties(Properties props);
//获取数据源对象
DataSource getDataSource();
}
MyBatis提供三种DataSourceFactory
的实现方式:JndiDataSourceFactory
,UnpooledDataSourceFactory
和PooledDataSourceFactory
。现对其逐一介绍。
UnpooledDataSourceFactory
DataSourceFactory
的UnpooledDataSourceFactory
的实现,首先会在其构造方法中直接实例化非池化的数据源UnpooledDataSource
,并 通过getDataSource()方法获取该数据源。UnpooledDataSource
数据源中相关属性的填充则通过setProperties()
进行设置。具体细节及说明,请参阅如下源码:
package org.apache.ibatis.datasource.unpooled;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
public class UnpooledDataSourceFactory implements DataSourceFactory {
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
/**
* 从properties中获取对应的配置信息
*/
@Override
public void setProperties(Properties properties) {
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."开头的为DataSource相关配置,均保存到driverProperties对象中,并最终会设置在metaDataSource中
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
//普通配置属性直接设置在metaDataSource中
String value = (String) properties.get(propertyName);
//调用私有类型转换方法
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
/** driverProperties对象有值的情况下才设置 */
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
@Override
public DataSource getDataSource() {
return dataSource;
}
/** 根据propertyName的属性类型转换对应的value值,主要处理Integer,long及boolean三种 */
private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
Class<?> targetType = metaDataSource.getSetterType(propertyName);
if (targetType == Integer.class || targetType == int.class) {
convertedValue = Integer.valueOf(value);
} else if (targetType == Long.class || targetType == long.class) {
convertedValue = Long.valueOf(value);
} else if (targetType == Boolean.class || targetType == boolean.class) {
convertedValue = Boolean.valueOf(value);
}
return convertedValue;
}
}
PooledDataSourceFactory
PooledDataSourceFactory
实现则更为简单,他并没有复写setProperties()
,getDataSource()
方法,而是直接继承UnpooledDataSourceFactory
,唯一区别就是构造方法中dataSource实例化对象为PooledDataSource
,具体代码如下
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
//与UnpooledDataSourceFactory唯一的区别
this.dataSource = new PooledDataSource();
}
}
JndiDataSourceFactory
介绍JndiDataSourceFactory
之前,先简单介绍下JNDI(全称为Java Naming and Directory Interface), JNDI是 SUN 公司提供的一种标准的 Java 命名系统接口,JNDI 提供统一的客户端 API,通过不同的访问提供者接口 JNDI 服务供应接口 ( SPI ) 的实现,由管理者将 JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务之间进行交互。其根本目的还是降低耦合性,提供部署的灵活性,降低维护成本。
JndiDataSourceFactory
实现与上述两种有所不同,从其命名的方式就可以知道DataSource的设置需要依赖具体的数据源厂商,因此不能在构造函数中进行实例化。所以只能从配置中读取相关信息,然后根据Context上下文环境,通过lookup的方式进行实例化。
具体细节及说明,请看如下代码及相关注释:
package org.apache.ibatis.datasource.jndi;
import java.util.Map.Entry;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
/**
* Jndi数据源工厂类
*/
public class JndiDataSourceFactory implements DataSourceFactory {
public static final String INITIAL_CONTEXT = "initial_context";
public static final String DATA_SOURCE = "data_source";
public static