mybatis连接池源码分析

news2024/11/26 3:35:19

文章目录

  • 前言
  • 一、PooledDataSourceFactory
  • 二、获取连接
  • 三、归还连接


前言

其实大部分连接池的代码都大同小异,总体获取连接,归还连接逻辑大都相同。希望通过阅读本文章,能给你带来帮助。
测试用例

  public void testMybatis()throws Exception{
		
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
    org.springframework.core.io.ClassPathResource classPathResource=new ClassPathResource("org/apache/ibatis/user/mybatis.xml");
    InputStream inputStream = classPathResource.getInputStream();
    // 1.读取配置文件获取SqlSessionFactory 
    SqlSessionFactory sqlSessionFactory= sqlSessionFactoryBuilder.build(inputStream);
    // 2.获取sqlsession
    SqlSession sqlSession = sqlSessionFactory.openSession();
	
	// 3.执行获取结果
    User user = new User(1L, null, null, null);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser(user);
    mapper.selectUser(user);
    System.out.println(users);
    }

在执行下列语句时,mybatis会去读取配置文件,并创建我们的Configuration,并会给Configuration设置相对的属性值。

SqlSessionFactory sqlSessionFactory= sqlSessionFactoryBuilder.build(inputStream);

在Configuration初始化时,就会设置typeAliasRegistry.registerAlias(“POOLED”, PooledDataSourceFactory.class);所以本节主要也就是根据PooledDataSourceFactory去讲解

  public Configuration() {
    // 事务工厂类型别名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    // 数据源工厂类型别名
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    // 缓存类型别名
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    // 数据库厂商标识类型别名
    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    // 语言驱动类型别名
    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    // 日志实现类型别名
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    // 代理工厂类型别名
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    // 默认语言驱动
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    // 注册语言驱动
    languageRegistry.register(RawLanguageDriver.class);
  }

一、PooledDataSourceFactory

PooledDataSourceFactory

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }

}

PooledDataSource

public class PooledDataSource implements DataSource {

  private static final Log log = LogFactory.getLog(PooledDataSource.class);

  private final PoolState state = new PoolState(this);

  private final UnpooledDataSource dataSource;

  // 连接池参数配置,最大连接数,
  protected int poolMaximumActiveConnections = 10;
  // 最大空闲连接数
  protected int poolMaximumIdleConnections = 5;
  // 最大检出时间
  protected int poolMaximumCheckoutTime = 20000;
  // 最大等待时间
  protected int poolTimeToWait = 20000;
  // 最大坏的连接数
  protected int poolMaximumLocalBadConnectionTolerance = 3;
  // 服务器是否ping的通MySQL数据库
  protected String poolPingQuery = "NO PING QUERY SET";
 
  protected boolean poolPingEnabled;
  protected int poolPingConnectionsNotUsedFor;

  private int expectedConnectionTypeCode;
  }

PoolState 当前运行的数据源状态

public class PoolState {

  protected PooledDataSource dataSource;
  // 空闲连接	
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  // 活跃连接
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  // 请求次数
  protected long requestCount;
  // 累计请求时间
  protected long accumulatedRequestTime;
  // 请求使用时间
  protected long accumulatedCheckoutTime;
  // 过期未还连接数量
  protected long claimedOverdueConnectionCount;
  //  过期未还连接数量连接时间
  protected long accumulatedCheckoutTimeOfOverdueConnections;
  //  累计等待时长
  protected long accumulatedWaitTime;
  // 获取连接等待数量
  protected long hadToWaitCount;
  // 坏的连接数量
  protected long badConnectionCount;
  }

以下代码可以配置我们的数据库连接池

<dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="poolMaximumActiveConnections" value="20"/>
    <property name="poolMaximumIdleConnections" value="5"/>
    <property name="poolMaximumCheckoutTime" value="20000"/>
    <property name="poolTimeToWait" value="20000"/>
    <property name="poolPingEnabled" value="true"/>
    <property name="poolPingQuery" value="SELECT 1"/>
    <property name="poolPingConnectionsNotUsedFor" value="3600000"/>
</dataSource>

二、获取连接

在执行sqlSessionFactory.openSession();会去数据库连接池获取我们的连接

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

PooledDataSource.getConnection()获取连接(这里就直接看获取连接的代码)

  public Connection getConnection() throws SQLException {
  	// 这里返回的是被代理过后的Connection 
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

PooledDataSource.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) {
    // 进行上锁 ,
      lock.lock();
      try {
      	// 1.如果空闲的连接不为空
        if (!state.idleConnections.isEmpty()) {
     		// 2. 从空闲连接拿一个,并且从空闲连接集合中删除
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
          // 2.如果空闲的连接为空,且当前活跃连接数量 <  配置的最大连接数 (池子还没满状态下)
        } else if (state.activeConnections.size() < poolMaximumActiveConnections) {
    
          // 3.就直接创建一个新的连接
          conn = new PooledConnection(dataSource.getConnection(), this);
          if (log.isDebugEnabled()) {
            log.debug("Created connection " + conn.getRealHashCode() + ".");
          }
          //  2.如果空闲的连接为空,且当前活跃连接数量 >=  配置的最大连接数 (池子已经满了,装不下了)
        } else {
          // 3. 在当前活跃连接池子中,拿到最开始进入活跃连接池的连接时间
          PooledConnection oldestActiveConnection = state.activeConnections.get(0);
          long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
          // 4.判断最开始在活跃连接是否 > 设置的超时连接。
          if (longestCheckoutTime > poolMaximumCheckoutTime) {
            // 5.进行一些超时计算,把超时连接从池子remove出去
            state.claimedOverdueConnectionCount++;
            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
            state.accumulatedCheckoutTime += longestCheckoutTime;
            state.activeConnections.remove(oldestActiveConnection);
            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
              try {
               // 6. 进行回滚
                oldestActiveConnection.getRealConnection().rollback();
              } catch (SQLException e) {
             
                log.debug("Bad connection. Could not roll back");
              }
            }
            // 7. 把旧的连接进行一个包装,生成一个新的连接PooledConnection  conn
            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
            conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
            conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
            // 8. 把旧的连接设置为不可用
            oldestActiveConnection.invalidate();
            if (log.isDebugEnabled()) {
              log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
            }
            
          } else {
            // 判断最开始在活跃连接不大于设置的超时连接。那当前线程获取连接就需要进行等待
            try {
              if (!countedWait) {
                state.hadToWaitCount++;
                countedWait = true;
              }
              if (log.isDebugEnabled()) {
                log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
              }
              long wt = System.currentTimeMillis();
              condition.await(poolTimeToWait, TimeUnit.MILLISECONDS);
              state.accumulatedWaitTime += System.currentTimeMillis() - wt;
            } catch (InterruptedException e) {
              // set interrupt flag
              Thread.currentThread().interrupt();
              break;
            }
          }
        }
  
        if (conn != null) {
          //ping一下当前连接是否可用,
          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.");
            }
          }
        }
      } finally {
        lock.unlock();
      }

    }

    return conn;
  }

情况一:存在空闲连接

  1. 对当前线程进行上锁
  2. 如果有空闲连接,则直接从空闲连接当中获取第一个连接,并移除。
  3. ping一下当前连接是否可用,判断当前连接是手动提交,还是自动提交。(手动提交就回滚,保证当前连接是干净的连接)
  4. 可用连接属性值设置,并加入活跃连接池中,记录请求次数,使用开始时间等等。
  5. 释放锁

情况二:不存在空闲连接,且活跃连接池数量 < 配置的最大连接数(池子还没满状态下)

  1. 对当前线程进行上锁
  2. 如果没有空闲连接,且当前活跃连接数量 < 配置的最大连接数 (池子还没满状态下)
  3. 直接创建一个新的连接
  4. ping一下当前连接是否可用,判断当前连接是手动提交,还是自动提交。(手动提交就回滚,保证当前连接是干净的连接)
  5. 可用连接属性值设置,并加入活跃连接池中,记录请求次数,使用开始时间等等。
  6. 释放锁

情况三:不存在空闲连接,且当前活跃连接数量 >= 配置的最大连接数 (池子已经满了,装不下了)

  1. 对当前线程进行上锁
  2. 如果没有空闲连接,且当前活跃连接数量 >= 配置的最大连接数 (池子已经满了,装不下了)
  3. 判断当前连接使用时间是否超时,如果没超时就进入等待状态。
  4. 如果当前连接超时了,就记录超时时间,把超时连接从活跃连接池移除。对当前超时连接进行回滚操作。
  5. 把超时的连接包装成一个新的连接PooledConnection,并把超时的连接设置为不可用。
  6. ping一下当前连接是否可用,判断当前连接是手动提交,还是自动提交。(手动提交就回滚,保证当前连接是干净的连接)
  7. 可用连接属性值设置,并加入活跃连接池中,记录请求次数,使用开始时间等等。
  8. 释放锁

三、归还连接

当我们调用 sqlSession.close()时,会去归还当前连接。

    User user = new User(1L, null, null, null);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser(user);
    mapper.selectUser(user);
    // 手动关闭sqlSession,归还连接
    sqlSession.close();

PooledConnection 类,实现了InvocationHandler 接口,大家可以发现这是一个jdk动态代理接口。
如果忘记了动态代理,戳这里

class PooledConnection implements InvocationHandler {

  private final Connection realConnection;
  private final Connection proxyConnection;

// 构造方法
 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;
    // 这里会进行jdk动态代理,在上面getconnection,获取连接时,就会返回这个被代理过后的Connection。
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }


// invoke方法,当我们的Connection 调用任何方法时,会先调用我们的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 查看当前调用方法,如果调用的是close方法,就执行归还连接的逻辑
    if (CLOSE.equals(methodName)) {
    // 归还连接
      dataSource.pushConnection(this);
      return null;
    }
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // issue #579 toString() should never fail
        // throw an SQLException instead of a Runtime
        checkConnection();
      }
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

  }

dataSource.pushConnection(this);

protected void pushConnection(PooledConnection conn) throws SQLException {
	// 1.上锁
    lock.lock();
    try {
    // 2. 从活跃连接池中移除
      state.activeConnections.remove(conn);
      // 3. 查看当前连接池是否可以,是否能ping通
      if (conn.isValid()) {
      // 4. 如果空闲连接数量 < 容器设置最大连接数量
        if (state.idleConnections.size() < poolMaximumIdleConnections
            && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          // 5.进行回滚操作
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          // 6.重新进行包装,把当前连接放入空闲连接当中
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          // 7. 把原有连接设置为不可用
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          condition.signal();
        } else {
         // 如果当前连接满了,就进行回滚,把当前连接设置为不可用
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate();
        }
      } else {
      // 如果是坏连接,不用管。
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode()
              + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    } finally {
    // 释放锁
      lock.unlock();
    }
  }

情况一:空闲连接数量 < 容器设置最大连接数量

  1. 对当前线程进行上锁
  2. 从活跃连接池中移除,并查看当前连接池是否可以,是否能ping通
  3. 如果空闲连接数量 < 容器设置最大连接数量
  4. 进行回滚操作,重新进行包装,把包装后的连接放入空闲连接池当中
  5. 把原有连接设置为不可用
  6. 释放锁

情况二:空闲连接数量 >= 容器设置最大连接数量

  1. 对当前线程进行上锁
  2. 从活跃连接池中移除,并查看当前连接池是否可以,是否能ping通
  3. 进行回滚操作
  4. 把当前连接设置为不可用
  5. 释放锁

今天母亲节,祝愿天下所有母亲节日快乐,身体安康!!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/525511.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入篇【C++】类与对象:运算符重载详解

深入篇【C】类与对象&#xff1a;运算符重载详解 ⏰一.运算符重载&#x1f553;1.<运算符重载&#x1f550;2.>运算符重载&#x1f552;3.运算符重载&#x1f551;4.运算符重载①.格式1.改进12.改进2 ②.默认成员函数1.功能2.不足 &#x1f553;5.<运算符重载&#x1…

学内核之十九:Linux文件系统结构大蓝图

目录 一&#xff1a;参考资料 二&#xff1a;整理的原因及基本原则 三&#xff1a;Linux文件系统大蓝图 四&#xff1a;补充说明 一&#xff1a;参考资料 博主梳理的关于文件系统的基础知识&#xff1a; 7.5 文件系统_定义_龙赤子的博客-CSDN博客 博主转载的关于page cac…

深入理解深度学习——正则化(Regularization):参数范数惩罚

分类目录&#xff1a;《深入理解深度学习》总目录 正则化在深度学习的出现前就已经被使用了数十年。线性模型&#xff0c;如线性回归和逻辑回归可以使用简单、直接、有效的正则化策略。许多正则化方法通过对目标函数 J J J添加一个参数范数惩罚 Ω ( θ ) \Omega(\theta) Ω(θ…

三、Neo4j 源码研究系列 - 持久化

version: v-2023051401 author: 路__ 说到数据库&#xff0c;那么离不开的模块就是持久化&#xff08;Persistence&#xff09;&#xff0c;数据持久化是数据库不可缺少的重要组成模块之一。可以说一个数据库少了持久化功能&#xff0c;可以说这个数据库就不足以称为数据库。…

并查集:解密算法面试中的常客

文章目录 1. 并查集原理&#x1f351; 举例说明&#x1f351; 并查集的应用 2. 并查集实现&#x1f351; 接口总览&#x1f351; 构造函数&#x1f351; 查询操作&#x1f345; 代码实现 &#x1f351; 合并操作&#x1f345; 动图演示&#x1f345; 代码实现 &#x1f351; 判…

Linux文件打开函数open()

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> int main(void) {int fd -1; /*这个整数用来存放文件描述符*/char filename[] "good.txt"; /*打开的文件名&#xff0c;是一个字符数组…

String类 [下]

目录 一、拷贝构造和赋值重载的传统写法和现代写法 0x01 拷贝构造的传统写法 0x02 拷贝构造的现代写法 0x03 赋值重载的传统写法 0x04 赋值重载的现代写法 0x05 总结 二、 增删改查之后的string 0x01 成员函数swap: 0x02 reserve&#xff1a;改变容量 0x03 push_back: 尾…

带你深入理解Java异常

&#x1f495;“人生就像一盘棋&#xff0c;有时候你慢一步&#xff0c;就输掉了一局。但只要你不停止思考和行动&#xff0c;就永远有机会翻盘。”&#x1f495; &#x1f43c;作者&#xff1a;不能再留遗憾了&#x1f43c; &#x1f386;专栏&#xff1a;Java学习&#x1f38…

《计算机网络——自顶向下方法》精炼——3.4.1-3.4.3

聪明出于勤奋,天才在于积累。——华罗庚 文章目录 对协议的进一步改进rdt2.1rdt2.2rdt3.0&#xff1a;含有比特差错和丢包的可靠数据传输协议 流水线协议回退n步&#xff08;GBN&#xff09; 对协议的进一步改进 rdt2.1 在上一篇文章中&#xff0c;我们讲到对于产生比特差错的…

A2-RIDE Long-tailed recognition by routing diverse distribution-aware experts

文章目录 0. Abstract1. Introduction2. Related Works3. RIDE&#xff1a;ROUTING DIVERSE DISTRIBUTION-AWARE EXPERTS4. Experiments5. Summary论文总结长尾数据分布 (Long-tailed Data Distribution)RIDE方法及模型1. **Multi-expert framework**2. **Routing diversified …

RabbitMQ如何保证顺序消费

目录标题 生产者有序的情况下如何保证顺序生产单个消费者多个消费者 生产者无序的情况下消息返回队列消息不返回队列 生产者有序的情况下 如何保证顺序生产 单一生产者&#xff1a;消息生产的顺序性仅支持单一生产者。 串行发送&#xff1a;如果生产者采用多线程并行发送&…

借助国内ChatGPT平替+markmap/Xmind飞速生成思维导图

系列文章目录 借助国内ChatGPT平替MindShow&#xff0c;飞速制作PPT 文章目录 系列文章目录前言一、科大讯飞“星火”认知大模型二、使用步骤1.借助讯飞星火生成思维导图的文案2.选择markmap绘制思维导图3.选择Xmind绘制思维导图 总结 前言 随着人工智能技术的不断发展&#x…

自动操作魔法师4.9.0.0

产品下载 (won-soft.com) 如下图所示&#xff1a; 彻底远离枯燥乏味的工作 在日常办公中&#xff0c;开发票&#xff0c;更新客户资料&#xff0c;打印报表&#xff0c;录入数据等等工作是极为重要&#xff0c;但大部分时候这些工作是相当枯燥的。你不得得一遍又一遍的进行重复…

第二章: Mybatis-Plus 快速入门

目录 1. 准备工作 数据库准备: 创建Maven 父模块 2. Mybatis 整合 Mybatis-Plus 创建子模块: 准备 log4j.properties 日志文件 3. Mybatis 原生写法实现查询User 编写mybatis-config.xml文件&#xff1a; 编写User实体对象&#xff1a;&#xff08;这里使用lombok进行了…

Hyper-V搭建免费桌面云

Hyper-V 是 Microsoft 的硬件虚拟化产品。 它用于创建并运行计算机的软件版本&#xff0c;称为“虚拟机”。 每个虚拟机都像一台完整的计算机一样运行操作系统和程序。 如果需要计算资源&#xff0c;虚拟机可提供更大的灵活性、帮助节省时间和金钱&#xff0c;并且与在物理硬件…

【AI面试】RoI Pooling 和 RoI Align 辨析

RoI Pooling和RoI Align是两种常用的目标检测中的RoI特征提取方法。它们的主要区别在于&#xff1a;如何将不同大小的RoI对齐到固定大小的特征图上&#xff0c;并在这个过程中保留更多的空间信息。 一、RoI Pooling RoI Pooling最早是在Fast R-CNN中提出的&#xff0c;它的基…

MySQL MHA

概述 什么是 MHA MHA&#xff08;Master High Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的过程中…

JAVA语言-比较器Comparator(java中Comparable和Comparator的区别)

文章目录 一、什么是Comparator二、Java compare方法和compareTo方法三、java中Comparable和Comparator的区别 Comparator的例子三、demo&#xff1a;java8使用Lambda表达式比较器Comparator给List对象排序&#xff0c;按时间、数字、字典排序 一、什么是Comparator Comparato…

Unittest单元测试框架之unittest_执行用例的详细信息

unittest_执行用例的详细信息 用unittest.main()执行测试集 这里的verbosity是一个选项,表示测试结果的信息复杂度&#xff0c;有三个值&#xff1a;0 (静默模式): 你只能获得总的测试用例数和总的结果 比如 总共100个 失败20 成功801 (默认模式): 非常类似静默模式 只是在每…

MySQL数据库从入门到精通学习第3天(查看,选择,修改,删除数据库)

查看&#xff0c;选择&#xff0c;修改&#xff0c;删除数据库 查看数据库选择数据库修改数据库删除数据库 查看数据库 创建完数据库&#xff0c;可以通过SHOW命令来查看所有的数据库信息&#xff0c;语法&#xff1a; SHOW DATABASES [LIKE %模式% WHERE 条件]; >>>…