hi,你好!欢迎访问本站!登录
本站由简数采集腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

Mybaits 源码剖析 (七)----- Select 语句的实行历程剖析(下篇)

2019-11-18杂谈搜奇网32°c
A+ A-

我们上篇文章讲到了查询要领内里的doQuery要领,这内里就是挪用JDBC的API了,个中的逻辑比较复杂,我们这边文章来讲,先看看我们上篇文章剖析的处所

SimpleExecutor

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4         Configuration configuration = ms.getConfiguration();
 5         // 建立 StatementHandler
 6         StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 7         // 建立 Statement
 8         stmt = prepareStatement(handler, ms.getStatementLog());  9         // 实行查询操纵
10         return handler.<E>query(stmt, resultHandler); 11     } finally {
12         // 封闭 Statement
13         closeStatement(stmt);
14     }
15 }

上篇文章我们剖析完了第6行代码,在第6行处我们建立了一个PreparedStatementHandler,我们要接着第8行代码最先剖析,也就是建立 Statement,先不忙着剖析,我们先来回忆一下 ,我们之前是怎样运用jdbc的

jdbc

public class Login {
    /**
     *    第一步,加载驱动,建立数据库的衔接
     *    第二步,编写sql
     *    第三步,须要对sql举行预编译
     *    第四步,向sql内里设置参数
     *    第五步,实行sql
     *    第六步,开释资本 
     * @throws Exception 
     */
     
    public static final String URL = "jdbc:mysql://localhost:3306/chenhao";
    public static final String USER = "liulx";
    public static final String PASSWORD = "123456";
    public static void main(String[] args) throws Exception {
        login("lucy","123");
    }
    
    public static void login(String username , String password) throws Exception{
        Connection conn = null; 
        PreparedStatement psmt = null;
        ResultSet rs = null;
        try {
            //加载驱动顺序
            Class.forName("com.mysql.jdbc.Driver");
            //获得数据库衔接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            //编写sql
            String sql = "select * from user where name =? and password = ?";//问号相当于一个占位符
            //对sql举行预编译
            psmt = conn.prepareStatement(sql);
            //设置参数
            psmt.setString(1, username);
            psmt.setString(2, password);
            //实行sql ,返回一个效果集
            rs = psmt.executeQuery();
            //输出效果
            while(rs.next()){
                System.out.println(rs.getString("user_name")+" 岁数:"+rs.getInt("age"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //开释资本
            conn.close();
            psmt.close();
            rs.close();
        }
    }
}

上面代码中解释已很清晰了,我们来看看mybatis中是怎样和数据库打交道的。

SimpleExecutor

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 猎取数据库衔接
    Connection connection = getConnection(statementLog); // 建立 Statement,
    stmt = handler.prepare(connection, transaction.getTimeout()); // 为 Statement 设置参数
 handler.parameterize(stmt); return stmt;
}

在上面的代码中我们终究看到了和jdbc相干的内容了,也许分为下面三个步骤:

  1. 猎取数据库衔接
  2. 建立PreparedStatement
  3. 为PreparedStatement设置运转时参数

我们先来看看猎取数据库衔接,跟进代码看看

BaseExecutor

protected Connection getConnection(Log statementLog) throws SQLException {
    //经由历程transaction来猎取Connection
    Connection connection = this.transaction.getConnection();
    return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}

我们看到是经由历程Executor中的transaction属性来猎取Connection,那我们就先来看看transaction,依据前面的文章中的设置 <transactionManager type="jdbc"/>,则MyBatis会建立一个JdbcTransactionFactory.class 实例,Executor中的transaction是一个JdbcTransaction.class 实例,其完成Transaction接口,那我们先来看看Transaction

JdbcTransaction

我们先来看看其接口Transaction

Transaction

public interface Transaction {
    //猎取数据库衔接
    Connection getConnection() throws SQLException;
    //提交事件
    void commit() throws SQLException;
    //回滚事件
    void rollback() throws SQLException;
    //封闭事件
    void close() throws SQLException;
    //猎取超时时刻
    Integer getTimeout() throws SQLException;
}

接着我们看看其完成类JdbcTransaction

JdbcTransaction

public class JdbcTransaction implements Transaction {
  
  private static final Log log = LogFactory.getLog(JdbcTransaction.class);
  
  //数据库衔接
  protected Connection connection; //数据源信息
  protected DataSource dataSource; //断绝级别
  protected TransactionIsolationLevel level;
  //是不是为自动提交
  protected boolean autoCommmit;
  
  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommmit = desiredAutoCommit;
  }
  
  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }
  
  public Connection getConnection() throws SQLException {
    //假如事件中不存在connection,则猎取一个connection并放入connection属性中 //第一次肯定为空
    if (connection == null) { openConnection(); } //假如事件中已存在connection,则直接返回这个connection
    return connection;
  }
  
    /**
     * commit()功用 
     * @throws SQLException
     */
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      //运用connection的commit()
 connection.commit();
    }
  }
  
    /**
     * rollback()功用 
     * @throws SQLException
     */
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      //运用connection的rollback()
 connection.rollback();
    }
  }
  
    /**
     * close()功用 
     * @throws SQLException
     */
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      //运用connection的close()
 connection.close();
    }
  }
  
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //经由历程dataSource来猎取connection,并设置到transaction的connection属性中
    connection = dataSource.getConnection(); if (level != null) { //经由历程connection设置事件的断绝级别
 connection.setTransactionIsolation(level.getLevel()); } //设置事件是不是自动提交
 setDesiredAutoCommit(autoCommmit);
  }
  
  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
        if (this.connection.getAutoCommit() != desiredAutoCommit) {
            if (log.isDebugEnabled()) {
                log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + this.connection + "]");
            }
            //经由历程connection设置事件是不是自动提交
            this.connection.setAutoCommit(desiredAutoCommit);
        }

    } catch (SQLException var3) {
        throw new TransactionException("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ".  Cause: " + var3, var3);
    }
  }
  
}

我们看到JdbcTransaction中有一个Connection属性和dataSource属性,运用connection来举行提交、回滚、封闭等操纵,也就是说JdbcTransaction实在只是在jdbc的connection上面封装了一下,现实运用的实在照样jdbc的事件。我们看看getConnection()要领

//数据库衔接
protected Connection connection;
//数据源信息
protected DataSource dataSource;

public Connection getConnection() throws SQLException {
//假如事件中不存在connection,则猎取一个connection并放入connection属性中
//第一次肯定为空
if (connection == null) {
  openConnection();
}
//假如事件中已存在connection,则直接返回这个connection
return connection;
}

protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
  log.debug("Opening JDBC Connection");
}
//经由历程dataSource来猎取connection,并设置到transaction的connection属性中
connection = dataSource.getConnection();
if (level != null) {
  //经由历程connection设置事件的断绝级别
  connection.setTransactionIsolation(level.getLevel());
}
//设置事件是不是自动提交
setDesiredAutoCommit(autoCommmit);
}

先是推断当前事件中是不是存在connection,假如存在,则直接返回connection,假如不存在则经由历程dataSource来猎取connection,这里我们邃晓了一点,假如当前事件没有封闭,也就是没有开释connection,那末在同一个Transaction中运用的是同一个connection,我们再来想一想,transaction是SimpleExecutor中的属性,SimpleExecutor又是SqlSession中的属性,那我们能够如许说,同一个SqlSession中只需一个SimpleExecutor,SimpleExecutor中有一个Transaction,Transaction有一个connection。我们来看看以下例子

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //建立一个SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession(); try {
         EmployeeMapper employeeMapper = sqlSession.getMapper(Employee.class);
         UserMapper userMapper = sqlSession.getMapper(User.class);
         List<Employee> allEmployee = employeeMapper.getAll();
         List<User> allUser = userMapper.getAll();
         Employee employee = employeeMapper.getOne();
    } finally {
        sqlSession.close();
    }
}

我们看到同一个sqlSession能够猎取多个Mapper代办对象,则多个Mapper代办对象中的sqlSession援用应该是同一个,那末多个Mapper代办对象挪用要领应该是同一个Connection,直到挪用close(),所以说我们的sqlSession是线程不安全的,假如一切的营业都运用一个sqlSession,那Connection也是同一个,一个营业实行完了就将其封闭,那其他的营业还没实行完呢。人人邃晓了吗?我们回归到源码,connection = dataSource.getConnection();,终究照样挪用dataSource来猎取衔接,那我们是不是是要来看看dataSource呢?

我们照样夙昔面的设置文件来看<dataSource type="UNPOOLED|POOLED">,这里有UNPOOLED和POOLED两种DataSource,一种是运用衔接池,一种是平常的DataSource,UNPOOLED将会创将new UnpooledDataSource()实例,POOLED将会new pooledDataSource()实例,都完成DataSource接口,那我们先来看看DataSource接口

DataSource

public interface DataSource  extends CommonDataSource,Wrapper {
  //猎取数据库衔接
  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;

}

很简朴,只需一个猎取数据库衔接的接口,那我们来看看其完成类

UnpooledDataSource

UnpooledDataSource,从称号上即可晓得,该种数据源不具有池化特征。该种数据源每次会返回一个新的数据库衔接,而非复用旧的衔接。其中心的要领有三个,离别以下:

  1. initializeDriver - 初始化数据库驱动
  2. doGetConnection - 猎取数据衔接
  3. configureConnection - 设置数据库衔接

初始化数据库驱动

看下我们上面运用JDBC的例子,在实行 SQL 之前,平常都是先猎取数据库衔接。平常步骤都是加载数据库驱动,然后经由历程 DriverManager 猎取数据库衔接。UnpooledDataSource 也是运用 JDBC 接见数据库的,因而它猎取数据库衔接的历程一样

UnpooledDataSource

public class UnpooledDataSource implements DataSource {
    private ClassLoader driverClassLoader;
    private Properties driverProperties;
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
    private String driver;
    private String url;
    private String username;
    private String password;
    private Boolean autoCommit;
    private Integer defaultTransactionIsolationLevel;

    public UnpooledDataSource() {
    }

    public UnpooledDataSource(String driver, String url, String username, String password) {
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
    }

    private synchronized void initializeDriver() throws SQLException {
        // 检测当前 driver 对应的驱动实例是不是已注册
        if (!registeredDrivers.containsKey(driver)) {
            Class<?> driverType;
            try {
                // 加载驱动范例
                if (driverClassLoader != null) {
                    // 运用 driverClassLoader 加载驱动
                    driverType = Class.forName(driver, true, driverClassLoader);
                } else {
                    // 经由历程其他 ClassLoader 加载驱动
                    driverType = Resources.classForName(driver);
                }

                // 经由历程反射建立驱动实例
                Driver driverInstance = (Driver) driverType.newInstance();
                /*
                 * 注册驱动,注重这里是将 Driver 代办类 DriverProxy 对象注册到 DriverManager 中的,而非 Driver 对象本身。
                 */
                DriverManager.registerDriver(new DriverProxy(driverInstance));
                // 缓存驱动类名和实例,防备屡次注册
                registeredDrivers.put(driver, driverInstance);
            } catch (Exception e) {
                throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
            }
        }
    }
    //略...
}

//DriverManager
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {

    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
}
经由历程反射机制加载驱动Driver,并将其注册到DriverManager中的一个常量鸠合中,供背面猎取衔接时运用,为何这里是一个List呢?我们现实开辟中有能够运用到了多种数据库范例,如Mysql、Oracle等,其驱动都是差别的,差别的数据源猎取衔接时运用的是差别的驱动。
在我们运用JDBC的时刻,也没有经由历程DriverManager.registerDriver(new DriverProxy(driverInstance));去注册Driver啊,假如我们运用的是Mysql数据源,那我们来看Class.forName("com.mysql.jdbc.Driver");这句代码发生了什么
Class.forName主如果做了什么呢?它主如果要求JVM查找并装载指定的类。如许我们的类com.mysql.jdbc.Driver就被装载进来了。而且在类被装载进JVM的时刻,它的静态要领就会被实行。我们来看com.mysql.jdbc.Driver的完成代码。在它的完成里有这么一段代码:
static {  
    try {  
        java.sql.DriverManager.registerDriver(new Driver());  
    } catch (SQLException E) {  
        throw new RuntimeException("Can't register driver!");  
    }  
}

 很明显,这里运用了DriverManager并将该类给注册上去了。所以,关于任何完成前面Driver接口的类,只需在他们被装载进JVM的时刻注册DriverManager就能够完成被后续顺序运用。

作为那些被加载的Driver完成,他们本身在被装载时会在实行的static代码段里经由历程挪用DriverManager.registerDriver()来把本身注册到DriverManager的registeredDrivers列表中。如许背面就能够经由历程获得的Driver来获得衔接了。

猎取数据库衔接

在上面例子中运用 JDBC 时,我们都是经由历程 DriverManager 的接口要领猎取数据库衔接。我们来看看UnpooledDataSource是怎样猎取的。

UnpooledDataSource

public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
}
    
private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
        props.putAll(driverProperties);
    }
    if (username != null) {
        // 存储 user 设置
        props.setProperty("user", username);
    }
    if (password != null) {
        // 存储 password 设置
        props.setProperty("password", password);
    }
    // 挪用重载要领
    return doGetConnection(props);
}

private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化驱动,我们上一节已讲过了,只用初始化一次
    initializeDriver();
    // 猎取衔接
    Connection connection = DriverManager.getConnection(url, properties);
    // 设置衔接,包含自动提交以及事件品级
    configureConnection(connection);
    return connection;
}

private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
        // 设置自动提交
        conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
        // 设置事件断绝级别
        conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
}

上面要领将一些设置信息放入到 Properties 对象中,然后将数据库衔接和 Properties 对象传给 DriverManager 的 getConnection 要领即可猎取到数据库衔接。我们来看看是怎样猎取数据库衔接的

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
    // 猎取类加载器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
      if (callerCL == null) {
        callerCL = Thread.currentThread().getContextClassLoader();
      }
    }
    // 此处省略部份代码 
    // 这里遍历的是在registerDriver(Driver driver)要领中注册的驱动对象
    // 每一个DriverInfo包含了驱动对象和其信息
    for(DriverInfo aDriver : registeredDrivers) {

      // 推断是不是为当前线程类加载器加载的驱动类
      if(isDriverAllowed(aDriver.driver, callerCL)) {
        try {
          println("trying " + aDriver.driver.getClass().getName());

          // 猎取衔接对象,这里挪用了Driver的父类的要领 // 假如这里有多个DriverInfo,比方Mysql和Oracle的Driver都注册registeredDrivers了 // 这里一切的Driver都邑尝试运用url和info去衔接,哪一个衔接上了就返回 // 会不会一切的都邑衔接上呢?不会,因为url的写法差别,差别的Driver会推断url是不是合适当前驱动
          Connection con = aDriver.driver.connect(url, info); if (con != null) {
            // 打印衔接胜利信息
            println("getConnection returning " + aDriver.driver.getClass().getName());
            // 返回衔接对像
            return (con);
          }
        } catch (SQLException ex) {
          if (reason == null) {
            reason = ex;
          }
        }
      } else {
        println("    skipping: " + aDriver.getClass().getName());
      }
    }  
}

代码中轮回一切注册的驱动,然后经由历程驱动举行衔接,一切的驱动都邑尝试衔接,然则差别的驱动,衔接的URL是差别的,如Mysql的url是jdbc:mysql://localhost:3306/chenhao,以jdbc:mysql://开首,则其Mysql的驱动肯定会推断猎取衔接的url相符,Oracle的也相似,我们来看看Mysql的驱动猎取衔接

因为篇幅缘由,我这里就不剖析了,人人有兴致的能够看看,末了由URL对应的驱动猎取到Connection返回,好了我们再来看看下一种DataSource

PooledDataSource

PooledDataSource 内部完成了衔接池功用,用于复用数据库衔接。因而,从效力上来讲,PooledDataSource 要高于 UnpooledDataSource。然则终究猎取Connection照样经由历程UnpooledDataSource,只不过PooledDataSource 供应一个存储Connection的功用。

辅佐类引见

PooledDataSource 须要借助两个辅佐类帮其完胜利用,这两个辅佐类离别是 PoolState 和 PooledConnection。PoolState 用于纪录衔接池运转时的状况,比方衔接猎取次数,无效衔接数目等。同时 PoolState 内部定义了两个 PooledConnection 鸠合,用于存储余暇衔接和活泼衔接。PooledConnection 内部定义了一个 Connection 范例的变量,用于指向实在的数据库衔接。以及一个 Connection 的代办类,用于对部份要领挪用举行阻拦。至于为何要阻拦,随后将举行剖析。除此之外,PooledConnection 内部也定义了一些字段,用于纪录数据库衔接的一些运转时状况。接下来,我们来看一下 PooledConnection 的定义。

PooledConnection

class PooledConnection implements InvocationHandler {

    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

    private final int hashCode;
    private final PooledDataSource dataSource;
    // 实在的数据库衔接
    private final Connection realConnection; // 数据库衔接代办
    private final Connection proxyConnection;
    
    // 从衔接池中掏出衔接时的时刻戳
    private long checkoutTimestamp;
    // 数据库衔接建立时刻
    private long createdTimestamp;
    // 数据库衔接末了运用时刻
    private long lastUsedTimestamp;
    // connectionTypeCode = (url + username + password).hashCode()
    private int connectionTypeCode;
    // 示意衔接是不是有用
    private boolean valid;

    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;
        // 建立 Connection 的代办类对象
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...}
    
    // 省略部份代码
}

下面再来看看 PoolState 的定义。

PoolState 

public class PoolState {

    protected PooledDataSource dataSource; // 余暇衔接列表
    protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 活泼衔接列表
    protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 从衔接池中猎取衔接的次数
    protected long requestCount = 0;
    // 要求衔接总耗时(单元:毫秒)
    protected long accumulatedRequestTime = 0;
    // 衔接实行时刻总耗时
    protected long accumulatedCheckoutTime = 0;
    // 实行时刻超时的衔接数
    protected long claimedOverdueConnectionCount = 0;
    // 超时时刻累加值
    protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
    // 守候时刻累加值
    protected long accumulatedWaitTime = 0;
    // 守候次数
    protected long hadToWaitCount = 0;
    // 无效衔接数
    protected long badConnectionCount = 0;
}

人人记着上面的余暇衔接列表和活泼衔接列表

猎取衔接

前面已说过,PooledDataSource 会将用过的衔接举行接纳,以便能够复用衔接。因而从 PooledDataSource 猎取衔接时,假如余暇链接列内外有衔接时,可直接取用。那假如没有余暇衔接怎样办呢?此时有两种解决办法,要么建立新衔接,要么守候其他衔接完成任务。

PooledDataSource

public class PooledDataSource implements DataSource {
    private static final Log log = LogFactory.getLog(PooledDataSource.class);
    //这里有辅佐类PoolState
    private final PoolState state = new PoolState(this);
    //另有一个UnpooledDataSource属性,实在真正猎取Connection是由UnpooledDataSource来完成的
    private final UnpooledDataSource dataSource;
    protected int poolMaximumActiveConnections = 10;
    protected int poolMaximumIdleConnections = 5;
    protected int poolMaximumCheckoutTime = 20000;
    protected int poolTimeToWait = 20000;
    protected String poolPingQuery = "NO PING QUERY SET";
    protected boolean poolPingEnabled = false;
    protected int poolPingConnectionsNotUsedFor = 0;
    private int expectedConnectionTypeCode;
    
    public PooledDataSource() {
        this.dataSource = new UnpooledDataSource();
    }
    
    public PooledDataSource(String driver, String url, String username, String password) {
        //组织器中建立UnpooledDataSource对象
        this.dataSource = new UnpooledDataSource(driver, url, username, password);
    }
    
    public Connection getConnection() throws SQLException {
        return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
    }
    
    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) {
                // 检测余暇衔接鸠合(idleConnections)是不是为空
                if (!state.idleConnections.isEmpty()) {
                    // idleConnections 不为空,示意有余暇衔接能够运用,直接从余暇衔接鸠合中掏出一个衔接
                    conn = state.idleConnections.remove(0);
                } else {
                    /*
                     * 暂无余暇衔接可用,但假如活泼衔接数还未超越限定
                     *(poolMaximumActiveConnections),则可建立新的衔接
                     */
                    if (state.activeConnections.size() < poolMaximumActiveConnections) {
                        // 建立新衔接,看到没,照样经由历程dataSource猎取衔接,也就是UnpooledDataSource猎取衔接
                        conn = new PooledConnection(dataSource.getConnection(), this);
                    } else {    // 衔接池已满,不能建立新衔接
                        // 掏出运转时刻最长的衔接
                        PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                        // 猎取运转时长
                        long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                        // 检测运转时长是不是超越限定,即超时
                        if (longestCheckoutTime > poolMaximumCheckoutTime) {
                            // 累加超时相干的统计字段
                            state.claimedOverdueConnectionCount++;
                            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                            state.accumulatedCheckoutTime += longestCheckoutTime;

                            // 从活泼衔接鸠合中移除超时衔接
                            state.activeConnections.remove(oldestActiveConnection);
                            // 若衔接未设置自动提交,此处举行回滚操纵
                            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                                try {
                                    oldestActiveConnection.getRealConnection().rollback();
                                } catch (SQLException e) {...}
                            }
                            /*
                             * 建立一个新的 PooledConnection,注重,
                             * 此处复用 oldestActiveConnection 的 realConnection 变量
                             */
                            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                            /*
                             * 复用 oldestActiveConnection 的一些信息,注重 PooledConnection 中的 
                             * createdTimestamp 用于纪录 Connection 的建立时刻,而非 PooledConnection 
                             * 的建立时刻。所以这里要复用原衔接的时刻信息。
                             */
                            conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                            conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());

                            // 设置衔接为无效状况
                            oldestActiveConnection.invalidate();
                            
                        } else {// 运转时刻最长的衔接并未超时
                            try {
                                if (!countedWait) {
                                    state.hadToWaitCount++;
                                    countedWait = true;
                                }
                                long wt = System.currentTimeMillis();
                                // 当前线程进入守候状况
                                state.wait(poolTimeToWait);
                                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }
                if (conn != null) {
                    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 {
                        // 衔接无效,此时累加无效衔接相干的统计字段
                        state.badConnectionCount++;
                        localBadConnectionCount++;
                        conn = null;
                        if (localBadConnectionCount > (poolMaximumIdleConnections
                            + poolMaximumLocalBadConnectionTolerance)) {
                            throw new SQLException(...);
                        }
                    }
                }
            }

        }
        if (conn == null) {
            throw new SQLException(...);
        }

        return conn;
    }
}

从衔接池中猎取衔接起首会碰到两种状况:

  1. 衔接池中有余暇衔接
  2. 衔接池中无余暇衔接

关于第一种状况,把衔接掏出返回即可。关于第二种状况,则要举行细分,会有以下的状况。

  1. 活泼衔接数没有超越最大活泼衔接数
  2. 活泼衔接数超越最大活泼衔接数

关于上面两种状况,第一种状况比较好处置惩罚,直接建立新的衔接即可。至于第二种状况,须要再次举行细分。

  1. 活泼衔接的运转时刻超越限定,即超时了
  2. 活泼衔接未超时

关于第一种状况,我们直接将超时衔接强行中断,并举行回滚,然后复用部份字段从新建立 PooledConnection 即可。关于第二种状况,如今没有更好的处置惩罚方式了,只能守候了。

接纳衔接

比拟于猎取衔接,接纳衔接的逻辑要简朴的多。接纳衔接胜利与否只取决于余暇衔接鸠合的状况,所需处置惩罚状况很少,因而比较简朴。

我们照样来看看

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

返回的是PooledConnection的一个代办类,为何不直接运用PooledConnection的realConnection呢?我们能够看下PooledConnection这个类

class PooledConnection implements InvocationHandler {

 很熟悉是吧,规范的代办类用法,看下其invoke要领

PooledConnection

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 重点在这里,假如挪用了其close要领,则现实实行的是将衔接放回衔接池的操纵
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
        dataSource.pushConnection(this); return null;
    } else {
        try {
            if (!Object.class.equals(method.getDeclaringClass())) {
                // issue #579 toString() should never fail
                // throw an SQLException instead of a Runtime
                checkConnection();
            }
            // 其他的操纵都交给realConnection实行
            return method.invoke(realConnection, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

那我们来看看pushConnection做了什么

protected void pushConnection(PooledConnection conn) throws SQLException {
    synchronized (state) {
        // 从活泼衔接池中移除衔接
 state.activeConnections.remove(conn); if (conn.isValid()) {
            // 余暇衔接鸠合未满
            if (state.idleConnections.size() < poolMaximumIdleConnections
                && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
                state.accumulatedCheckoutTime += conn.getCheckoutTime();

                // 回滚未提交的事件
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }

                // 建立新的 PooledConnection
                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); state.idleConnections.add(newConn); // 复用时刻信息
                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());

                // 将原衔接置为无效状况
                conn.invalidate();

                // 关照守候的线程
 state.notifyAll();
                
            } else {// 余暇衔接鸠合已满
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                // 回滚未提交的事件
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }

                // 封闭数据库衔接
                conn.getRealConnection().close();
                conn.invalidate();
            }
        } else {
            state.badConnectionCount++;
        }
    }
}

先将衔接从活泼衔接鸠合中移除,假如余暇鸠合未满,此时复用原衔接的字段信息建立新的衔接,并将其放入余暇鸠合中即可;若余暇鸠合已满,此时无需接纳衔接,直接封闭即可。

衔接池总觉得很神奇,但仔细剖析完其代码以后,也就没那末神奇了,就是将衔接运用完以后放到一个鸠合中,下面再猎取衔接的时刻起首从这个鸠合中猎取。  另有PooledConnection的代办情势的运用,值得我们进修

好了,我们已猎取到了数据库衔接,接下来要建立PrepareStatement了,我们上面JDBC的例子是怎样猎取的? psmt = conn.prepareStatement(sql);,直接经由历程Connection来猎取,而且把sql传进去了,我们看看Mybaits中是怎样建立PrepareStatement的

建立PreparedStatement 

PreparedStatementHandler

stmt = handler.prepare(connection, transaction.getTimeout());

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    Statement statement = null;
    try {
        // 建立 Statement
        statement = instantiateStatement(connection);
        // 设置超时和 FetchSize
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}

protected Statement instantiateStatement(Connection connection) throws SQLException {
    //猎取sql字符串,比方"select * from user where id= ?"
    String sql = boundSql.getSql(); // 依据前提挪用差别的 prepareStatement 要领建立 PreparedStatement
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
            //经由历程connection猎取Statement,将sql语句传进去
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
            return connection.prepareStatement(sql, keyColumnNames);
        }
    } else if (mappedStatement.getResultSetType() != null) {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
        return connection.prepareStatement(sql);
    }
}

看到没和jdbc的情势如出一辙,我们详细来看看connection.prepareStatement做了什么

 1 public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
 2        
 3     boolean canServerPrepare = true;
 4 
 5     String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql) : sql;
 6 
 7     if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
 8         canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
 9     }
10 
11     if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
12         canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
13     }
14 
15     if (this.useServerPreparedStmts && canServerPrepare) {
16         if (this.getCachePreparedStatements()) {
17             ......
18         } else {
19             try {
20                 //这里运用的是ServerPreparedStatement建立PreparedStatement
21                 pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); 22 
23                 pStmt.setResultSetType(resultSetType);
24                 pStmt.setResultSetConcurrency(resultSetConcurrency);
25             } catch (SQLException sqlEx) {
26                 // Punt, if necessary
27                 if (getEmulateUnsupportedPstmts()) {
28                     pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
29                 } else {
30                     throw sqlEx;
31                 }
32             }
33         }
34     } else {
35         pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
36     }
37 }

我们只用看最症结的第21行代码,运用ServerPreparedStatement的getInstance返回一个PreparedStatement,实在本质上ServerPreparedStatement继承了PreparedStatement对象,我们看看其组织要领

protected ServerPreparedStatement(ConnectionImpl conn, String sql, String catalog, int resultSetType, int resultSetConcurrency) throws SQLException {
    //略...

    try {
        this.serverPrepare(sql);
    } catch (SQLException var10) {
        this.realClose(false, true);
        throw var10;
    } catch (Exception var11) {
        this.realClose(false, true);
        SQLException sqlEx = SQLError.createSQLException(var11.toString(), "S1000", this.getExceptionInterceptor());
        sqlEx.initCause(var11);
        throw sqlEx;
    }
    //略...

}

继承挪用this.serverPrepare(sql);

public class ServerPreparedStatement extends PreparedStatement {
    //寄存运转时参数的数组
    private ServerPreparedStatement.BindValue[] parameterBindings; //服务器预编译好的sql语句返回的serverStatementId
    private long serverStatementId; private void serverPrepare(String sql) throws SQLException {
        synchronized(this.connection.getMutex()) {
            MysqlIO mysql = this.connection.getIO();
            try {
                //向sql服务器发送了一条PREPARE指令
                Buffer prepareResultPacket = mysql.sendCommand(MysqlDefs.COM_PREPARE, sql, (Buffer)null, false, characterEncoding, 0); //纪录下了预编译好的sql语句所对应的serverStatementId
                this.serverStatementId = prepareResultPacket.readLong(); this.fieldCount = prepareResultPacket.readInt();
                //猎取参数个数,比方 select * from user where id= ?and name = ?,个中有两个?,则这里返回的参数个数应该为2
                this.parameterCount = prepareResultPacket.readInt();
                this.parameterBindings = new ServerPreparedStatement.BindValue[this.parameterCount];

                for(int i = 0; i < this.parameterCount; ++i) { //依据参数个数,初始化数组
                    this.parameterBindings[i] = new ServerPreparedStatement.BindValue(); } 
            } catch (SQLException var16) {
                throw sqlEx;
            } finally {
                this.connection.getIO().clearInputStream();
            }

        }
    }
}
ServerPreparedStatement继承PreparedStatement,ServerPreparedStatement初始化的时刻就向sql服务器发送了一条PREPARE指令,把SQL语句传到mysql服务器,如select * from user where id= ?and name = ?,mysql服务器会对sql举行编译,并保存在服务器,返回预编译语句对应的id,并保存在
ServerPreparedStatement中,同时建立BindValue[] parameterBindings数组,背面设置参数就直接增加到此数组中。好了,此时我们建立了一个ServerPreparedStatement并返回,下面就是设置运转时参数了

设置运转时参数到 SQL 中

我们已猎取到了PreparedStatement,接下来就是将运转时参数设置到PreparedStatement中,以下代码

handler.parameterize(stmt);

JDBC是怎样设置的呢?我们看看上面的例子,很简朴吧

psmt = conn.prepareStatement(sql);
//设置参数
psmt.setString(1, username); psmt.setString(2, password);

我们来看看parameterize要领

public void parameterize(Statement statement) throws SQLException {
    // 经由历程参数处置惩罚器 ParameterHandler 设置运转时参数到 PreparedStatement 中
    parameterHandler.setParameters((PreparedStatement) statement);
}

public class DefaultParameterHandler implements ParameterHandler {
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

    public void setParameters(PreparedStatement ps) {
        /*
         * 从 BoundSql 中猎取 ParameterMapping 列表,每一个 ParameterMapping 与原始 SQL 中的 #{xxx} 占位符一一对应
         */ List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    // 猎取属性名
                    String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        // 为用户传入的参数 parameterObject 建立元信息对象
                        MetaObject metaObject = configuration.newMetaObject(parameterObject); // 从用户传入的参数中猎取 propertyName 对应的值
                        value = metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        // 由范例处置惩罚器 typeHandler 向 ParameterHandler 设置参数
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException e) {
                        throw new TypeException(...);
                    } catch (SQLException e) {
                        throw new TypeException(...);
                    }
                }
            }
        }
    }
}

起首从boundSql中猎取parameterMappings 鸠合,这块人人能够看看我前面的文章,然后遍历猎取 parameterMapping中的propertyName ,如#{name} 中的name,然后从运转时参数parameterObject中猎取name对应的参数值,末了设置到PreparedStatement 中,我们重要来看是怎样设置参数的。也就是

typeHandler.setParameter(ps, i + 1, value, jdbcType);,这句代码终究会向我们例子中一样实行,以下

public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
   ps.setString(i, parameter);
}

还记得我们的PreparedStatement是什么吗?是ServerPreparedStatement,那我们就来看看ServerPreparedStatement的setString要领

public void setString(int parameterIndex, String x) throws SQLException {
    this.checkClosed();
    if (x == null) {
        this.setNull(parameterIndex, 1);
    } else {
        //依据参数下标从parameterBindings数组总猎取BindValue
        ServerPreparedStatement.BindValue binding = this.getBinding(parameterIndex, false); this.setType(binding, this.stringTypeCode); //设置参数值
        binding.value = x;
        binding.isNull = false;
        binding.isLongData = false;
    }

}

protected ServerPreparedStatement.BindValue getBinding(int parameterIndex, boolean forLongData) throws SQLException {
    this.checkClosed();
    if (this.parameterBindings.length == 0) {
        throw SQLError.createSQLException(Messages.getString("ServerPreparedStatement.8"), "S1009", this.getExceptionInterceptor());
    } else {
        --parameterIndex;
        if (parameterIndex >= 0 && parameterIndex < this.parameterBindings.length) {
            if (this.parameterBindings[parameterIndex] == null) {
                this.parameterBindings[parameterIndex] = new ServerPreparedStatement.BindValue();
            } else if (this.parameterBindings[parameterIndex].isLongData && !forLongData) {
                this.detectedLongParameterSwitch = true;
            }

            this.parameterBindings[parameterIndex].isSet = true;
            this.parameterBindings[parameterIndex].boundBeforeExecutionNum = (long)this.numberOfExecutions;
            //依据参数下标从parameterBindings数组总猎取BindValue
            return this.parameterBindings[parameterIndex];
        } else {
            throw SQLError.createSQLException(Messages.getString("ServerPreparedStatement.9") + (parameterIndex + 1) + Messages.getString("ServerPreparedStatement.10") + this.parameterBindings.length, "S1009", this.getExceptionInterceptor());
        }
    }
}
就是依据参数下标从ServerPreparedStatement的参数数组parameterBindings中猎取BindValue对象,然后设置值,好了如今ServerPreparedStatement包含了预编译SQL语句的Id和参数数组,末了一步就是实行SQL了。

实行查询

实行查询操纵就是我们文章开首的末了一行代码,以下

return handler.<E>query(stmt, resultHandler);

我们来看看query是怎样做的

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    //直接实行ServerPreparedStatement的execute要领
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}

public boolean execute() throws SQLException {
    this.checkClosed();
    ConnectionImpl locallyScopedConn = this.connection;
    if (!this.checkReadOnlySafeStatement()) {
        throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") + Messages.getString("PreparedStatement.21"), "S1009", this.getExceptionInterceptor());
    } else {
        ResultSetInternalMethods rs = null;
        CachedResultSetMetaData cachedMetadata = null;
        synchronized(locallyScopedConn.getMutex()) {
            //略....
            rs = this.executeInternal(rowLimit, sendPacket, doStreaming, this.firstCharOfStmt == 'S', metadataFromCache, false); //略....
        }

        return rs != null && rs.reallyResult();
    }
}

省略了许多代码,只看最症结的executeInternal

ServerPreparedStatement

protected ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly, Field[] metadataFromCache, boolean isBatch) throws SQLException {
    try {
        return this.serverExecute(maxRowsToRetrieve, createStreamingResultSet, metadataFromCache);
    } catch (SQLException var11) {
        throw sqlEx;
    } 
}

private ResultSetInternalMethods serverExecute(int maxRowsToRetrieve, boolean createStreamingResultSet, Field[] metadataFromCache) throws SQLException {
    synchronized(this.connection.getMutex()) {
        //略....
        MysqlIO mysql = this.connection.getIO();
        Buffer packet = mysql.getSharedSendPacket();
        packet.clear();
        packet.writeByte((byte)MysqlDefs.COM_EXECUTE);
        //将该语句对应的id写入数据包
        packet.writeLong(this.serverStatementId); int i;
        //将对应的参数写入数据包
        for(i = 0; i < this.parameterCount; ++i) {
            if (!this.parameterBindings[i].isLongData) {
                if (!this.parameterBindings[i].isNull) {
                    this.storeBinding(packet, this.parameterBindings[i], mysql);
                } else {
                    nullBitsBuffer[i / 8] = (byte)(nullBitsBuffer[i / 8] | 1 << (i & 7));
                }
            }
        }
        //发送数据包,示意实行id对应的预编译sql
        Buffer resultPacket = mysql.sendCommand(MysqlDefs.COM_EXECUTE, (String)null, packet, false, (String)null, 0); //略....
        ResultSetImpl rs = mysql.readAllResults(this,  this.resultSetType,  resultPacket, true, (long)this.fieldCount, metadataFromCache);
        //返回效果
        return rs;
    }
}

ServerPreparedStatement在纪录下serverStatementId后,关于雷同SQL模板的操纵,每次只是发送serverStatementId和对应的参数,省去了编译sql的历程。 至此我们的已从数据库拿到了查询效果,然则效果是ResultSetImpl范例,我们还须要将返回效果转化成我们的java对象呢,留在下一篇来讲吧

 

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
Mybaits 源码剖析 (七)----- Select 语句的实行历程剖析(下篇)

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282380.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>