水煮MyBatis(八)- 数据源的差异

90 阅读3分钟

前言

这一章主要理一下Mybatis里自带的三个数据源实现上的差异,不过需要注意的是,DataSource的实现不止三个,而且我们一般使用其他的数据源,比如HiKari、Druid等。

三个工厂

JNDI的比较特殊,是基于外部配置的,忽略不讲。Pooled工厂和Unpooled工厂是继承关系,主要是因为其读取配置的方式都是一致的,区别在于其管理连接资源的方式,后面会提到。
image.png

三个数据源

基于上面三个工厂,我们也主要介绍一下三个数据源

  • JNDI数据源:主要是给EJB或者容器外部配置数据源,比如tomcat的context.xml文件中中配置,不常见;
  • 池化数据源:链接资源初始化之后,保持活性,供不同线程使用;
  • 非池化数据源:获取链接时初始化,执行完成之后销毁;

JNDI数据源的一个例子

    @Test
    public void testJndi() throws Exception {
        InitialContext context = new InitialContext();
        //查找数据源
        Object datasourceRef = context.lookup("java:comp/env/jdbc/mybatis");
        DataSource dataSource = (DataSource) datasourceRef;
        Connection conn = dataSource.getConnection();
        // 执行sql语句
        ResultSet resultSet = conn.createStatement()
                .executeQuery("select * from tb_image ");
        log.info(">>>>>>>>>>>>>>>>>>>=>,{}", resultSet);
        conn.close();
    }

非连接池

非连接池工厂,这里主要看获取链接的方式,如下:

  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }

关注点:每次都会生成新的连接,然后也不会归还链接,执行完成之后直接关闭。

连接池

这个数据源获取连接的方式如下

  public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

可以看到,是从一个链接集合中pop一个资源并返回,如果获取不到,则根据配置判定是否需要新建一个链接。

popConnection的具体细节,篇幅略长,下一个章节单独介绍。

归还链接

这是PooledConnection源码中的invoke方法,代码中执行数据库查询或者更新时,判断具体的执行方法,如果是执行提交、回滚或者创建Statement等,则直接处理; 如果调用conn.close(),则将链接放回池中,以供复用。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 如果调用conn.close(),则将链接放回池中,以供复用
    if (CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    }
    try {
      // 执行提交、回滚或者创建Statement等
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

连接池的优势

先给出结论:优势主要在于创建连接的频率。

    @Test
    public void testConn() throws Exception {
        String sql = "select * from tb_image where md5 = ?";
        StopWatch sw = new StopWatch();
        Class.forName("com.mysql.jdbc.Driver");
        sw.start("create conn");
        Connection con = DriverManager.getConnection(
                "jdbc:mysql://192.168.1.172:3306/test8", "test", "123456");
        sw.stop();
        System.out.println("创建连接耗时:" + sw.getLastTaskTimeMillis() + " 毫秒");
        PreparedStatement st = con.prepareStatement(sql);
        //设置参数
        st.setString(1, "6e705a7733ac5gbwopmp02");
        sw.start("execute");
        //查询,得出结果集
        ResultSet rs = st.executeQuery();
        sw.stop();
        System.out.println("查询连接耗时:" + sw.getLastTaskTimeMillis() + " 毫秒");
        System.out.println(sw.prettyPrint());
    }

输出结果

创建连接耗时:263 毫秒
查询连接耗时:1 毫秒
StopWatch '': running time = 264639000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
263557200  100%  create conn
001081800  000%  execute

这个结果对比很直观,创建连接263毫秒,执行耗时1毫秒,连接池的优势显而易见。