Mybatis学习笔记-数据源与连接池

参考:http://blog.csdn.net/luanlouis/article/details/37671851

对于应用程序来说,与数据库的交互是必不可少的。但对于大多数应用来说,数据访问对象(Dao)的性能是整个应用的一个瓶颈点,目前比较成熟的解决方案是利用数据库连接池对数据库连接(Connection)进行本地缓存,避免频繁的创建数据库连接。

Mybatis作为当前最流行的数据访问层ORM框架之一,对连接池技术做了很好的集成,下面就来探究一下Mybatis的数据源与连接池的实现。

  • Mybatis数据源的分类
    这里写图片描述
    通过观察Mybatis核心包或查看帮助文档可知,有三种数据源:
    1)UNPOOLED:每次请求时简单的打开数据库连接。
    2)POOLED:每次请求时从连接池中取得连接。
    3)JNDI:从容器上下文的数据源中(JNDI)获取连接。

    接口关系如下所示:
    这里写图片描述

  • 数据源的创建过程

    Mybatis中数据源的创建是由对应的数据源工程来实现的,数据源工厂的顶级接口为DataSourceFactory。下面是数据源工厂体系的类图:

    这里写图片描述

1)读取mybatis核心配置文件<environments/>标签

<environments default="mysql_environment">
        <environment id="mysql_environment">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
                <property name="username" value="zhangdd" />
                <property name="password" value="zd1991.." />
            </dataSource>
        </environment>
    </environments> 

根据<dataSource/>的type类型来选择用不同的工厂类创建对应的数据源。

  • type=”POOLED”,创建PooledDataSource实例
  • type=”UNPOOLED”,创建UnpooledDataSource实例
  • type=”JNDI”,从容器上下文中查找并获取数据源实例

    注:mybatis的在执行具体的sql语句的时候才调用DataSource的getConnection()的。

  • 为什么要使用连接池技术

我们来看一下下面的例子

public class CreateConnectionTest {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "zhangdd";
        String password = "zd1991..";
        long beforeTimeOffset = -1L; //创建Connection对象前时间  
        long afterTimeOffset = -1L; //创建Connection对象后时间  
        long executeTimeOffset = -1L;  //执行sql的时间
        PreparedStatement pstmt;
        ResultSet rs;
        String sql = "select * from employee where id = ?";


        Class.forName("com.mysql.jdbc.Driver");

        beforeTimeOffset = new Date().getTime();
        System.out.println("before create:" + beforeTimeOffset);

        Connection connection = DriverManager.getConnection(url, user, password);

        afterTimeOffset = new Date().getTime();
        System.out.println("after create:" + afterTimeOffset);

        System.out.println("create cost:" + (afterTimeOffset - beforeTimeOffset) + "ms");


        pstmt = connection.prepareStatement(sql);
        pstmt.setInt(1, 1);
        rs = pstmt.executeQuery();
        executeTimeOffset = new Date().getTime();
        System.out.println("execute time:" + executeTimeOffset);
        System.out.println("execute cost:" + (executeTimeOffset - afterTimeOffset) + "ms");

        connection.close();
    }
}

结果为:

before create:1438840632462
after create:1438840632776
create cost:314ms
execute time:1438840632789
execute cost:13ms

由此可见,创建数据库连接是及其耗时的,如果一个应用需要频繁的操作数据库,那绝大多数时间会浪费在创建数据库连接而不是处理业务数据上。

解决以上问题的办法是事先创建好一部分连接放到内存中,当程序需要数据库连接时,直接从内存中获取,而不需要与数据库重新建立socket连接。数据库连接池是上述方案的非常好的实现。

  • Mybatis数据库连接池的实现

    基本原理
    首先mybatis将创建好的Connection对象装饰城PooledConnection,

public PooledConnection(Connection connection,  
   PooledDataSource dataSource) {
    this.hashCode = connection.hashCode();
    this.realConnection = connection;
    this.dataSource = dataSource;
    this.createdTimestamp = System.currentTimeMillis();
    this.lastUsedTimestamp = System.currentTimeMillis();
    this.valid = true;

    proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}

然后将其放入到PoolState(存储连接的容器)中,在PooledState中有两个用于存放PooledConnection的容器,其中一个是空闲连接容器(idleConnections),用于存放空闲的PooledConnection,另一个是活动连接容器(activeConnections),用于存放正在使用的PooledConnection。

protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
  protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

重点来了…
从mybatis连接池中获取连接时:
1)首先判断idleConnections 中是否有空闲的连接,若有,则从idleConnections 中直接返回一个空闲的PooledConnection,若没有,则进行 2)

2)查看活动池activeConnections 是否已满,如果未满,则创建一个新的PooledConnection对象,并放入到activeConnections 中,并返回该对象,若activeConnections 已满,则进行 3)

3)查看最先加入到activeConnections 中的PooledConnection对象是否已经过期,若已过期,则将次对象从activeConnections 中移除,创建一个新的PooledConnection对象并将其加入到activeConnections 中。
若没有过期的PooledConnection对象,则进行 4)

4)进入等待状态,重复2)

下一个重点…
在传统JDBC中的Connection对象上调用close()方法时执行的操作的断开本次连接,但现在应用的连接池技术,就不能将该连接直接断开,而是将连接返回到连接池PoolState中。
那么,mybatis是怎么实现的呢?前面已经说过,mybatis将Connection对象封装成PooledConnection,同时PooledConnection是一个InvocationHandler,这是重点

class PooledConnection implements InvocationHandler

这样一来,PooledConnection对象就是Connection的代理对象,当调用Connection接口的方法时,代理对象PooledConnection对象就要利用invoke()方法进行过滤,当过滤到close()方法时,执行了一些特别的动作

 public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        if (method.getDeclaringClass() != Object.class) {
          checkConnection();
        }
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

从上面的代码中可以看到,close()对应的动作是

 dataSource.pushConnection(this);

综上所述,mybatis利用动态代理改写了Connection接口中的close()方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值