最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何在MyBatis中實(shí)現(xiàn)DataSource

 更新時(shí)間:2021年06月02日 14:28:41   作者:波波烤鴨  
今天給大家整理了如何在MyBatis中實(shí)現(xiàn)DataSource,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下

一、DataSource

首先大家要清楚DataSource屬于MyBatis三層架構(gòu)設(shè)計(jì)的基礎(chǔ)層

在這里插入圖片描述  

然后我們來(lái)看看具體的實(shí)現(xiàn)。

在數(shù)據(jù)持久層中,數(shù)據(jù)源是一個(gè)非常重要的組件,其性能直接關(guān)系到整個(gè)數(shù)據(jù)持久層的性能,在實(shí)際開(kāi)發(fā)中我們常用的數(shù)據(jù)源有 Apache Common DBCP,C3P0,Druid 等,MyBatis不僅可以集成第三方數(shù)據(jù)源,還提供的有自己實(shí)現(xiàn)的數(shù)據(jù)源。

在MyBatis中提供了兩個(gè) javax.sql.DataSource 接口的實(shí)現(xiàn),分別是 PooledDataSource 和 UnpooledDataSource .

在這里插入圖片描述

二、DataSourceFactory

DataSourceFactory是用來(lái)創(chuàng)建DataSource對(duì)象的,接口中聲明了兩個(gè)方法,作用如下

public interface DataSourceFactory {
  // 設(shè)置 DataSource 的相關(guān)屬性,一般緊跟在初始化完成之后
  void setProperties(Properties props);

  // 獲取 DataSource 對(duì)象
  DataSource getDataSource();

}

DataSourceFactory接口的兩個(gè)具體實(shí)現(xiàn)是 UnpooledDataSourceFactory 和 PooledDataSourceFactory 這兩個(gè)工廠對(duì)象的作用通過(guò)名稱我們也能發(fā)現(xiàn)是用來(lái)創(chuàng)建不帶連接池的數(shù)據(jù)源對(duì)象和創(chuàng)建帶連接池的數(shù)據(jù)源對(duì)象,先來(lái)看下 UnpooledDataSourceFactory 中的方法

/**
   * 完成對(duì) UnpooledDataSource 的配置
   * @param properties 封裝的有 DataSource 所需要的相關(guān)屬性信息
   */
  @Override
  public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    // 創(chuàng)建 DataSource 對(duì)應(yīng)的 MetaObject 對(duì)象
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    // 遍歷 Properties 集合,該集合中配置了數(shù)據(jù)源需要的信息
    for (Object key : properties.keySet()) {
      String propertyName = (String) key; // 獲取屬性名稱
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        // 以 "driver." 開(kāi)頭的配置項(xiàng)是對(duì) DataSource 的配置
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) {
        // 有該屬性的 setter 方法
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        // 設(shè)置 DataSource 的相關(guān)屬性值
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      // 設(shè)置 DataSource.driverProperties 的屬性值
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

UnpooledDataSourceFactory的getDataSource方法實(shí)現(xiàn)比較簡(jiǎn)單,直接返回DataSource屬性記錄的 UnpooledDataSource 對(duì)象

三、UnpooledDataSource

UnpooledDataSource 是 DataSource接口的其中一個(gè)實(shí)現(xiàn),但是 UnpooledDataSource 并沒(méi)有提供數(shù)據(jù)庫(kù)連接池的支持,我們來(lái)看下他的具體實(shí)現(xiàn)吧

聲明的相關(guān)屬性信息

 private ClassLoader driverClassLoader; // 加載Driver的類加載器
  private Properties driverProperties; // 數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)的相關(guān)信息
  // 緩存所有已注冊(cè)的數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();

  private String driver; // 驅(qū)動(dòng)
  private String url; // 數(shù)據(jù)庫(kù) url
  private String username; // 賬號(hào)
  private String password; // 密碼

  private Boolean autoCommit; // 是否自動(dòng)提交
  private Integer defaultTransactionIsolationLevel; // 事務(wù)隔離級(jí)別
  private Integer defaultNetworkTimeout;

然后在靜態(tài)代碼塊中完成了 Driver的復(fù)制

static {
    // 從 DriverManager 中獲取 Drivers
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      // 將獲取的 Driver 記錄到 Map 集合中
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }

UnpooledDataSource 中獲取Connection的方法最終都會(huì)調(diào)用 doGetConnection() 方法。

 private Connection doGetConnection(Properties properties) throws SQLException {
      // 初始化數(shù)據(jù)庫(kù)驅(qū)動(dòng)
      initializeDriver();
    // 創(chuàng)建真正的數(shù)據(jù)庫(kù)連接
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置Connection的自動(dòng)提交和事務(wù)隔離級(jí)別
    configureConnection(connection);
    return connection;
  }

四、PooledDataSource

有開(kāi)發(fā)經(jīng)驗(yàn)的小伙伴都知道,在操作數(shù)據(jù)庫(kù)的時(shí)候數(shù)據(jù)庫(kù)連接的創(chuàng)建過(guò)程是非常耗時(shí)的,數(shù)據(jù)庫(kù)能夠建立的連接數(shù)量也是非常有限的,所以數(shù)據(jù)庫(kù)連接池的使用是非常重要的,使用數(shù)據(jù)庫(kù)連接池會(huì)給我們帶來(lái)很多好處,比如可以實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接的重用,提高響應(yīng)速度,防止數(shù)據(jù)庫(kù)連接過(guò)多造成數(shù)據(jù)庫(kù)假死,避免數(shù)據(jù)庫(kù)連接泄漏等等。

首先來(lái)看下聲明的相關(guān)的屬性

// 管理狀態(tài)
  private final PoolState state = new PoolState(this);

  // 記錄UnpooledDataSource,用于生成真實(shí)的數(shù)據(jù)庫(kù)連接對(duì)象
  private final UnpooledDataSource dataSource;

  // OPTIONAL CONFIGURATION FIELDS
  protected int poolMaximumActiveConnections = 10; // 最大活躍連接數(shù)
  protected int poolMaximumIdleConnections = 5; // 最大空閑連接數(shù)
  protected int poolMaximumCheckoutTime = 20000; // 最大checkout時(shí)間
  protected int poolTimeToWait = 20000; // 無(wú)法獲取連接的線程需要等待的時(shí)長(zhǎng)
  protected int poolMaximumLocalBadConnectionTolerance = 3; //
  protected String poolPingQuery = "NO PING QUERY SET"; // 測(cè)試的SQL語(yǔ)句
  protected boolean poolPingEnabled; // 是否允許發(fā)送測(cè)試SQL語(yǔ)句
  // 當(dāng)連接超過(guò) poolPingConnectionsNotUsedFor毫秒未使用時(shí),會(huì)發(fā)送一次測(cè)試SQL語(yǔ)句,檢測(cè)連接是否正常
  protected int poolPingConnectionsNotUsedFor;
 // 根據(jù)數(shù)據(jù)庫(kù)URL,用戶名和密碼生成的一個(gè)hash值。
  private int expectedConnectionTypeCode;

然后重點(diǎn)來(lái)看下 getConnection 方法,該方法是用來(lái)給調(diào)用者提供 Connection 對(duì)象的。

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

我們會(huì)發(fā)現(xiàn)其中調(diào)用了 popConnection 方法,在該方法中 返回的是 PooledConnection 對(duì)象,而 PooledConnection 對(duì)象實(shí)現(xiàn)了 InvocationHandler 接口,所以會(huì)使用到Java的動(dòng)態(tài)代理,其中相關(guān)的屬性為

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

  private final int hashCode;
  private final PooledDataSource dataSource;
  //  真正的數(shù)據(jù)庫(kù)連接
  private final Connection realConnection;
  //  數(shù)據(jù)庫(kù)連接的代理對(duì)象
  private final Connection proxyConnection;
  private long checkoutTimestamp; // 從連接池中取出該連接的時(shí)間戳
  private long createdTimestamp; // 該連接創(chuàng)建的時(shí)間戳
  private long lastUsedTimestamp; // 最后一次被使用的時(shí)間戳
  private int connectionTypeCode; // 又?jǐn)?shù)據(jù)庫(kù)URL、用戶名和密碼計(jì)算出來(lái)的hash值,可用于標(biāo)識(shí)該連接所在的連接池
  // 連接是否有效的標(biāo)志
  private boolean valid;

重點(diǎn)關(guān)注下invoke 方法

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.equals(methodName)) {
      // 如果是 close 方法被執(zhí)行則將連接放回連接池中,而不是真正的關(guān)閉數(shù)據(jù)庫(kù)連接
      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
        // 通過(guò)上面的 valid 字段來(lái)檢測(cè) 連接是否有效
        checkConnection();
      }
      // 調(diào)用真正數(shù)據(jù)庫(kù)連接對(duì)象的對(duì)應(yīng)方法
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

  }

還有就是前面提到的 PoolState 對(duì)象,它主要是用來(lái)管理 PooledConnection 對(duì)象狀態(tài)的組件,通過(guò)兩個(gè) ArrayList 集合分別管理空閑狀態(tài)的連接和活躍狀態(tài)的連接,定義如下:

 protected PooledDataSource dataSource;
  // 空閑的連接
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  // 活躍的連接
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  protected long requestCount = 0; // 請(qǐng)求數(shù)據(jù)庫(kù)連接的次數(shù)
  protected long accumulatedRequestTime = 0; // 獲取連接累計(jì)的時(shí)間
  // CheckoutTime 表示應(yīng)用從連接池中取出來(lái),到歸還連接的時(shí)長(zhǎng)
  // accumulatedCheckoutTime 記錄了所有連接累計(jì)的CheckoutTime時(shí)長(zhǎng)
  protected long accumulatedCheckoutTime = 0;
  // 當(dāng)連接長(zhǎng)時(shí)間沒(méi)有歸還連接時(shí),會(huì)被認(rèn)為該連接超時(shí)
  // claimedOverdueConnectionCount 記錄連接超時(shí)的個(gè)數(shù)
  protected long claimedOverdueConnectionCount = 0;
  // 累計(jì)超時(shí)時(shí)間
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  // 累計(jì)等待時(shí)間
  protected long accumulatedWaitTime = 0;
  // 等待次數(shù)
  protected long hadToWaitCount = 0;
  // 無(wú)效連接數(shù)
  protected long badConnectionCount = 0;

再回到 popConnection 方法中來(lái)看

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()) { // 檢測(cè)空閑連接
          // Pool has available connection 連接池中有空閑的連接
          conn = state.idleConnections.remove(0); // 獲取連接
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {// 當(dāng)前連接池 沒(méi)有空閑連接
          // Pool does not have available connection
          if (state.activeConnections.size() < poolMaximumActiveConnections) { // 活躍數(shù)沒(méi)有達(dá)到最大連接數(shù) 可以創(chuàng)建新的連接
            // Can create new connection 創(chuàng)建新的數(shù)據(jù)庫(kù)連接
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else { // 活躍數(shù)已經(jīng)達(dá)到了最大數(shù) 不能創(chuàng)建新的連接
            // Cannot create new connection 獲取最先創(chuàng)建的活躍連接
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            // 獲取該連接的超時(shí)時(shí)間
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            // 檢查是否超時(shí)
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection  對(duì)超時(shí)連接的信息進(jìn)行統(tǒng)計(jì)
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              // 將超時(shí)連接移除 activeConnections
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                // 如果超時(shí)連接沒(méi)有提交 則自動(dòng)回滾
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  /*
                     Just log a message for debug and continue to execute the following
                     statement like nothing happened.
                     Wrap the bad connection with a new PooledConnection, this will help
                     to not interrupt current executing thread and give current thread a
                     chance to join the next competition for another valid/good database
                     connection. At the end of this loop, bad {@link @conn} will be set as null.
                   */
                  log.debug("Bad connection. Could not roll back");
                }
              }
              // 創(chuàng)建 PooledConnection,但是數(shù)據(jù)庫(kù)中的真正連接并沒(méi)有創(chuàng)建
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              // 將超時(shí)的 PooledConnection 設(shè)置為無(wú)效
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // Must wait  無(wú)空閑連接,無(wú)法創(chuàng)建新連接和無(wú)超時(shí)連接 那就只能等待
              try {
                if (!countedWait) {
                  state.hadToWaitCount++; // 統(tǒng)計(jì)等待次數(shù)
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait); // 阻塞等待
                // 統(tǒng)計(jì)累計(jì)的等待時(shí)間
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          // ping to server and check the connection is valid or not
          // 檢查 PooledConnection 是否有效
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            // 配置 PooledConnection 的相關(guān)屬性
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            state.requestCount++; // 進(jìn)行相關(guān)的統(tǒng)計(jì)
            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.");
            }
          }
        }
      }

    }

為了更好的理解代碼的含義,我們繪制了對(duì)應(yīng)的流程圖

在這里插入圖片描述  

然后我們來(lái)看下當(dāng)我們從連接池中使用完成了數(shù)據(jù)庫(kù)的相關(guān)操作后,是如何來(lái)關(guān)閉連接的呢?通過(guò)前面的 invoke 方法的介紹其實(shí)我們能夠發(fā)現(xiàn),當(dāng)我們執(zhí)行代理對(duì)象的 close 方法的時(shí)候其實(shí)是執(zhí)行的 pushConnection 方法。

在這里插入圖片描述

具體的實(shí)現(xiàn)代碼為

protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      // 從 activeConnections 中移除 PooledConnection 對(duì)象
      state.activeConnections.remove(conn);
      if (conn.isValid()) { // 檢測(cè) 連接是否有效
        if (state.idleConnections.size() < poolMaximumIdleConnections // 是否達(dá)到上限
            && conn.getConnectionTypeCode() == expectedConnectionTypeCode // 該 PooledConnection 是否為該連接池的連接
        ) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累計(jì) checkout 時(shí)長(zhǎng)
          if (!conn.getRealConnection().getAutoCommit()) { // 回滾未提交的事務(wù)
            conn.getRealConnection().rollback();
          }
          // 為返還連接創(chuàng)建新的 PooledConnection 對(duì)象
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          // 添加到 空閑連接集合中
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          conn.invalidate(); // 將原來(lái)的 PooledConnection 連接設(shè)置為無(wú)效
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          // 喚醒阻塞等待的線程
          state.notifyAll();
        } else { // 空閑連接達(dá)到上限或者 PooledConnection不屬于當(dāng)前的連接池
          state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累計(jì) checkout 時(shí)長(zhǎng)
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close(); // 關(guān)閉真正的數(shù)據(jù)庫(kù)連接
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate(); // 設(shè)置 PooledConnection 無(wú)線
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++; // 統(tǒng)計(jì)無(wú)效的 PooledConnection 對(duì)象個(gè)數(shù)
      }
    }
  }

為了便于理解,我們同樣的來(lái)繪制對(duì)應(yīng)的流程圖:

在這里插入圖片描述

還有就是我們?cè)谠创a中多處有看到 conn.isValid方法來(lái)檢測(cè)連接是否有效

 public boolean isValid() {
    return valid && realConnection != null && dataSource.pingConnection(this);
  }

dataSource.pingConnection(this)中會(huì)真正的實(shí)現(xiàn)數(shù)據(jù)庫(kù)的SQL執(zhí)行操作

在這里插入圖片描述  

最后一點(diǎn)要注意的是在我們修改了任意的PooledDataSource中的屬性的時(shí)候都會(huì)執(zhí)行forceCloseAll來(lái)強(qiáng)制關(guān)閉所有的連接。

在這里插入圖片描述

 /**
   * Closes all active and idle connections in the pool.
   */
  public void forceCloseAll() {
    synchronized (state) {
      // 更新 當(dāng)前的 連接池 標(biāo)識(shí)
      expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
      for (int i = state.activeConnections.size(); i > 0; i--) {// 處理全部的活躍連接
        try {
          // 獲取 獲取的連接
          PooledConnection conn = state.activeConnections.remove(i - 1);
          conn.invalidate(); // 標(biāo)識(shí)為無(wú)效連接
          // 獲取真實(shí)的 數(shù)據(jù)庫(kù)連接
          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
            realConn.rollback(); // 回滾未處理的事務(wù)
          }
          realConn.close(); // 關(guān)閉真正的數(shù)據(jù)庫(kù)連接
        } catch (Exception e) {
          // ignore
        }
      }
      // 同樣的邏輯處理空閑的連接
      for (int i = state.idleConnections.size(); i > 0; i--) {
        try {
          PooledConnection conn = state.idleConnections.remove(i - 1);
          conn.invalidate();

          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
            realConn.rollback();
          }
          realConn.close();
        } catch (Exception e) {
          // ignore
        }
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("PooledDataSource forcefully closed/removed all connections.");
    }
  }

到此這篇關(guān)于如何在MyBatis中實(shí)現(xiàn)DataSource的文章就介紹到這了,更多相關(guān)DataSource的實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談Java中的atomic包實(shí)現(xiàn)原理及應(yīng)用

    淺談Java中的atomic包實(shí)現(xiàn)原理及應(yīng)用

    這篇文章主要介紹了淺談Java中的atomic包實(shí)現(xiàn)原理及應(yīng)用,涉及Atomic在硬件上的支持,Atomic包簡(jiǎn)介及源碼分析等相關(guān)內(nèi)容,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • 詳解JDBC使用

    詳解JDBC使用

    JDBC(Java Database Connectivity),即Java數(shù)據(jù)庫(kù)連接,是一種用于執(zhí)行SQL語(yǔ)句的Java API,可以為多種關(guān)系數(shù)據(jù)庫(kù)提供同一訪問(wèn),它由一組用Java語(yǔ)言編寫的類和接口組成。
    2017-05-05
  • Spring?@Bean注解深入分析源碼執(zhí)行過(guò)程

    Spring?@Bean注解深入分析源碼執(zhí)行過(guò)程

    隨著SpringBoot的流行,我們現(xiàn)在更多采用基于注解式的配置從而替換掉了基于XML的配置,所以本篇文章我們主要探討基于注解的@Bean以及和其他注解的使用
    2023-01-01
  • 騰訊開(kāi)源消息中間件TubeMQ總體介紹分析

    騰訊開(kāi)源消息中間件TubeMQ總體介紹分析

    這篇文章主要為大家介紹了騰訊開(kāi)源消息中間件TubeMQ的總體介紹分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-02-02
  • Java圖片處理開(kāi)源框架Thumbnailator

    Java圖片處理開(kāi)源框架Thumbnailator

    這篇文章主要為大家詳細(xì)介紹了Java圖片處理開(kāi)源框架Thumbnailator的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • spring cloud gateway中如何讀取請(qǐng)求參數(shù)

    spring cloud gateway中如何讀取請(qǐng)求參數(shù)

    這篇文章主要介紹了spring cloud gateway中如何讀取請(qǐng)求參數(shù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 詳解Java中NullPointerException異常的原因和解決辦法

    詳解Java中NullPointerException異常的原因和解決辦法

    本文主要介紹了詳解Java中NullPointerException異常的原因和解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • 利用Java對(duì)比兩個(gè)PDF文件之間的差異

    利用Java對(duì)比兩個(gè)PDF文件之間的差異

    這篇文章主要為大家詳細(xì)介紹了如何在 Java 程序中通過(guò)代碼快速比較兩個(gè) PDF 文檔并找出文檔之間的內(nèi)容差異,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-10-10
  • 基于Transactional事務(wù)的使用以及注意說(shuō)明

    基于Transactional事務(wù)的使用以及注意說(shuō)明

    這篇文章主要介紹了Transactional事務(wù)的使用以及注意說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Springboot整合quartz產(chǎn)生錯(cuò)誤及解決方案

    Springboot整合quartz產(chǎn)生錯(cuò)誤及解決方案

    這篇文章主要介紹了Springboot整合quartz產(chǎn)生錯(cuò)誤及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06

最新評(píng)論

海晏县| 阿克苏市| 泾源县| 靖西县| 法库县| 隆林| 登封市| 偃师市| 武宣县| 渭南市| 克什克腾旗| 宜黄县| 治县。| 开阳县| 左贡县| 安西县| 四平市| 苏尼特左旗| 青阳县| 阿巴嘎旗| 温州市| 天台县| 徐汇区| 大关县| 黎川县| 游戏| 拉萨市| 夹江县| 微山县| 太湖县| 满洲里市| 陵川县| 长武县| 当阳市| 涟水县| 綦江县| 上栗县| 灵丘县| 盐亭县| 沙雅县| 荆门市|