一.介绍
所谓池,就是一种容器的抽象概念。当我们访问数据库时,就会创建一个连接对象,因此,所谓数据源池,就是将这些连接对象放入到这个‘池’的容器中,便于我们管理、调用和执行sql。
-分类
数据源池分为:有池化数据源和无池化数据源
-区别
无池化数据源:顾名思义,没有数据源池。即:每一次访问数据库并进行请求连接时,数据源都会创建一个新的连接。
有池化数据源:数据源池中设置一个属性:最多活跃连接数poolMaximumActiveConnections,它的默认值是10。当我们访问数据库时,数据源池中活跃链接数等于10,就代表此时数据源池中活跃连接数已满,无法在将新的链接放入到活跃链接数的队列中,所以如果还想创建新的活跃连接,必须等到连接超时并被释放后,才能再次创建活跃连接,从而进行调用和执行sql语句等。
二.分析
简单说一下概念之后,我们进入正文:通过学习Mybatis源码来一探究竟~
1.无池化数据源UnpooledDataSource
主要就是来介绍一下数据源都是怎么构成的,里面有什么属性和方法
public class UnpooledDataSource implements DataSource {
//驱动类加载器
private ClassLoader driverClassLoader;
//驱动属性类(暂时起这个名字)
private Properties driverProperties;
//驱动注册器(k-驱动全类名:比如:com.mysql.cj.jdbc.Driver; v-驱动类的实例对象)
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
//驱动
private String driver;
//数据库路径
private String url;
//数据库用户名
private String username;
//数据库密码
private String password;
//是否自动提交
private Boolean autoCommit;
//默认事务级别
private Integer defaultTransactionIsolationLevel;
//默认网络超时时间
private Integer defaultNetworkTimeout;
。。。省略构造器方法
//获得连接方法,主要这个就是封装一下,实际是调用下面的getConnection方法
public Connection getConnection() throws SQLException {
return this.doGetConnection(this.username, this.password);
}
//根据用户名和密码进行连接,也是封装,主要调用doGetConnection方法
public Connection getConnection(String username, String password) throws SQLException {
return this.doGetConnection(username, password);
}
//以下4个都是属于辅助方法,本质都是通过驱动管理器DriverManager实现对应方法对登录时间超时和日志打印进行处理
public void setLoginTimeout(int loginTimeout) {
DriverManager.setLoginTimeout(loginTimeout);
}
public int getLoginTimeout() {
return DriverManager.getLoginTimeout();
}
public void setLogWriter(PrintWriter logWriter) {
DriverManager.setLogWriter(logWriter);
}
public PrintWriter getLogWriter() {
return DriverManager.getLogWriter();
}
...省略set、get方法
private Connection doGetConnection(String username, String password) throws SQLException {
//Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,
// 配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。
// 就像在Java中,其配置文件常为.properties文件,是以键值对的形式进行参数配置的。
Properties props = new Properties();
if (this.driverProperties != null) {
props.putAll(this.driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return this.doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
this.initializeDriver();
Connection connection = DriverManager.getConnection(this.url, properties);
this.configureConnection(connection);
return connection;
}
//初始化驱动方法:主要就是判断驱动注册器是否有驱动,如果有的话,啥也不做;没有的话就通过驱动全类名创建驱动类的实例,最后再将驱动全类名和驱动类的实例对象一起以k-v值的方式存入驱动注册器中
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(this.driver)) {
try {
Class driverType;
if (this.driverClassLoader != null) {
driverType = Class.forName(this.driver, true, this.driverClassLoader);
} else {
driverType = Resources.classForName(this.driver);
}
Driver driverInstance = (Driver)driverType.getDeclaredConstructor().newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
registeredDrivers.put(this.driver, driverInstance);
} catch (Exception var3) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + var3);
}
}
}
private void configureConnection(Connection conn) throws SQLException {
if (this.defaultNetworkTimeout != null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), this.defaultNetworkTimeout);
}
if (this.autoCommit != null && this.autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(this.autoCommit);
}
if (this.defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(this.defaultTransactionIsolationLevel);
}
}
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException(this.getClass().getName() + " is not a wrapper.");
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public Logger getParentLogger() {
return Logger.getLogger("global");
}
//通过静态块来注册所有的驱动到驱动注册器中
static {
/**Enumeration 接口中定义了一些方法,通过这些方法可以遍历集合中的元素。*/
Enumeration<Driver> drivers = DriverManager.getDrivers();//通过驱动管理器获得驱动
while (drivers.hasMoreElements()) {
//如果drivers中有驱动,那么一个个注册到驱动注册器中registeredDrivers
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
private static class DriverProxy implements Driver {
//每个驱动程序都应提供一个实现Driver接口的类
private Driver driver;
DriverProxy(Driver d) {
this.driver = d;
}
//尝试与给定的URL建立数据库连接。
public boolean acceptsURL(String u) throws SQLException {
return this.driver.acceptsURL(u);
}
//检测驱动是否认为可以让它打开给定的Url的连接
public Connection connect(String u, Properties p) throws SQLException {
return this.driver.connect(u, p);
}
//检测驱动的主要版本号
public int getMajorVersion() {
return this.driver.getMajorVersion();
}
//检测驱动的次要版本号
public int getMinorVersion() {
return this.driver.getMinorVersion();
}
//获取有关此驱动的可能属性的信息
public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
return this.driver.getPropertyInfo(u, p);
}
//报告此驱动程序是否是真正的JDBC Compliant™驱动程序。
public boolean jdbcCompliant() {
return this.driver.jdbcCompliant();
}
//返回此驱动程序使用的所有记录器的父记录器。
public Logger getParentLogger() {
return Logger.getLogger("global");
}
}
}
无池话数据源工厂UnpooledDataSourceFactory
这个类里面有一部分是反射的内容,他不是重点,可以先关注我,后续出文章给大家讲解
其实,对于这个类你不需要太较真,你只需要知道,通过工厂类你可以调用数据源以及设置数据源需要的属性(比如url、driver、username、password等),最后获得设置好的数据源即可
核心方法
setProperties()方法:本质一句话,把你xml文件里对数据源配置的属性值全部设置到数据源中
public class UnpooledDataSourceFactory implements DataSourceFactory {
//设置驱动属性前缀=driver.
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();
protected DataSource dataSource = new UnpooledDataSource();
public UnpooledDataSourceFactory() {
}
//主要通过反射去将这些驱动名、用户名之类的属性封装到Properties类中,从而达到设置数据源属性的效果。
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
//获取元对象,这个和本章节的内容关联性小,先不去赘述太多,可以先关注我,陆续讲解这部分内容
MetaObject metaDataSource = SystemMetaObject.forObject(this.dataSource);
//获取properties类中所有的属性名
Iterator var4 = properties.keySet().iterator();
//就是将一个个属性值找到对应的属性名进行设置
while(var4.hasNext()) {
Object key = var4.next();
String propertyName = (String)key;
String value;
if (propertyName.startsWith("driver.")) {
value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else {
if (!metaDataSource.hasSetter(propertyName)) {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
//通过属性名获得值,切记:这个属性值,就是无论你真正属性类型是什么,一律转换成string类型,你肯定好奇,这是咋回事,这个和get方法有关,具体可以自行查阅源码。
value = (String)properties.get(propertyName);
//将属性值的当前类型(String)进行转换成正确类型
Object convertedValue = this.convertValue(metaDataSource, propertyName, value);
//将属性名赋值,而现在的属性值就是你已经根据真正属性类型设置到真正的属性中啦
metaDataSource.setValue(propertyName, convertedValue);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
//获得无池化数据源
public DataSource getDataSource() {
return this.dataSource;
}
//这个方法主要是一个辅助方法,根据属性名获得属性类型,再通过属性类型,将属性值Value进行返回
//metaDataSource是元对象(后续会讲,你只需要明白,通过这个对象可以根据属性名获得属性类型
//propertyName:属性名
//value:属性值
private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
//通过元对象的方法,获取属性的类型
Class<?> targetType = metaDataSource.getSetterType(propertyName);
//再分别判断类型是布尔、整型、Long
if (targetType != Integer.class && targetType != Integer.TYPE) {
if (targetType != Long.class && targetType != Long.TYPE) {
if (targetType == Boolean.class || targetType == Boolean.TYPE) {
//进行值类型转换
convertedValue = Boolean.valueOf(value);
}
} else {
//进行值类型转换
convertedValue = Long.valueOf(value);
}
} else {
//进行值类型转换
convertedValue = Integer.valueOf(value);
}
//返回这个转换后的值
return convertedValue;
}
}
三.总结
恭喜您🎉,凭借对学习源码的求知欲和毅力看到这里,为了让读者休息:我把有池化数据源放到了下一篇文章,读者也可以根据这个无池化数据源的实现逻辑试图先自己去阅读一下源码
----如果您对我的讲解还算满意的话,不妨点个赞,进行关注我,我会陆续出一些高质量的技术文章。(如需提供技术服务可看主页)
----如果您对我的讲解有问题,欢迎您的评论指正,我会虚心接受您的意见!
----注:本篇文章属于个人原创,如需要文章内容,请在您的文章中写下本篇文章的连接,谢谢!