继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

源码分析----Mybatis数据源与连接池

慕码人8056858
关注TA
已关注
手记 1238
粉丝 350
获赞 1323

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

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


以下是本文的组织结构:

  • 一、MyBatis数据源DataSource分类

  • 二、数据源DataSource的创建过程

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

  • 四、不使用连接池的UnpooledDataSource

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

  • 六、使用了连接池的PooledDataSource

一、MyBatis数据源DataSource分类

MyBatis数据源实现是在以下四个包中:

webp

image.png

MyBatis把数据源DataSource分为三种:
1、UNPOOLED     不使用连接池的数据源
2、POOLED          使用连接池的数据源
3、JNDI                 使用JNDI实现的数据源

相应地,MyBatis内部分别定义了实现了java.sql.DataSource接口的UnpooledDataSource,PooledDataSource类来表示UNPOOLED、POOLED类型的数据源。 如下图所示:

webp

image.png

二、数据源DataSource的创建过程

MyBatis数据源DataSource对象的创建发生在MyBatis初始化的过程中。下面让我们一步步地了解MyBatis是如何创建数据源DataSource的。

在mybatis的XML配置文件中(spring-jdbc.xml),使用元素来配置数据源:

<bean id="dataSource" class="com.xxxxx.jdbc.XxxxDataSource"  type="POOLED"
          init-method="init" destroy-method="close">
        <property name="poolType" value="tomcat-jdbc" />
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="initialPoolSize" value="5" />
        <property name="checkoutTimeout" value="1000" />
        <property name="connectionInitSql" value="set names utf8mb4" />
        <property name="lazyInit" value="${jdbc_lazyinit}" />
        <!--每60秒运行一次空闲连接回收器
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>-->
        <!--池中的连接空闲180秒后被回收-->
        <property name="minEvictableIdleTimeMillis" value="180000"/>
        <property name="socketTimeout" value="120000"/>
    </bean>
  1. MyBatis在初始化时,解析此文件,根据的type属性来创建相应类型的的数据源DataSource,即:

  • type=”POOLED”  :MyBatis会创建PooledDataSource实例

  • type=”UNPOOLED” :MyBatis会创建UnpooledDataSource实例

  • type=”JNDI”     :MyBatis会从JNDI服务上查找DataSource实例,然后返回使用

  1. 顺便说一下,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

其类图如下所示:

webp

image.png

  1. 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()方法的呢?请看下一节。

四、不使用连接池的UnpooledDataSource

当 的type属性被配置成了”UNPOOLED”,MyBatis首先会实例化一个UnpooledDataSourceFactory工厂实例,然后通过.getDataSource()方法返回一个UnpooledDataSource实例对象引用,我们假定为dataSource。

使用UnpooledDataSource的getConnection(),每调用一次就会产生一个新的Connection实例对象。

UnPooledDataSource的getConnection()方法实现如下:

    /**
     * UnpooledDataSource的getConnection()实现
     */
    public Connection getConnection() throws SQLException {        return doGetConnection(username, password);
    } 
    private Connection doGetConnection(String username, String password) throws SQLException {        //封装username和password成properties
        Properties props = new Properties();        if (driverProperties != null) {
            props.putAll(driverProperties);
        }        if (username != null) {
            props.setProperty("user", username);
        }        if (password != null) {
            props.setProperty("password", password);
        }        return doGetConnection(props);
    } 
    /*
     *  获取数据连接
     */
    private Connection doGetConnection(Properties properties) throws SQLException {        //1.初始化驱动
        initializeDriver();        //2.从DriverManager中获取连接,获取新的Connection对象
        Connection connection = DriverManager.getConnection(url, properties);        //3.配置connection属性
        configureConnection(connection);        return connection;
    }

如上代码所示,UnpooledDataSource会做以下事情:

  1. 初始化驱动:   判断driver驱动是否已经加载到内存中,如果还没有加载,则会动态地加载driver类,并实例化一个Driver对象,使用DriverManager.registerDriver()方法将其注册到内存中,以供后续使用。

  2. 创建Connection对象:    使用DriverManager.getConnection()方法创建连接。

  3. 配置Connection对象:   设置是否自动提交autoCommit和隔离级别isolationLevel。

  4. 返回Connection对象。

上述的序列图如下所示:

webp

image.png

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

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

1. 创建一个java.sql.Connection实例对象的代价

首先让我们来看一下创建一个java.sql.Connection对象的资源消耗。我们通过连接Oracle数据库,创建创建Connection对象,来看创建一个Connection对象、执行SQL语句各消耗多长时间。代码如下:

public static void main(String[] args) throws Exception{
 
    String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?";
    PreparedStatement st = null;
    ResultSet rs = null; 
    long beforeTimeOffset = -1L; //创建Connection对象前时间
    long afterTimeOffset = -1L; //创建Connection对象后时间
    long executeTimeOffset = -1L; //创建Connection对象后时间
 
    Connection con = null;
    Class.forName("oracle.jdbc.driver.OracleDriver");
 
    beforeTimeOffset = new Date().getTime();
    System.out.println("before:\t" + beforeTimeOffset);
 
    con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:xe", "louluan", "123456");
 
    afterTimeOffset = new Date().getTime();
    System.out.println("after:\t\t" + afterTimeOffset);
    System.out.println("Create Costs:\t\t" + (afterTimeOffset - beforeTimeOffset) + " ms");
 
    st = con.prepareStatement(sql);    //设置参数
    st.setInt(1, 101);
    st.setInt(2, 0);    //查询,得出结果集
    rs = st.executeQuery();
    executeTimeOffset = new Date().getTime();
    System.out.println("Exec Costs:\t\t" + (executeTimeOffset - afterTimeOffset) + " ms");
 
}

上述程序的执行结果为:

webp

image.png



作者:消失er
链接:https://www.jianshu.com/p/45c1f4691c72


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP