国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

MyBatis 源碼閱讀之?dāng)?shù)據(jù)庫連接

TwIStOy / 2910人閱讀

摘要:源碼閱讀之?dāng)?shù)據(jù)庫連接的配置文件所有配置會(huì)被類讀取,我們可以通過此類來了解各個(gè)配置是如何運(yùn)作的。也就是說的統(tǒng)計(jì)字段是關(guān)于整個(gè)數(shù)據(jù)源的,而一個(gè)則是針對(duì)單個(gè)連接的。

MyBatis 源碼閱讀之?dāng)?shù)據(jù)庫連接

MyBatis 的配置文件所有配置會(huì)被 org.apache.ibatis.builder.xml.XMLConfigBuilder 類讀取,
我們可以通過此類來了解各個(gè)配置是如何運(yùn)作的。
而 MyBatis 的映射文件配置會(huì)被 org.apache.ibatis.builder.xml.XMLMapperBuilder 類讀取。
我們可以通過此類來了解映射文件的配置時(shí)如何被解析的。

本文探討 事務(wù)管理器數(shù)據(jù)源 相關(guān)代碼
配置 environment

以下是 mybatis 配置文件中 environments 節(jié)點(diǎn)的一般配置。



    
        
            
        
        
            
            
            
            
        
    

environments 節(jié)點(diǎn)的加載也不算復(fù)雜,它只會(huì)加載 id 為 development 屬性值的 environment 節(jié)點(diǎn)。
它的加載代碼在 XMLConfigBuilder 類的 environmentsElement() 方法中,代碼不多,邏輯也簡單,此處不多講。

TransactionManager

接下來我們看看 environment 節(jié)點(diǎn)下的子節(jié)點(diǎn)。transactionManager 節(jié)點(diǎn)的 type 值默認(rèn)提供有 JDBCMANAGED ,dataSource 節(jié)點(diǎn)的 type 值默認(rèn)提供有 JNDIPOOLEDUNPOOLED
它們對(duì)應(yīng)的類都可以在 Configuration 類的構(gòu)造器中找到,當(dāng)然下面我們也一個(gè)一個(gè)來分析。

現(xiàn)在我們大概了解了配置,然后來分析這些配置與 MyBatis 類的關(guān)系。

TransactionFactory

transactionManager 節(jié)點(diǎn)對(duì)應(yīng) TransactionFactory 接口,使用了 抽象工廠模式 。MyBatis 給我們提供了兩個(gè)實(shí)現(xiàn)類:ManagedTransactionFactoryJdbcTransactionFactory ,它們分別對(duì)應(yīng)者 type 屬性值為 MANAGED 和 JDBC 。

TransactionFactory 有三個(gè)方法,我們需要注意的方法只有 newTransaction() ,它用來創(chuàng)建一個(gè)事務(wù)對(duì)象。

void setProperties(Properties props);

Transaction newTransaction(Connection conn);

Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

其中 JdbcTransactionFactory 創(chuàng)建的事務(wù)對(duì)象是 JdbcTransaction 的實(shí)例,該實(shí)例是對(duì) JDBC 事務(wù)的簡單封裝,實(shí)例中 ConnectionDataSource 對(duì)象正是事務(wù)所在的 連接數(shù)據(jù)源
TransactionIsolationLevel 代表當(dāng)前事務(wù)的隔離等級(jí),它是一個(gè)枚舉類,簡單明了無需多言。而 autoCommit 表示是否開啟了自動(dòng)提交,開啟了,則沒有事務(wù)的提交和回滾等操作的意義了。

ManagedTransactionFactory 創(chuàng)建的事務(wù)對(duì)象是 ManagedTransaction 的實(shí)例,它本身并不控制事務(wù),即 commitrollback 都是不做任何操作,而是交由 JavaEE 容器來控制事務(wù),以方便集成。

DataSourceFactory

DataSourceFactory 是獲取數(shù)據(jù)源的接口,也使用了 抽象工廠模式 ,代碼如下,方法極為簡單:

public interface DataSourceFactory {

    /**
     * 可傳入一些屬性配置
     */
    void setProperties(Properties props);

    DataSource getDataSource();
}

MyBatis 默認(rèn)支持三種數(shù)據(jù)源,分別是 UNPOOLEDPOOLEDJNDI 。對(duì)應(yīng)三個(gè)工廠類:
UnpooledDataSourceFactoryPooledDataSourceFactoryJNDIDataSourceFactory

其中 JNDIDataSourceFactory 是使用 JNDI 來獲取數(shù)據(jù)源。我們很少使用,并且代碼不是非常復(fù)雜,此處不討論。我們先來看看 UnpooledDataSourceFactory

public class UnpooledDataSourceFactory implements DataSourceFactory {

    private static final String DRIVER_PROPERTY_PREFIX = "driver.";
    private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();

    protected DataSource dataSource;

    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }

    @Override
    public void setProperties(Properties properties) {
        Properties driverProperties = new Properties();
        // MetaObject 用于解析實(shí)例對(duì)象的元信息,如字段的信息、方法的信息
        MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
        // 解析所有配置的鍵值對(duì)key-value,發(fā)現(xiàn)非預(yù)期的屬性立即拋異常,以便及時(shí)發(fā)現(xiàn)
        for (Object key : properties.keySet()) {
            String propertyName = (String) key;
            if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
                // 添加驅(qū)動(dòng)的配置屬性
                String value = properties.getProperty(propertyName);
                driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
            } else if (metaDataSource.hasSetter(propertyName)) {
                // 為數(shù)據(jù)源添加配置屬性
                String value = (String) properties.get(propertyName);
                Object convertedValue = convertValue(metaDataSource, propertyName, value);
                metaDataSource.setValue(propertyName, convertedValue);
            } else {
                throw new DataSourceException("Unknown DataSource property: " + propertyName);
            }
        }
        if (driverProperties.size() > 0) {
            metaDataSource.setValue("driverProperties", driverProperties);
        }
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 將 String 類型的值轉(zhuǎn)為目標(biāo)對(duì)象字段的類型的值
     */
    private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
        Object convertedValue = value;
        Class targetType = metaDataSource.getSetterType(propertyName);
        if (targetType == Integer.class || targetType == int.class) {
            convertedValue = Integer.valueOf(value);
        } else if (targetType == Long.class || targetType == long.class) {
            convertedValue = Long.valueOf(value);
        } else if (targetType == Boolean.class || targetType == boolean.class) {
            convertedValue = Boolean.valueOf(value);
        }
        return convertedValue;
    }
}

雖然代碼看起來復(fù)雜,實(shí)際上非常簡單,在創(chuàng)建工廠實(shí)例時(shí)創(chuàng)建它對(duì)應(yīng)的 UnpooledDataSource 數(shù)據(jù)源。
setProperties() 方法用于給數(shù)據(jù)源添加部分屬性配置,convertValue() 方式時(shí)一個(gè)私有方法,就是處理 當(dāng) DataSource 的屬性為整型或布爾類型時(shí)提供對(duì)字符串類型的轉(zhuǎn)換功能而已。

最后我們看看 PooledDataSourceFactory ,這個(gè)類非常簡單,僅僅是繼承了 UnpooledDataSourceFactory ,然后構(gòu)造方法替換數(shù)據(jù)源為 PooledDataSource

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

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

雖然它的代碼極少,實(shí)際上都在 PooledDataSource 類中。

DataSource

看完了工廠類,我們來看看 MyBatis 提供的兩種數(shù)據(jù)源類: UnpooledDataSourcePooledDataSource

UnpooledDataSource

UnpooledDataSource 看名字就知道是沒有池化的特征,相對(duì)也簡單點(diǎn),以下代碼省略一些不重要的方法

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class UnpooledDataSource implements DataSource {

    private ClassLoader driverClassLoader;
    private Properties driverProperties;
    private static Map registeredDrivers = new ConcurrentHashMap();

    private String driver;
    private String url;
    private String username;
    private String password;

    private Boolean autoCommit;

    // 事務(wù)隔離級(jí)別
    private Integer defaultTransactionIsolationLevel;

    static {
        // 遍歷所有可用驅(qū)動(dòng)
        Enumeration drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }

    // ......

    private Connection doGetConnection(Properties properties) throws SQLException {
        // 每次獲取連接都會(huì)檢測驅(qū)動(dòng)
        initializeDriver();
        Connection connection = DriverManager.getConnection(url, properties);
        configureConnection(connection);
        return connection;
    }

    /**
     * 初始化驅(qū)動(dòng),這是一個(gè) 同步 方法
     */
    private synchronized void initializeDriver() throws SQLException {
        // 如果不包含驅(qū)動(dòng),則準(zhǔn)備添加驅(qū)動(dòng)
        if (!registeredDrivers.containsKey(driver)) {
            Class driverType;
            try {
                // 加載驅(qū)動(dòng)
                if (driverClassLoader != null) {
                    driverType = Class.forName(driver, true, driverClassLoader);
                } else {
                    driverType = Resources.classForName(driver);
                }
                Driver driverInstance = (Driver)driverType.newInstance();
                // 注冊驅(qū)動(dòng)代理到 DriverManager
                DriverManager.registerDriver(new DriverProxy(driverInstance));
                // 緩存驅(qū)動(dòng)
                registeredDrivers.put(driver, driverInstance);
            } catch (Exception e) {
                throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
            }
        }
    }

    private void configureConnection(Connection conn) throws SQLException {
        // 設(shè)置是否自動(dòng)提交事務(wù)
        if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
            conn.setAutoCommit(autoCommit);
        }
        // 設(shè)置 事務(wù)隔離級(jí)別
        if (defaultTransactionIsolationLevel != null) {
            conn.setTransactionIsolation(defaultTransactionIsolationLevel);
        }
    }

    private static class DriverProxy implements Driver {
        private Driver driver;

        DriverProxy(Driver d) {
            this.driver = d;
        }

        /**
         * Driver 僅在 JDK7 中定義了本方法,用于返回本驅(qū)動(dòng)的所有日志記錄器的父記錄器
         * 個(gè)人也不是十分明確它的用法,畢竟很少會(huì)關(guān)注驅(qū)動(dòng)的日志
         */
        public Logger getParentLogger() {
            return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
        }

        // 其他方法均為調(diào)用 driver 對(duì)應(yīng)的方法,此處省略
    }
}

這里 DriverProxy 僅被注冊到 DriverManager 中,這是一個(gè)代理操作,但源碼上并沒有什么特別的處理代碼,我也不懂官方為什么在這里加代理,有誰明白的可以留言相互討論。這里的其他方法也不是非常復(fù)雜,我都已經(jīng)標(biāo)有注釋,應(yīng)該都可以看懂,不再細(xì)說。

以上便是 UnpooledDataSource 的初始化驅(qū)動(dòng)和獲取連接關(guān)鍵代碼。

PooledDataSource

接下來我們來看最后一個(gè)類 PooledDataSource ,它也是直接實(shí)現(xiàn) DataSource ,不過因?yàn)閾碛谐鼗奶匦裕拇a復(fù)雜不少,當(dāng)然效率比 UnpooledDataSource 會(huì)高出不少。

PooledDataSource 通過兩個(gè)輔助類 PoolStatePooledConnection 來完成池化功能。
PoolState 是記錄連接池運(yùn)行時(shí)的狀態(tài),定義了兩個(gè) PooledConnection 集合用于記錄空閑連接和活躍連接。
PooledConnection 內(nèi)部定義了兩個(gè) Connection 分別表示一個(gè)真實(shí)連接和代理連接,還有一些其他字段用于記錄一個(gè)連接的運(yùn)行時(shí)狀態(tài)。

先來詳細(xì)了解一下 PooledConnection

/**
 * 此處使用默認(rèn)的訪問權(quán)限
 * 實(shí)現(xiàn)了 InvocationHandler
 */
class PooledConnection implements InvocationHandler {

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

    /** hashCode() 方法返回 */
    private final int hashCode;

    private final Connection realConnection;

    private final Connection proxyConnection;

    // 省略 checkoutTimestamp、createdTimestamp、lastUsedTimestamp
    private boolean valid;

    /*
     * Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in
     *
     * @param connection - the connection that is to be presented as a pooled connection
     * @param dataSource - the dataSource that the connection is from
     */
    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;
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }

    /*
     * 設(shè)置連接狀態(tài)為不正常,不可使用
     */
    public void invalidate() {
        valid = false;
    }

    /*
     * 查看連接是否可用
     *
     * @return 如果可用則返回 true
     */
    public boolean isValid() {
        return valid && realConnection != null && dataSource.pingConnection(this);
    }

    /**
     * 自動(dòng)上一次使用后經(jīng)過的時(shí)間
     */
    public long getTimeElapsedSinceLastUse() {
        return System.currentTimeMillis() - lastUsedTimestamp;
    }

    /**
     * 存活時(shí)間
     */
    public long getAge() {
        return System.currentTimeMillis() - createdTimestamp;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
            // 對(duì)于 close() 方法,將連接放回池中
            dataSource.pushConnection(this);
            return null;
        } else {
            try {
                if (!Object.class.equals(method.getDeclaringClass())) {
                    checkConnection();
                }
                return method.invoke(realConnection, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
    }

    private void checkConnection() throws SQLException {
        if (!valid) {
            throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
        }
    }
}

本類實(shí)現(xiàn)了 InvocationHandler 接口,這個(gè)接口是用于 JDK 動(dòng)態(tài)代理的,在這個(gè)類的構(gòu)造器中 proxyConnection 就是創(chuàng)建了此代理對(duì)象。
來看看 invoke() 方法,它攔截了 close() 方法,不再關(guān)閉連接,而是將其繼續(xù)放入池中,然后其他已實(shí)現(xiàn)的方法則是每次調(diào)用都需要檢測連接是否合法。

PoolState 類,這個(gè)類實(shí)際上沒什么可說的,都是一些統(tǒng)計(jì)字段,沒有復(fù)雜邏輯,不討論; 需要注意該類是針對(duì)一個(gè) PooledDataSource 對(duì)象統(tǒng)計(jì)的
也就是說 PoolState 的統(tǒng)計(jì)字段是關(guān)于整個(gè)數(shù)據(jù)源的,而一個(gè) PooledConnection 則是針對(duì)單個(gè)連接的。

最后我們回過頭來看 PooledDataSource 類,數(shù)據(jù)源的操作就只有兩個(gè),獲取連接,釋放連接,先來看看獲取連接

public class PooledDataSource implements DataSource {

    private final UnpooledDataSource dataSource;

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

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return popConnection(username, password).getProxyConnection();
    }

    /**
     * 獲取一個(gè)連接
     */
    private PooledConnection popConnection(String username, String password) throws SQLException {
        boolean countedWait = false;
        PooledConnection conn = null;
        long t = System.currentTimeMillis();
        int localBadConnectionCount = 0;

        // conn == null 也可能是沒有獲得連接,被通知后再次走流程
        while (conn == null) {
            synchronized (state) {
                // 是否存在空閑連接
                if (!state.idleConnections.isEmpty()) {
                    // 池里存在空閑連接
                    conn = state.idleConnections.remove(0);
                } else {
                    // 池里不存在空閑連接
                    if (state.activeConnections.size() < poolMaximumActiveConnections) {
                        // 池里的激活連接數(shù)小于最大數(shù),創(chuàng)建一個(gè)新的
                        conn = new PooledConnection(dataSource.getConnection(), this);
                    } else {
                        // 最壞的情況,無法獲取連接

                        // 檢測最早使用的連接是否超時(shí)
                        PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                        long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                        if (longestCheckoutTime > poolMaximumCheckoutTime) {
                            // 使用超時(shí)連接,對(duì)超時(shí)連接的操作進(jìn)行回滾
                            state.claimedOverdueConnectionCount++;
                            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                            state.accumulatedCheckoutTime += longestCheckoutTime;
                            state.activeConnections.remove(oldestActiveConnection);
                            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                                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");
                                }
                            }
                            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                            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()) {
                        // 對(duì)之前的操作回滾
                        if (!conn.getRealConnection().getAutoCommit()) {
                            conn.getRealConnection().rollback();
                        }
                        conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        // 激活連接池?cái)?shù)+1
                        state.activeConnections.add(conn);
                        state.requestCount++;
                        state.accumulatedRequestTime += System.currentTimeMillis() - t;
                    } else {
                        // 連接壞掉了,超過一定閾值則拋異常提醒
                        state.badConnectionCount++;
                        localBadConnectionCount++;
                        conn = null;
                        if (localBadConnectionCount > (poolMaximumIdleConnections
                                + poolMaximumLocalBadConnectionTolerance)) {
                            // 省略日志
                            throw new SQLException(
                                    "PooledDataSource: Could not get a good connection to the database.");
                        }
                    }
                }
            }

        }

        if (conn == null) {
            // 省略日志
            throw new SQLException(
                    "PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        }

        return conn;
    }
}

上面的代碼都已經(jīng)加了注釋,總體流程不算復(fù)雜:

while => 連接為空

能否直接從池里拿連接 => 可以則獲取連接并返回

不能,查看池里的連接是否沒滿 => 沒滿則創(chuàng)建一個(gè)連接并返回

滿了,查看池里最早的連接是否超時(shí) => 超時(shí)則強(qiáng)制該連接回滾,然后獲取該連接并返回

未超時(shí),等待連接可用

檢測連接是否可用

釋放連接操作,更為簡單,判斷更少

protected void pushConnection(PooledConnection conn) throws SQLException {
    // 同步操作
    synchronized (state) {
        // 從活動(dòng)池中移除連接
        state.activeConnections.remove(conn);
        if (conn.isValid()) {
            // 不超過空閑連接數(shù) 并且連接是同一類型的連接
            if (state.idleConnections.size() < poolMaximumIdleConnections
                    && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                // 廢棄原先的對(duì)象
                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                state.idleConnections.add(newConn);
                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                // 該對(duì)象已經(jīng)不能用于連接了
                conn.invalidate();
                if (log.isDebugEnabled()) {
                    log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
                }
                state.notifyAll();
            } else {
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                // 關(guān)閉連接
                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++;
        }
    }
}

部分碼注釋已添加,這里就說一下總體流程:

從活動(dòng)池中移除連接

如果該連接可用

連接池未滿,則連接放回池中

滿了,回滾,關(guān)閉連接

總體流程大概就是這樣

以下還有兩個(gè)方法代碼較多,但邏輯都很簡單,稍微說明一下:

pingConnection() 執(zhí)行一條 SQL 檢測連接是否可用。

forceCloseAll() 回滾并關(guān)閉激活連接池和空閑連接池中的連接

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72851.html

相關(guān)文章

  • Java深入-框架技巧

    摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計(jì)模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進(jìn)階面試問題列表 -...

    chengtao1633 評(píng)論0 收藏0
  • MyBatis 源碼閱讀 databaseId

    摘要:源碼閱讀之的配置文件所有配置會(huì)被類讀取,我們可以通過此類來了解各個(gè)配置是如何運(yùn)作的。是用于項(xiàng)目中存在多種數(shù)據(jù)庫時(shí)區(qū)分同一條對(duì)應(yīng)的數(shù)據(jù)庫。可以這樣認(rèn)為,在中的和組合才是一條的唯一標(biāo)識(shí)。如果發(fā)現(xiàn)自己的沒被正確識(shí)別,可以查看方法是否和預(yù)期一致。 MyBatis 源碼閱讀之 databaseId MyBatis 的配置文件所有配置會(huì)被 org.apache.ibatis.builder.xml...

    Donald 評(píng)論0 收藏0
  • MyBatis 源碼分析系列文章合集

    摘要:簡介我從七月份開始閱讀源碼,并在隨后的天內(nèi)陸續(xù)更新了篇文章。考慮到超長文章對(duì)讀者不太友好,以及拆分文章工作量也不小等問題。經(jīng)過兩周緊張的排版,一本小小的源碼分析書誕生了。我在寫系列文章中,買了一本書作為參考,這本書是技術(shù)內(nèi)幕。 1.簡介 我從七月份開始閱讀MyBatis源碼,并在隨后的40天內(nèi)陸續(xù)更新了7篇文章。起初,我只是打算通過博客的形式進(jìn)行分享。但在寫作的過程中,發(fā)現(xiàn)要分析的代碼...

    Crazy_Coder 評(píng)論0 收藏0
  • Java進(jìn)階

    摘要:探索專為而設(shè)計(jì)的將探討進(jìn)行了何種改進(jìn),以及這些改進(jìn)背后的原因。關(guān)于最友好的文章進(jìn)階前言之前就寫過一篇關(guān)于最友好的文章反響很不錯(cuò),由于那篇文章的定位就是簡單友好,因此盡可能的摒棄復(fù)雜的概念,只抓住關(guān)鍵的東西來講,以保證大家都能看懂。 周月切換日歷 一個(gè)可以進(jìn)行周月切換的日歷,左右滑動(dòng)的切換月份,上下滑動(dòng)可以進(jìn)行周,月不同的視圖切換,可以進(jìn)行事件的標(biāo)記,以及節(jié)假日的顯示,功能豐富 Andr...

    sushi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<