300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 《深入理解Mybatis原理》 02-Mybatis数据源与连接池

《深入理解Mybatis原理》 02-Mybatis数据源与连接池

时间:2019-04-05 18:25:31

相关推荐

《深入理解Mybatis原理》 02-Mybatis数据源与连接池

对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题。本文将通过对MyBatis框架的数据源结构进行详尽的分析,并且深入解析MyBatis的连接池。

本文首先会讲述MyBatis的数据源的分类,然后会介绍数据源是如何加载和使用的。紧接着将分类介绍UNPOOLED、POOLED和JNDI类型的数据源组织;期间我们会重点讲解POOLED类型的数据源和其实现的连接池原理。

本文结构如下:

一、MyBatis数据源DataSource分类 二、数据源DataSource的创建过程 三、 DataSource什么时候创建Connection对象 四、不使用连接池的UnpooledDataSource 五、为什么要使用连接池? 六、使用了连接池的PooledDataSource

一、Mybatis数据源分类

mybatis数据源实现类在mybatis的dataSource包中:

Mybatis将数据源分为三种:

JNDI 数据源 : 使用JNDI方式数据源

POOLED数据眼: 使用连接池数据源

UNPOOLED 数据源 : 不使用连接池数据源

即:

相应的Mybatis内部分别以实现 javax.sql.DataSource 接口的 PooledDataSource和 UnPooledDataSource 实现 POOLED和UNPOOLED数据源。(关于数据源创建细节请看下面章节)

JNDI数据源则通过 javax.naming.Context 上下文生成数据源。

二、数据源DataSource创建过程

数据源配置如下:

<dataSource type="UNPOOLED"><property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/pmdb"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource>

Mybatis数据源的创建过程:

Mybatis初始化阶段 就会创建好数据源,具体创建数据源的时机发生在解析mybatis XML配置文件<environments></environments> 节点下的<dataSource></dataSouorce>节点:

//context :dataSource节点数据private DataSourceFactory dataSourceElement(XNode context) throws Exception {if (context != null) {//获取dataSource配置的类型 (POOLED、UNPOOLED、JNDI)String type = context.getStringAttribute("type");//将dataSource下的username、password等信息解析为PropertiesProperties props = context.getChildrenAsProperties();//根据dataSource的type类型(别名机制)获取到对应的DateSource实现类,并实例化该类DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a DataSourceFactory.");}

创建DataSource最关键的一步在: DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();

打开该方法resolveClass方法实现,看它到底做了什么:

public <T> Class<T> resolveAlias(String string) {try {if (string == null) {return null;}String key = string.toLowerCase(Locale.ENGLISH);Class<T> value;if (TYPE_ALIASES.containsKey(key)) {value = (Class<T>) TYPE_ALIASES.get(key);} else {value = (Class<T>) Resources.classForName(string);}return value;} catch (ClassNotFoundException e) {throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);}}

resolveClass方法核心功能就是根据XML dataSource节点配置的type属性找到对应的实现类:

如上图所示:

根据配置的type别名找到Factory,然后创建出对应的DataSource

JNDI : JndiDataSourceFactoryPOOLED: PooledDataSourceFactoryUNPOOLED: UnpooledDataSourceFactory

Mybatis创建DataSource之后会将其放在Configuration的Environment中,供以后使用。

三、DataSource什么时候创建Connection对象

InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();User user = (User) sqlSession.selectOne("selectByPrimaryKey", 1);

如上图所示,前三行代码都不会去创建javax.sql.Connection,当执行到 selectOne("selectByPrimaryKey", 1);时,才会去真正创建Connection对象:

@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException {Statement stmt = null;try {flushStatements();Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}

那对于UNPOOLED类型DataSource的实现UnpooledDataSource是怎么样实现getConnection方法呢?请看一下节。

四、不使用连接池的UnpooledDataSource

//UnpoolDataSource 创建Connection对象private Connection doGetConnection(Properties properties) throws SQLException {//1. 初始化驱动initializeDriver();//2. 创建Connection对象Connection connection = DriverManager.getConnection(url, properties);//3. 配置ConnectionconfigureConnection(connection);return connection;}

如上代码所示,流程如下:

1. 初始化驱动 : 判断驱动是否加载到内存中,若有则直接取出,否则创建驱动 2. 创建Connecion : 调用DriverManager创建相应的Connection对象 3. 配置Connection: 配置Connection对象一些默认配置项 4. 返回DataSource对象: 返回创建好的Connection对象供以使用

总结:从上述的代码中可以看到,我们每调用一次getConnection()方法,都会通过DriverManager.getConnection()返回新的java.sql.Connection实例。

五、为什么要使用连接池?

public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {long start = System.currentTimeMillis();Class.forName("com.mysql.jdbc.Driver");Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mall", "root", "123456");System.out.println("创建Connection对象耗时 : " + String.valueOf(System.currentTimeMillis() - start));String sql = "select * from mmall_user where id = 1";start = System.currentTimeMillis();Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(sql);System.out.println("查询语句耗时:" + String.valueOf(System.currentTimeMillis() - start));while(resultSet.next()){System.out.println("id = " + resultSet.getInt(1) + " username = " + resultSet.getString(2));}resultSet.close();statement.close();connection.close();}

创建Connection对象耗时 789ms, 而查询语句才耗时7ms.(不排除数据库数据少的原因,但是查询耗时一般不会超过789ms)

一次查询请求创建Connection对象耗时789ms。要知道100ms对于Java来说都是很奢侈的。(一个Connection对象耗时 700ms,10000 * 700 = 116分钟,10000次请求只创建对象就耗时116分钟,这是根本不能接受的)

所以使用连接池是非常有必要的。

六、使用了连接池的PooledDataSource

了解连接池之前,先了解两个参数概念:

idleConnections : 空闲Connection对象,当其他请求需要创建Connection时,直接到ideaConnection取出一个连接,可以减少资源、耗时。

activeConnections : 活动Connection对象,记录当前正在被请求所使用的Connection对象,当一次请求使用完一个Connection时,不将其立即销毁,而是放到idleConnection缓存池里面。

对于UnpooledDataSource每次请求都会创建一个新的Connection对象,当请求结束后会执行Connection.cloes()方法关闭该Connection.

PooledDataSource是如何创建的Connection的呢?

@Overridepublic Connection getConnection() throws SQLException {return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return popConnection(username, password).getProxyConnection();}

数据源type设置为 POOLED,当实例化DataSource时会根据别名实例化出 PooledDataSource对象。

当调用getConnection方法创建Connection时,最终会调用 popConnecion方法并返回一个代理对象。

private PooledConnection popConnection(String username, String password) throws SQLException {boolean countedWait = false;PooledConnection conn = null;long t = System.currentTimeMillis();int localBadConnectionCount = 0;while (conn == null) {synchronized (state) {//判断连接池中是否还有空闲Connection对象,若有则直接返回一个Connectionif (!state.idleConnections.isEmpty()) {// Pool has available connectionconn = state.idleConnections.remove(0);if (log.isDebugEnabled()) {log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");}} else {//没有空闲Connection对象//当前活动对象个数是小于最大活动数量 则会生成一个新的Connection对象if (state.activeConnections.size() < poolMaximumActiveConnections) {// Can create new connectionconn = new PooledConnection(dataSource.getConnection(), this);if (log.isDebugEnabled()) {log.debug("Created connection " + conn.getRealHashCode() + ".");}} else {//判断老的活动对象是否超过poolMaximumCheckoutTime时间PooledConnection oldestActiveConnection = state.activeConnections.get(0);long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();//超过poolMaximumCheckoutTime时间,则尝试结束该Connection对象线程,并返回重用Connectionif (longestCheckoutTime > poolMaximumCheckoutTime) {// Can claim overdue connectionstate.claimedOverdueConnectionCount++;state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;state.accumulatedCheckoutTime += longestCheckoutTime;state.activeConnections.remove(oldestActiveConnection);if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {try {oldestActiveConnection.getRealConnection().rollback();} catch (SQLException e) {/*Just log a message for debug and continue to execute the followingstatement like nothing happend.Wrap the bad connection with a new PooledConnection, this will helpto not intterupt current executing thread and give current thread achance to join the next competion for another valid/good databaseconnection. At the end of this loop, bad {@link @conn} will be set as null.*/log.debug("Bad connection. Could not roll back");} }conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());oldestActiveConnection.invalidate();if (log.isDebugEnabled()) {log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");}} else {//没有超时,则等待该Connection线程结束// Must waittry {if (!countedWait) {state.hadToWaitCount++;countedWait = true;}if (log.isDebugEnabled()) {log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");}long wt = System.currentTimeMillis();state.wait(poolTimeToWait);state.accumulatedWaitTime += System.currentTimeMillis() - wt;} catch (InterruptedException e) {break;}}}}if (conn != null) {// ping to server and check the connection is valid or notif (conn.isValid()) {if (!conn.getRealConnection().getAutoCommit()) {conn.getRealConnection().rollback();}conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));conn.setCheckoutTimestamp(System.currentTimeMillis());conn.setLastUsedTimestamp(System.currentTimeMillis());state.activeConnections.add(conn);state.requestCount++;state.accumulatedRequestTime += System.currentTimeMillis() - t;} else {if (log.isDebugEnabled()) {log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");}state.badConnectionCount++;localBadConnectionCount++;conn = null;if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {if (log.isDebugEnabled()) {log.debug("PooledDataSource: Could not get a good connection to the database.");}throw new SQLException("PooledDataSource: Could not get a good connection to the database.");}}}}}if (conn == null) {if (log.isDebugEnabled()) {log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");}throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");}return conn;}

综上所述,大致流程如下:

1. 若IdleConnection中是否有空闲的连接对象,则直接返回Connection2. 判断当前activeConnection数量是否小于poolMaximumActiveConnections(活动连接最大数量),若小于,则和Unpooled方式一样创建新的Connection对象并返回。若大于,则会判断当前所有活动连接的占用时间是否超时,若超时则停止该Connection,并直接返回供其他请求使用。若没有超时,则等待Connection使用完毕后再返回。

连接池对Connection.close()的处理:

PooledDataSource中除了popConnection方法,还有一个pushConnection方法pushConnection方法会将使用完毕的Connection放入idleConnections缓存池中,供其他请求继续使用。传统的jdbc连接使用完Connection之后,会手动执行Connection.cloes()方法关闭连接。Pooled连接池为了重复利用Connection减少不必要的开销,对Connection.cloes做了动态代理。也就是说,在Pooled模式下,若我们手动执行connecion.cloes(),实际上并不会执行原生Connection.close方法。而是通过PooledConnection对原生Connection做动态代理,把close方法映射到 pushConnection方法上:@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();//若执行close方法,实际上会代理执行pushConnection方法if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {dataSource.pushConnection(this);return null;} else {try {if (!Object.class.equals(method.getDeclaringClass())) {// issue #579 toString() should never fail// throw an SQLException instead of a RuntimecheckConnection();}return method.invoke(realConnection, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}}

以上就是本文《深入理解Mybatis原理》 02-Mybatis数据源与连接池的全部内容,

上述内容如有不妥之处,还请读者指出,共同探讨,共同进步!

@author : jackcheng1117@

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。