mybatis源码解析之Configuration加载
Posted 厨房小码农
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis源码解析之Configuration加载相关的知识,希望对你有一定的参考价值。
概述
上一篇我们主要分析了下<environments>标签下面,transactionManager的配置,上问最后还有个遗留问题:就是在设置事物管理器的时候有个autocommit的变量的初始值是在哪边处理的呢?今天我们就来解答一下。
<environments>的dataSource分析
1 private void environmentsElement(XNode context) throws Exception { 2 if (context != null) { 3 if (environment == null) { 4 environment = context.getStringAttribute("default"); 5 } 6 for (XNode child : context.getChildren()) { 7 String id = child.getStringAttribute("id"); 8 if (isSpecifiedEnvironment(id)) { 9 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); 10 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); 11 DataSource dataSource = dsFactory.getDataSource(); 12 Environment.Builder environmentBuilder = new Environment.Builder(id) 13 .transactionFactory(txFactory) 14 .dataSource(dataSource); 15 configuration.setEnvironment(environmentBuilder.build()); 16 } 17 } 18 } 19 }
上一篇我们分析了黄色部分的,今天我们来分析下红色部分的,我们照旧先来看下configuation.xml中这部分的配置:
1 <environments default="development"> 2 <environment id="development"> 3 <transactionManager type="JDBC" /> 4 <dataSource type="POOLED"> 5 <property name="driver" value="${driveClass}" /> 6 <property name="url" value="${url}" /> 7 <property name="username" value="${userName}" /> 8 <property name="password" value="${password}" /> 9 </dataSource> 10 </environment> 11 </environments>
这边4到9行就是配置的datasource的相关信息,我们来看下上面标红的第九行代码,跟进去之后,解析代码如下:
1 private DataSourceFactory dataSourceElement(XNode context) throws Exception { 2 if (context != null) { 3 String type = context.getStringAttribute("type"); 4 Properties props = context.getChildrenAsProperties(); 5 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); 6 factory.setProperties(props); 7 return factory; 8 } 9 throw new BuilderException("Environment declaration requires a DataSourceFactory."); 10 }
第3行代码,根据上面配置文件中的type来确定数据源的类型,第4行,再获取连接数据源的一些必要属性配置,看下第5行代码:
1 protected Class<?> resolveClass(String alias) { 2 if (alias == null) { 3 return null; 4 } 5 try { 6 return resolveAlias(alias); 7 } catch (Exception e) { 8 throw new BuilderException("Error resolving class. Cause: " + e, e); 9 } 10 } 11 12 protected Class<?> resolveAlias(String alias) { 13 return typeAliasRegistry.resolveAlias(alias); 14 }
可以看出,跟前面实例化事物管理器一样,也是从typeAliasRegistry中根据type去获取,然后实例化,我上面配置的是pooled,这边最终实例化的应该是PooledDataSourceFactory这个数据源工厂,其实这边的配置不止这一个,还有JNDI,UNPOOLED,分别对应于JndiDataSourceFactory,UnpooledDataSourceFactory,这些也都是在Configuration.class中加载好的,
1 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 2 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 3 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
我们依次来介绍下,先看下目录结构:
可以看到,DataSourceFactory是数据源工厂接口,主要有如下两个方法:
1 public interface DataSourceFactory { 2 3 void setProperties(Properties props); 4 5 DataSource getDataSource(); 6 7 }
第3行代码,是用来设置数据源的相关属性,一般是在初始化完成之后进行,第5行代码,是获取数据源对象的方法。
这个接口有两个实现类,JndiDataSourceFactory和UnpooledDataSourceFactory,另外一个PooledDataSourceFactory其实是继承于UnpooledDataSourceFactory。
UnpooledDataSourceFactory
UnpooledDataSourceFactory主要用来创建UnpooledDataSource对象,代码如下:
1 public class UnpooledDataSourceFactory implements DataSourceFactory { 2 3 private static final String DRIVER_PROPERTY_PREFIX = "driver."; 4 private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); 5 6 protected DataSource dataSource; 7 8 public UnpooledDataSourceFactory() { 9 this.dataSource = new UnpooledDataSource(); 10 } 11 12 @Override 13 public void setProperties(Properties properties) { 14 Properties driverProperties = new Properties(); 15 MetaObject metaDataSource = SystemMetaObject.forObject(dataSource); 16 for (Object key : properties.keySet()) { 17 String propertyName = (String) key; 18 if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { 19 String value = properties.getProperty(propertyName); 20 driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); 21 } else if (metaDataSource.hasSetter(propertyName)) { 22 String value = (String) properties.get(propertyName); 23 Object convertedValue = convertValue(metaDataSource, propertyName, value); 24 metaDataSource.setValue(propertyName, convertedValue); 25 } else { 26 throw new DataSourceException("Unknown DataSource property: " + propertyName); 27 } 28 } 29 if (driverProperties.size() > 0) { 30 metaDataSource.setValue("driverProperties", driverProperties); 31 } 32 } 33 34 @Override 35 public DataSource getDataSource() { 36 return dataSource; 37 } 38 39 private Object convertValue(MetaObject metaDataSource, String propertyName, String value) { 40 Object convertedValue = value; 41 Class<?> targetType = metaDataSource.getSetterType(propertyName); 42 if (targetType == Integer.class || targetType == int.class) { 43 convertedValue = Integer.valueOf(value); 44 } else if (targetType == Long.class || targetType == long.class) { 45 convertedValue = Long.valueOf(value); 46 } else if (targetType == Boolean.class || targetType == boolean.class) { 47 convertedValue = Boolean.valueOf(value); 48 } 49 return convertedValue; 50 } 51 52 }
第8到10行代码,在构造方法中初始化UnpooledDataSource对象,并在getDataSource方法中返回UnpooledDataSource对象,在setProperties方法中完成对UnpooledDataSource对象的相关配置。我们看下setProperties方法大致流程如下:
1.创建datasource对应的MetaObject
2.遍历properties集合,这个集合中存放了数据源需要的信息,也是我们配置在configuration.xml的property
3.判断key值是否以“driver.”开头,以这个开头的是驱动类信息,保存至driverProperties中
4.不是以“driver.”开头属性,先判断在MetaObject(其实就是UnpooledDataSource)中是否有对应的set方法,没有,则抛出异常
5.有的话,现获取属性值,根据MetaObject中返回值进行类型进行类型转换,主要针对Integer,Long,Boolean
6.设置MetaObject中driverProperties的属性值,也就是datasource的属性值。
PooledDataSourceFactory
PooledDataSourceFactory 主要用来创建 PooledDataSource 对象,它继承了 UnpooledDataSource 类,设置 DataSource 参数的方法复用UnpooledDataSource 中的 setProperties 方法,只是数据源返回的是 PooledDataSource 对象而已。
1 public class PooledDataSourceFactory extends UnpooledDataSourceFactory { 2 3 public PooledDataSourceFactory() { 4 this.dataSource = new PooledDataSource(); 5 } 6 7 }
初始化PooledDataSourceFactory这个类时,会在构造方法中初始化PooledDataSource对象,后续getDataSource方法中返回的也就是这个对象。
JndiDataSourceFactory
JndiDataSourceFactory 依赖 JNDI 服务器中获取用户配置的 DataSource,这里暂时不看。
下面我们就来看看具体的数据源是怎么实现的,
UnpooledDataSource
UnpooledDataSource 不使用连接池来创建数据库连接,每次获取数据库连接时都会创建一个新的连接进行返回;
1 public class UnpooledDataSource implements DataSource { 2 3 private ClassLoader driverClassLoader; // 加载 Driver 类的类加载器 4 private Properties driverProperties; // 数据库连接驱动的相关配置 5 private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); // 缓存所有已注册的数据库连接驱动 6 7 private String driver; 8 private String url; 9 private String username; 10 private String password; 11 12 private Boolean autoCommit; // 是否自动提交 13 private Integer defaultTransactionIsolationLevel; // 事物的隔离级别 14 15 static { 16 Enumeration<Driver> drivers = DriverManager.getDrivers(); // 从DriverManager中获取已注册的驱动信息 17 while (drivers.hasMoreElements()) { 18 Driver driver = drivers.nextElement(); 19 registeredDrivers.put(driver.getClass().getName(), driver); // 将已注册的驱动信息保存至registeredDrivers中 20 } 21 }
接下来看一下,实现DataSource的两个获取连接的方法:
1 @Override 2 public Connection getConnection() throws SQLException { 3 return doGetConnection(username, password); // 根据properties中设置的属性类来获取连接 4 } 5 6 @Override 7 public Connection getConnection(String username, String password) throws SQLException { 8 return doGetConnection(username, password); 9 }
可以看出最终调用的都是doGetConnection(username, password)方法,具体如下:
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 } 14 15 private Connection doGetConnection(Properties properties) throws SQLException { 16 initializeDriver(); // 初始化数据库驱动 17 Connection connection = DriverManager.getConnection(url, properties); // 通过 DriverManager 来获取一个数据库连接 18 configureConnection(connection); // 配置数据库连接的 autoCommit 和隔离级别 19 return connection; 20 } 21 22 private synchronized void initializeDriver() throws SQLException { 23 if (!registeredDrivers.containsKey(driver)) { // 当前的驱动还有注册过,进行注册 24 Class<?> driverType; 25 try { 26 if (driverClassLoader != null) { 27 driverType = Class.forName(driver, true, driverClassLoader); 28 } else { 29 driverType = Resources.classForName(driver); 30 } 33 Driver driverInstance = (Driver)driverType.newInstance(); // 创建驱动对象实例 34 DriverManager.registerDriver(new DriverProxy(driverInstance)); // 往DriverManager中注册驱动 35 registeredDrivers.put(driver, driverInstance); // 在registerDrivers中记录加载过的驱动 36 } catch (Exception e) { 37 throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e); 38 } 39 } 40 } 41 // 设置数据库连接的 autoCommit 和隔离级别 42 private void configureConnection(Connection conn) throws SQLException { 43 if (autoCommit != null && autoCommit != conn.getAutoCommit()) { 44 conn.setAutoCommit(autoCommit); // 上一篇文章最后的问题答案就在这边 45 } 46 if (defaultTransactionIsolationLevel != null) { 47 conn.setTransactionIsolation(defaultTransactionIsolationLevel); 48 } 49 }
以上代码就是 UnpooledDataSource 类的主要实现逻辑,每次获取连接都是从数据库新创建一个连接进行返回,又因为,数据库连接的创建是一个耗时的操作,且数据库连接是非常珍贵的资源,如果每次获取连接都创建一个,则可能会造成系统的瓶颈,拖垮响应速度等,这时就需要数据库连接池了,Mybatis 也提供了自己数据库连接池的实现,就是 PooledDataSource 类。
PooledDataSource
这个类说真的,还是比较复杂的,我也是研究了有一会儿的,它内部创建数据库连接时基于我们上面介绍的UnpooledDataSource,但是呢,PooledDataSource并不会像UnpooledDataSource那样去管理数据库连接,而是通过PoolConnection来实现对于连接的管理,当然,既然是连接池,就有相关的状态,这边通过PoolState来管理连接池的状态,下面我们就来一次介绍下:
PoolConnection
代码如下,最明显的就是这个实现了InvocationHandler接口,说明它是一个代理类,那他肯定就会实现invoke接口。
1 class PooledConnection implements InvocationHandler { 2 3 private static final String CLOSE = "close"; // 判断是否是close方法 4 private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; 5 6 private int hashCode = 0;
7 private PooledDataSource dataSource; // 当前PoolConnection是属于哪个PooldataSource的 8 private Connection realConnection; // 真正的数据库连接 9 private Connection proxyConnection; // 数据库连接的代理对象 10 private long checkoutTimestamp; // 从连接池中取出该连接的时间戳 11 private long createdTimestamp; // 该连接创建的时间戳 12 private long lastUsedTimestamp; // 该连接最后一次被使用的时间戳 13 private int connectionTypeCode; // 用于标识该连接所在的连接池,由URL+username+password 计算出来的hash值 14 private boolean valid; // 代理连接是否有效 15 22 public PooledConnection(Connection connection, PooledDataSource dataSource) { 23 this.hashCode = connection.hashCode(); 24 this.realConnection = connection; 25 this.dataSource = dataSource; 26 this.createdTimestamp = System.currentTimeMillis(); 27 this.lastUsedTimestamp = System.currentTimeMillis(); 28 this.valid = true; 29 this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); 30 } 31 // 废弃连接 35 public void invalidate() { 36 valid = false; 37 } 38 // 判断连接是否有效
1.根据valid
2.向数据库中发送检测测试的SQL,查看真正的连接还是否有效 44 public boolean isValid() { 45 return valid && realConnection != null && dataSource.pingConnection(this); 46 }
上面这几个方法就是PoolConnection的构造方法,初始化相关类变量,判断连接是否有效及废弃连接的方法。下面我们再来看下invoke方法:
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2 String methodName = method.getName(); 3 if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 判断是不是close方法,如果是,将连接放回到连接池中,下次继续获取使用 4 dataSource.pushConnection(this); 5 return null; 6 } else { 7 try { 8 if (!Object.class.equals(method.getDeclaringClass())) { // 不是close方法,执行真正的数据库连接执行 9 // issue #579 toString() should never fail 10 // throw an SQLException instead of a Runtime 11 checkConnection(); // 检查连接是否有效 12 } 13 return method.invoke(realConnection, args); 14 } catch (Throwable t) { 15 throw ExceptionUtil.unwrapThrowable(t); 16 } 17 } 18 }
这个方法主要就是通过代理判断是不是close方法,是的的话并不是这届关闭,而是将其放回连接池中,供下次使用。
PoolState
这个类主要用来管理连接池的一些状态,没有
1 public class PoolState { 2 3 protected PooledDataSource dataSource; // 该poolstate属于哪个pooleddatasource 4 5 protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 来用存放空闲的 pooledConnection 连接 6 protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 用来存放活跃的 PooledConnection 连接 7 protected long requestCount = 0; // 请求数据库连接的次数 8 protected long accumulatedRequestTime = 0; // 获取连接的累计时间 9 protected long accumulatedCheckoutTime = 0; // 所有连接的累计从获取连接到归还连接的时长 10 protected long claimedOverdueConnectionCount = 0; // 连接超时的连接个数 11 protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // 累计超时时间 12 protected long accumulatedWaitTime = 0; // 累计等待时间 13 protected long hadToWaitCount = 0; // 等待次数 14 protected long badConnectionCount = 0; // 无效连接数
PooledDataSource
PooledDataSource 它是一个简单的,同步的,线程安全的数据库连接池,它使用UnpooledDataSource 来创建数据库连接,使用PooledConnection来管理数据库中的连接,使用PoolState来管理连接池的状态。下面我们就来一起看下这个类,首先看下相关的类变量:
1 public class PooledDataSource implements DataSource { 2 3 private final PoolState state = new PoolState(this); // 当前连接池的状态 4 5 private final UnpooledDataSource dataSource; // 用来创建真正的数据库连接对象 6 7 // OPTIONAL CONFIGURATION FIELDS 8 protected int poolMaximumActiveConnections = 10; // 最大活跃连接数 默认值 10 9 protected int poolMaximumIdleConnections = 5; // 最大空闲连接数 默认值 5 10 protected int poolMaximumCheckoutTime = 20000; // 最大获取连接的时长 默认值 20000 11 protected int poolTimeToWait = 20000; // 获取连接时,最大的等待时长 默认值 20000 12 protected String poolPingQuery = "NO PING QUERY SET"; // 检测连接是否可用时的测试sql 13 protected boolean poolPingEnabled; // 是否允许发送测试sql 14 protected int poolPingConnectionsNotUsedFor; // 当连接超过 poolPingConnectionsNotUsedFor 毫秒未使用时,会发送一次测试 SQL 语句,测试连接是否正常 15 16 private int expectedConnectionTypeCode; // 用来标识当前的连接池,是 url+username+password 的 hash 值
下面我们来看下从数据库获取连接的实现:
1 public Connection getConnection(String username, String password) throws SQLException { 2 return popConnection(username, password).getProxyConnection(); // 这边最终拿到的连接时代理的连接,不是真正的数据库连接 3 } 4 private PooledConnection popConnection(String username, String password) throws SQLException { 5 boolean countedWait = false; 6 PooledConnection conn = null; // pooledConnection对象 7 long t = System.currentTimeMillis(); 8 int localBadConnectionCount = 0; // 无效的连接个数 9 10 while (conn == null) { // pooledConnection代理对象为空 11 synchronized (state) { // 加锁操作 12 if (!state.idleConnections.isEmpty()) { // 判断当前idleConnection中是否存在空闲连接 13 // Pool has available connection 14 conn = state.idleConnections.remove(0); // 存在,则获取第一个连接 15 if (log.isDebugEnabled()) { 16 log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); 17 } 18 } else { // 以上是关于mybatis源码解析之Configuration加载的主要内容,如果未能解决你的问题,请参考以下文章Mybatis之Configuration初始化(配置文件.xml的解析)