Mybatis中的数据库连接池
mybatis中的数据库连接池
一、前言
在数据库连接和事务以及线程之间的关系这个章节中聊到了为什么需要数据库连接池,下面将从代码实战中来进行描述,然后来看看mybatis框架中利用的数据库连接池
二、为什么要使用数据库连接池
1、创建一个java.sql.Connection实例对象的代价
首先让我们来看一下创建一个java.sql.Connection对象的资源消耗。我们通过连接Oracle数据库,创建创建Connection对象,来看创建一个Connection对象、执行SQL语句各消耗多长时间。代码如下:
public class JDBCTest {
public static void main(String[] args) throws SQLException {
long start = new Date().getTime();
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ie_draw", "root", "root");
long end = new Date().getTime();
System.out.println("创建数据库连接消耗的时间是:"+(end-start));
String sql = "select count(*) from template";
start = new Date().getTime();
PreparedStatement preparedStatement = connection.prepareStatement("select count(*) from template");
ResultSet resultSet = preparedStatement.executeQuery();
end = new Date().getTime();
System.out.println("耗时:"+(end-start));
}
}
上述程序在我笔记本上的执行结果为:
创建数据库连接消耗的时间是:900
耗时:32
这让我多少是有点无语的。创建一个数据库连接的时间过长,而执行一个SQL的时间太短。
创建一个Connection对象用了900毫秒!这个时间对计算机来说可以说是一个非常奢侈的!
这仅仅是一个Connection对象就有这么大的代价,设想一下另外一种情况:如果我们在Web应用程序中,为用户的每一个请求就操作一次数据库,当有10000个在线用户并发操作的话,对计算机而言,仅仅创建Connection对象不包括做业务的时间就要损耗10000×900ms= 900 0000 ms = 9000 s = 150 min,竟然要150分钟!!!如果对高用户群体使用这样的系统,简直就是开玩笑!
2、问题分析
创建一个java.sql.Connection对象的代价是如此巨大,是因为创建一个Connection对象的过程,在底层就相当于和数据库建立的通信连接,在建立通信连接的过程,消耗了这么多的时间,而往往我们建立连接后(即创建Connection对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!
3、解决方案
对于需要频繁地跟数据库交互的应用程序,可以在创建了Connection对象,并操作完数据库后,可以不释放掉资源,而是将它放到内存中,当下次需要操作数据库时,可以直接从内存中取出Connection对象,不需要再创建了,这样就极大地节省了创建Connection对象的资源消耗。由于内存也是有限和宝贵的,这又对我们对内存中的Connection对象怎么有效地维护提出了很高的要求。我们将在内存中存放Connection对象的容器称之为 连接池(Connection Pool)。
三、Mybatis数据库连接池
下面让我们来看一下MyBatis的线程池是怎样实现的。
mybatis中datasource分类
MyBatis把数据源DataSource分为三种:
- UNPOOLED 不使用连接池的数据源
- POOLED 使用连接池的数据源
- JNDI 使用JNDI实现的数据源
即:
相应地,MyBatis内部分别定义了实现了java.sql.DataSource接口的UnpooledDataSource,PooledDataSource类来表示UNPOOLED、POOLED类型的数据源。 如下图所示:
对于JNDI类型的数据源DataSource,则是通过JNDI上下文中取值。
mybatis中的数据库连接池创建
MyBatis数据源DataSource对象的创建发生在MyBatis初始化的过程中。
下面让我们一步步地了解MyBatis是如何创建数据源DataSource的。
在mybatis的XML配置文件中,使用
MyBatis在初始化时,解析此文件,根据
type=”POOLED” :MyBatis会创建PooledDataSource实例
type=”UNPOOLED” :MyBatis会创建UnpooledDataSource实例
type=”JNDI” :MyBatis会从JNDI服务上查找DataSource实例,然后返回使用
DataSourceFactory
顺便说一下,MyBatis是通过工厂模式来创建数据源DataSource对象的,MyBatis定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory,通过其getDataSource()方法返回数据源DataSource:
定义如下:
public interface DataSourceFactory {
void setProperties(Properties props);
//生产DataSource
DataSource getDataSource();
}
上述三种不同类型的type,则有对应的以下dataSource工厂:
POOLED PooledDataSourceFactory
UNPOOLED UnpooledDataSourceFactory
JNDI JndiDataSourceFactory
其类图如下所示:
MyBatis创建了DataSource实例后,会将其放到Configuration对象内的Environment对象中, 供以后使用
DataSource什么时候创建Connection对象
当我们需要创建SqlSession对象并需要执行SQL语句时,这时候MyBatis才会去调用dataSource对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。
这里同样的,按照我们的之前的思想,一个线程和一个数据库连接对应的生命周期中。数据库连接的使用只是占据了一个线程的使用过程中一小部分。
如果存在着大量请求,我们的理想效果是,线程可以复用,数据库连接可以复用,那么系统处理效率高。
可以说,如果单位时间内,某个数据库连接被复用的频率越高,可以认为系统响应效率越快。
比如,我们有如下方法执行一个简单的SQL语句:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.selectList("SELECT * FROM STUDENTS");
前4句都不会导致java.sql.Connection对象的创建,只有当第5句sqlSession.selectList("SELECT * FROM STUDENTS"),才会触发MyBatis在底层执行下面这个方法来创建java.sql.Connection对象:
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
而对于DataSource的UNPOOLED的类型的实现-UnpooledDataSource是怎样实现getConnection()方法的呢?
具体的参考:https://louluan.blog.csdn.net/article/details/37671851
因为我想的是在spring整合mybatis之后,肯定不会使用这种数据库连接池。
所以我在这里不再来进行研究。
四、事务管理器
说起来事务管理器,我直到今天才真正的知道事务管理器是干嘛的。
原来事务管理器就是根据一个数据库连接来进行设置,开启事务的。
在mybatis中对应的时候一个TransactionFactory类来表示的。
看一下接口规范定义:
public interface TransactionFactory {
/**
* Sets transaction factory custom properties.
* @param props
*/
default void setProperties(Properties props) {
// NOP
}
/**
* Creates a {@link Transaction} out of an existing connection.
* @param conn Existing database connection
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(Connection conn);
/**
* Creates a {@link Transaction} out of a datasource.
* @param dataSource DataSource to take the connection from
* @param level Desired isolation level
* @param autoCommit Desired autocommit
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}