MyBatis原理歸納

博文目標:但願你們看了這篇博文後,對Mybatis總體運行過程有一個清晰的認識和把握。

1.什麼是 MyBatis ?

MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。(這是官網解釋)spring

2.MyBatis運行原理

MyBatis運行原理

框架圖解釋說明

當框架啓動時,經過configuration解析config.xml配置文件和mapper.xml映射文件,映射文件可使用xml方式或者註解方式,而後由configuration得到sqlsessionfactory對象,再由sqlsessionfactory得到sqlsession數據庫訪問會話對象,經過會話對象得到對應DAO層的mapper對象,經過調用mapper對象相應方法,框架就會自動執行SQL語句從而得到結果。
講完了,6不6,能夠,牛逼,就這麼簡單。此時心中是否有千萬只草泥馬奔涌而出,別急,對於上述,我會在下面針對重點進行一一講解。sql

3.xml解析&配置解析

這裏請你們自行百度解決,網上也有比較多的解析庫,對於你們來講應該是沒有什麼問題,咱們這邊主要抓住框架運行的整體過程。對於細節你們能夠課後慢慢研究。數據庫

mybatis啓動(編程式)編程

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

咱們再來看下這個build操做在底層作了什麼緩存

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
            ;
        }
    }
    return var5;
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

咱們能夠很明顯的看到mybatis經過XMLConfigBuilder初始化而且解析了咱們的配置文件,最後獲得一個Configuration類型的對象傳給另一個build操做,這個build操做最後直接new了一個DefaultSqlSessionFactory對象而且返回。session

4.何爲Mapper對象?

經過上面的敘述咱們已經知道咱們與mybatis交互主要是經過配置文件或者配置對象,可是咱們最終的目的是要操做數據庫的,因此mybatis爲咱們提供了sqlSession這個對象來進行全部的操做,也就是說咱們真正經過mybatis操做數據庫只要對接sqlSession這個對象就能夠了。那麼問題來了,咱們怎麼樣經過sqlSession來了操做數據庫的呢?mybatis

  • 問題1:如何獲取sqlSession?

public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
    
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        Executor executor = this.configuration.newExecutor(tx, execType);
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }
    
    return var8;
}

由上面代碼咱們可知咱們能夠經過SqlSessionFactory的openSession去獲取咱們的sqlSession,也就是默認獲得一個DefaultSqlSession對象。app

  • 問題2:Mapper對象怎麼來的?

平時咱們使用以下代碼得到一個Mapper對象。框架

public <T> T getMapper(Class<T> type) {
    return this.configuration.getMapper(type, this);
}

經過調用DefaultSqlSession的getMapper方法而且傳入一個類型對象獲取,底層呢調用的是配置對象configuration的getMapper方法,configuration對象是咱們在加載DefaultSqlSessionFactory時傳入的。ui

而後咱們再來看下這個配置對象的getMapper,傳入的是類型對象(補充一點這個類型對象就是咱們平時寫的DAO層接口,裏面是一些數據庫操做的接口方法。),和自身也就是DefaultSqlSession。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return this.mapperRegistry.getMapper(type, sqlSession);
}

咱們看到這個configuration的getMapper方法裏調用的是mapperRegistry的getMapper方法,參數依然是類型對象和sqlSession。這裏呢,咱們要先來看下這個MapperRegistry即所謂Mapper註冊器是什麼。

public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }
    ....
}

從這裏咱們能夠知道其實啊這個MapperRegistry就是保持了一個Configuration對象和一個HashMap,而這個HashMap的key是類型對象,value呢是MapperProxyFactory。咱們這裏先無論MapperProxyFactory是什麼東西,咱們如今只須要知道MapperRegistry是這麼一個東西就能夠了。這裏有人會問MapperRegistry對象是怎麼來的,這裏呢是在初始化Configuration對象時初始化了這個MapperRegistry對象的,代碼你們能夠去看,爲了不混亂,保持貼出來的代碼是一條線走下來的,這裏就不貼出來了。接下來咱們繼續看下這個MapperRegistry的getMapper方法。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

這裏咱們能夠看到從knownMappers中獲取key爲類型對象的MapperProxyFactory對象。而後調用MapperProxyFactory對象的newInstance方法返回,newInstance方法傳入sqlSession對象。到這裏咱們可能看不出什麼端倪,那咱們就繼續往下看這個newInstance方法作的什麼事情吧。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

這裏咱們能夠看到MapperProxyFactory直接new了一個MapperProxy對象,而後調用另一重載的newInstance方法傳入MapperProxy對象。這裏咱們能夠看出一些東西了,經過調用Proxy.newProxyInstance動態代理了咱們的mapperProxy對象!這裏的mapperInterface即咱們的dao層(持久層)接口的類型對象。
因此總結下就是咱們經過sqlSesssion.getMapper(clazz)獲得的Mapper對象是一個mapperProxy的代理類!
因此也就引出下面的問題。

  • 問題3:爲何我調用mapper對象方法就能發出sql操做數據庫?

經過上面的講解,咱們知道了這個mapper對象實際上是一個一個mapperProxy的代理類!因此呢這個mapperProxy必然實現了InvocationHandler接口。

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
    ....
}

因此當咱們調用咱們的持久層接口的方法時必然就會調用到這個MapperProxy對象的invoke方法,因此接下來咱們進入這個方法看看具體mybatis爲咱們作了什麼。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
        try {
            return method.invoke(this, args);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    } else {
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
    if (mapperMethod == null) {
        mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
        this.methodCache.put(method, mapperMethod);
    }

    return mapperMethod;
}

從代碼中咱們能夠看到前面作了一個判斷,這個判斷主要是防止咱們調用像toString方法或者equals方法時也能正常調用。而後咱們能夠看到它調用cachedMapperMethod返回MapperMethod對象,接着就執行這個MapperMethod對象的execute方法。這個cachedMapperMethod方法主要是能緩存咱們使用過的一些mapperMethod對象,方便下次使用。這個MapperMethod對象主要是獲取方法對應的sql命令和執行相應SQL操做等的處理,具體細節同窗們能夠抽空研究。

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }
    ....
}

說到這個mapperMethod對象的execute方法,咱們看下代碼具體作了什麼事情吧。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object param;
    Object result;
    switch(this.command.getType()) {
    case INSERT:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

咱們能夠清晰的看到這裏針對數據庫的增刪改查作了對應的操做,這裏咱們能夠看下查詢操做。咱們能夠看到這裏針對方法的不一樣返回值做了不一樣的處理,咱們看下其中一種狀況。

param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);

這裏咱們能夠看到它將方法參數類型轉換成數據庫層面上的參數類型,最後調用sqlSession對象的selectOne方法執行。因此咱們看到最後仍是回到sqlSession對象上來,也就是前面所說的sqlSession是mybatis提供的與數據庫交互的惟一對象。

接下來咱們看下這個selectOne方法作了什麼事,這裏咱們看的是defaultSqlSession的selectOne方法。

public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

咱們看到它調用selectList方法,經過去返回值的第一個值做爲結果返回。那麼咱們來看下這個selectList方法。

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    List var5;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception var9) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
    } finally {
        ErrorContext.instance().reset();
    }

    return var5;
}

咱們能夠看到這裏調用了executor的query方法,咱們再進入到query裏看看。這裏咱們看的是BaseExecutor的query方法。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
    return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
            this.clearLocalCache();
        }

        List list;
        try {
            ++this.queryStack;
            list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
            if (list != null) {
                this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            --this.queryStack;
        }

        if (this.queryStack == 0) {
            Iterator i$ = this.deferredLoads.iterator();

            while(i$.hasNext()) {
                BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
                deferredLoad.load();
            }

            this.deferredLoads.clear();
            if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                this.clearLocalCache();
            }
        }

        return list;
    }
}

這裏咱們抓住這樣的一句話

list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

進入這個方法

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        this.localCache.removeObject(key);
    }

    this.localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        this.localOutputParameterCache.putObject(key, parameter);
    }

    return list;
}

咱們看到有個一個方法doQuery,進入方法看看作了什麼。點進去後咱們發現是抽象方法,咱們選擇simpleExecutor子類查看實現。

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;

    List var9;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Connection connection = this.getConnection(statementLog);
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

咱們能夠看到經過configuration對象的newStatementHandler方法構建了一個StatementHandler,而後在調用prepareStatement方法中獲取鏈接對象,經過StatementHandler獲得Statement對象。另外咱們注意到在獲取了Statement對象後調用了parameterize方法。繼續跟蹤下去(自行跟蹤哈)咱們能夠發現會調用到ParameterHandler對象的setParameters去處理咱們的參數。因此這裏的prepareStatement方法主要使用了StatementHandler和ParameterHandler對象幫助咱們處理語句集和參數的處理。最後還調用了StatementHandler的query方法,咱們繼續跟蹤下去。

這裏咱們進入到PreparedStatementHandler這個handler查看代碼。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    c ps = (PreparedStatement)statement;
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}

看到這裏,咱們終於找到了操做數據庫的地方了,就是ps.execute()這句代碼。底層咱們能夠發現就是咱們平時寫的JDBC!而後將這個執行後的PreparedStatement交給resultSetHandler處理結果集,最後返回咱們須要的結果集。

以上,咱們將mybatis的整體運行思路跟你們講解了一遍,不少地方沒有講到細節上,由於本篇主要目的就是帶你們熟悉mybatis整體流程的,細節你們能夠私底下結合mybatis的執行流程去梳理和理解。

好啦,你們有什麼問題均可以在評論區評論,我看到會盡快回復噠。哎呀,終於寫完了。拜拜。

噢,對了,這裏預告下,下下篇我將帶你們手寫一遍mybatis!沒錯,純手寫還能跑起來的那種!那下篇呢,下篇固然仍是講mybatis啦,不過是spring-mybatis!

相關文章
相關標籤/搜索