成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

TwIStOy / 2911人閱讀

摘要:源碼閱讀之?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() 方法中,代碼不多,邏輯也簡(jiǎn)單,此處不多講。

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

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

DataSourceFactory

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

public interface DataSourceFactory {

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

    DataSource getDataSource();
}

MyBatis 默認(rèn)支持三種數(shù)據(jù)源,分別是 UNPOOLED 、 POOLEDJNDI 。對(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í)際上非常簡(jiǎn)單,在創(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è)類非常簡(jiǎn)單,僅僅是繼承了 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ì)也簡(jiǎn)單點(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ì)檢測(cè)驅(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();
                // 注冊(cè)驅(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 僅被注冊(cè)到 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)閾碛谐鼗奶匦?,它的代碼復(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)用都需要檢測(cè)連接是否合法。

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 {
                        // 最壞的情況,無法獲取連接

                        // 檢測(cè)最早使用的連接是否超時(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) {
                    // 檢測(cè)連接是否可用
                    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í),等待連接可用

檢測(cè)連接是否可用

釋放連接操作,更為簡(jiǎn)單,判斷更少

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è)方法代碼較多,但邏輯都很簡(jiǎn)單,稍微說明一下:

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

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/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 源碼分析系列文章合集

    摘要:簡(jiǎn)介我從七月份開始閱讀源碼,并在隨后的天內(nèi)陸續(xù)更新了篇文章。考慮到超長(zhǎng)文章對(duì)讀者不太友好,以及拆分文章工作量也不小等問題。經(jīng)過兩周緊張的排版,一本小小的源碼分析書誕生了。我在寫系列文章中,買了一本書作為參考,這本書是技術(shù)內(nèi)幕。 1.簡(jiǎn)介 我從七月份開始閱讀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ò),由于那篇文章的定位就是簡(jiǎn)單友好,因此盡可能的摒棄復(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元查看
<