MyBatis 连接池

1 MyBatis内置数据源类型

MyBatis支持三种内置的数据源类型:

  1. Pooled:实现dataSource接口,并且使用了池的思想。
  2. UNPooled:同样也是实现了dataSource接口,但是该类型并没有使用池的思想。
  3. JNDI:采用服务器提供的JNDI技术实现,并且在不同服务器之间获取的连接池不一样。

2 MyBatis数据源配置

下面是MyBatis的SqlMapConfig.xml配置中数据源的配置:

<!-- 数据库连接池 -->
<dataSource type="POOLED">
   <property name="driver" value="${jdbc.driver}" />
   <property name="url" value="${jdbc.url}" />
   <property name="username" value="${jdbc.username}" />
   <property name="password" value="${jdbc.password}" />
</dataSource>

3 UNPooled

这种方式是不具备连接池的思想,如果频繁的创建和销毁连接对象,会影响程序的运行效率。

3.1 UNPooled配置

<dataSource type="unpooled">
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEcndoing=utf8"/>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</dataSource>

3.2 UNPooled源码分析

1)获取连接的源码

@Override
public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
}

2)接着再进入到doGetConnection()方法中:

private Connection doGetConnection(String username, String password) throws SQLException {
  // 实例化一个集合
  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);
}

3)此时又返回一个doGetConnection(),这是重载的另一个方法。

private Connection doGetConnection(Properties properties) throws SQLException {
  // 初始化
  initializeDriver();
  // 获取一个连接对象
  Connection connection = DriverManager.getConnection(url, properties);
  configureConnection(connection);
  return connection;
}

4)第一行代码中,调用了initializeDriver()方法。

private synchronized void initializeDriver() throws SQLException {
  if (!registeredDrivers.containsKey(driver)) {
    Class<?> driverType;
    try {
      if (driverClassLoader != null) {
          // 使用反射获取到连接驱动
        driverType = Class.forName(driver, true, driverClassLoader);
      } else {
        driverType = Resources.classForName(driver);
      }
      // DriverManager requires the driver to be loaded via the system ClassLoader.
      // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
      // 实例化连接驱动
        Driver driverInstance = (Driver)driverType.newInstance();
        // 注册驱动
      DriverManager.registerDriver(new DriverProxy(driverInstance));
      registeredDrivers.put(driver, driverInstance);
    } catch (Exception e) {
      throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
    }
  }
}

大致流程:

  1. 在获取连接对象时,调用initializeDriver()方法判断是否已经注册连接驱动。
  2. 完成驱动注册,使用DriverManager.getConnection获取一个连接对象。
  3. 将连接对象交给configureConnection()方法,并设置自动提交事务,及事务的隔离级别。
private void configureConnection(Connection conn) throws SQLException {
  if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
    conn.setAutoCommit(autoCommit);
  }
  if (defaultTransactionIsolationLevel != null) {
    conn.setTransactionIsolation(defaultTransactionIsolationLevel);
  }
}

4 Pooled

连接池就是用于存储连接对象的一个容器。而容器就是一个集合,且必须是线程安全的,即两个线程不能拿到同一个连接对象。同时还要具备队列的特性:先进先出原则。

使用连接池的好处:避免频繁创建和关闭数据库连接造成的开销,节省系统资源。

4.1 Pooled配置

<dataSource type="pooled">
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEcndoing=utf8"/>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</dataSource>

4.2 Pooled源码分析

1)获取连接源码

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

2)调用popConnection()方法

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) {
      if (!state.idleConnections.isEmpty()) {
        // Pool has available connection
        conn = state.idleConnections.remove(0);
        if (log.isDebugEnabled()) {
          log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
        }
      } else {
        // Pool does not have available connection
        if (state.activeConnections.size() < poolMaximumActiveConnections) {
          // Can create new connection
          conn = new PooledConnection(dataSource.getConnection(), this);
          if (log.isDebugEnabled()) {
            log.debug("Created connection " + conn.getRealHashCode() + ".");
          }
        } else {
          // Cannot create new connection
          PooledConnection oldestActiveConnection = state.activeConnections.get(0);
          long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
          if (longestCheckoutTime > poolMaximumCheckoutTime) {
            // Can claim overdue connection
            state.claimedOverdueConnectionCount++;
            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
            state.accumulatedCheckoutTime += longestCheckoutTime;
            state.activeConnections.remove(oldestActiveConnection);
            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
              try {
                oldestActiveConnection.getRealConnection().rollback();
              } catch (SQLException e) {
                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 {
            // Must wait
            try {
              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 not
        if (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. 判断连接池中是否有空闲的连接对象,有则直接返回。
  2. 如果连接池没有空闲的连接,先判断活动连接池是否小于连接池承载的最大数量,小于则再创建新的连接对象。
  3. 但是如果连接池已经达到最大承载数量,那么在连接池中就把最先进来的连接(oldest)返回出去。

5 JNDI

JNDI(Java Naming and Directory Interface),是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表。

如果将类型设置成JNDI,MyBatis从应用服务器(如:Tomcat)配置好的JNDI数据源获取数据源连接。

注意:MyBatis需要使用JNDI作为数据源,必须为Web项目。

5.1 创建Web工程,导入依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  <modelVersion>4.0.0</modelVersion>  
  <groupId>com.yiidian</groupId>  
  <artifactId>ch02_07_mybatis_datasource</artifactId>  
  <version>1.0-SNAPSHOT</version>  
  <packaging>war</packaging>
  <dependencies> 
    <!--mysql驱动-->  
    <dependency> 
      <groupId>mysql</groupId>  
      <artifactId>mysql-connector-java</artifactId>  
      <version>5.1.46</version> 
    </dependency>  
    <!-- mybatis依赖-->  
    <dependency> 
      <groupId>org.mybatis</groupId>  
      <artifactId>mybatis</artifactId>  
      <version>3.5.3</version> 
    </dependency>  
    <dependency> 
      <groupId>log4j</groupId>  
      <artifactId>log4j</artifactId>  
      <version>1.2.17</version> 
    </dependency>  
    <dependency> 
      <groupId>junit</groupId>  
      <artifactId>junit</artifactId>  
      <version>4.12</version> 
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
  </dependencies> 
</project>

5.2 创建和配置context.xml配置

在webapp目录建立META-INF目录,在里面建立context.xml文件

内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource
            name="jdbc/mybatis"
            type="javax.sql.DataSource"
            auth="Container"
            maxActive="20"
            maxWait="10000"
            maxIdle="5"
            username="root"
            password="root"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/mybatis"
    />
</Context>

以上配置也可以配置在Tomcat的server.xml文件里面:

Tomcat的server.xml

<Context>


  <Resource
            name="jdbc/mybatis"
            type="javax.sql.DataSource"
            auth="Container"
            maxActive="20"
            maxWait="10000"
            maxIdle="5"
            username="root"
            password="root"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/mybatis"
    />

</Context>

5.3 SqlMapConfig.xml配置

<environments default="mysql">
    <environment id="mysql">
        <transactionManager type="jdbc"></transactionManager>
        <dataSource type="JNDI">
            <property name="data_source" value="java:comp/env/jdbc/mybatis"/>
        </dataSource>
    </environment>
</environments>

5.4 编写Servlet调用Dao方法

package com.yiidian.web;

import com.yiidian.dao.CustomerDao;
import com.yiidian.domain.Customer;
import com.yiidian.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 控制器
 * 一点教程网 - www.yiidian.com
 */
public class CustomerServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //2.生成Dao代理对象
        CustomerDao customerDao = session.getMapper(CustomerDao.class);
        //3.调用方法
        List<Customer> list = customerDao.findAll();
        System.out.println(list);

        //4.关闭连接
        session.close();
    }
}

5.5 MyBatisUtils工具类内容

package com.yiidian.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
 * 工具类
 *一点教程网 - www.yiidian.com
 */
public class MyBatisUtils {
    private static SqlSessionFactoryBuilder builder;
    private static SqlSessionFactory factory;

    /**
     * 初始化SqlSessionFactory
     */
    static {
        try {
            builder = new SqlSessionFactoryBuilder();
            InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
            factory = builder.build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取sqlSession
     */
    public static SqlSession getSession(){
        return factory.openSession();
    }
}

5.6 部署到Tomcat,访问测试

http://localhost:8080/customer

 

源码下载:https://pan.baidu.com/s/1stjv6RB_FmTGN2vmVWAonA

热门文章

优秀文章