真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Mybatis中Mapper與接口綁定原理的源碼分析

今天就跟大家聊聊有關(guān)Mybatis中Mapper與接口綁定原理的源碼分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

10年積累的做網(wǎng)站、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站設(shè)計后付款的網(wǎng)站建設(shè)流程,更有南明免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

這里再來分析下,Mapper與接口綁定原理。

本章疑問:

// 5.操作Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
public interface UserMapper {
    public UserEntity getUser(int id);
}

為什么UserMapper是接口,沒用實(shí)現(xiàn)類,那么他是怎么初始化的?getMapper()方法為什么可以調(diào)用?

mapper接口是怎么初始化的?是反射?不是的,接口是不能反射初始化。揭秘:其實(shí)是代理設(shè)計模式【動態(tài)代理】,底層使用AOP實(shí)現(xiàn)。

另外MyBayis中最重要的是SqlSession:操縱SQL語句。

分析源碼前,我們先回顧下動態(tài)代理技術(shù),在我的這篇博客中詳細(xì)介紹了:淺談Java【代理設(shè)計模式】——看這篇文章就懂了。

思考問題:動態(tài)代理分為:jdk動態(tài)代理和CGLIB動態(tài)代理,那么Mybatis使用了那種代理設(shè)計模式?

答案:MyBatis采用的jdk動態(tài)代理,因?yàn)榇淼氖墙涌凇?/p>

回顧jdk動態(tài)代理

JDK動態(tài)代理的一般步驟如下:

1.創(chuàng)建被代理的接口和類;

2.實(shí)現(xiàn)InvocationHandler接口,對目標(biāo)接口中聲明的所有方法進(jìn)行統(tǒng)一處理;

3.調(diào)用Proxy的靜態(tài)方法,創(chuàng)建代理類并生成相應(yīng)的代理對象;

代碼實(shí)現(xiàn)jdk動態(tài)代理:

/**
 * 1.創(chuàng)建被代理的接口和類;
 */
public interface OrderService {
    public String add();
}
public class OrderServiceImpl implements OrderService {
    public String add() {
        System.out.println("OrderServiceImpl add。。。");
        return "success";
    }
}
/**
 * 2.實(shí)現(xiàn)InvocationHandler接口,對目標(biāo)接口中聲明的所有方法進(jìn)行統(tǒng)一處理;
 */
public class JdkMapperProxy implements InvocationHandler {
    //目標(biāo)對象,被代理對象
    private Object targect;
    public JdkMapperProxy(Object targect){
        this.targect=targect;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置通知...在代理方法之前處理");
        //目標(biāo)方法,目標(biāo)方法參數(shù)
        Object result = method.invoke(targect, args);//被執(zhí)行目標(biāo)方法,被代理的方法
        System.out.println("后置通知...在代理方法之后處理");
        return null;
    }
}
/**
 * 3.調(diào)用Proxy的靜態(tài)方法,創(chuàng)建代理類并生成相應(yīng)的代理對象;
 */
public class TestMybatis02 {
    public static void main(String[] args) {

        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        OrderService orderService = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader()
                , OrderServiceImpl.class.getInterfaces(), new JdkMapperProxy(new OrderServiceImpl()));
        orderService.add();
    }
}

運(yùn)行TestMybatis02 結(jié)果如下:

前置通知...在代理方法之前處理
OrderServiceImpl add。。。
后置通知...在代理方法之后處理

生成的代理類

Mybatis中Mapper與接口綁定原理的源碼分析

回顧了下jdk動態(tài)代理,下面我們開始源碼分析

思考問題:會不會把下面這段配置轉(zhuǎn)為實(shí)體類


    select * from user where id=#{id}

答案是肯定的,在那里進(jìn)行解析的呢?下面開始分析源碼:下面就是解析的地方

private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null && !namespace.equals("")) {
         ....
            //進(jìn)入這里
            this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } else {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
    } catch (Exception var3) {
        throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
    }
}

重點(diǎn)這段代碼:

this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(List list) {
    if (this.configuration.getDatabaseId() != null) {
        //會進(jìn)入到這里
        this.buildStatementFromContext(list, this.configuration.getDatabaseId());
    }
    this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
    Iterator i$ = list.iterator();
    while(i$.hasNext()) {
        XNode context = (XNode)i$.next();
        XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
        try {
            //進(jìn)入到這里
            statementParser.parseStatementNode();
        } catch (IncompleteElementException var7) {
            this.configuration.addIncompleteStatement(statementParser);
        }
    }
}
public void parseStatementNode() {
    String id = this.context.getStringAttribute("id");
    String databaseId = this.context.getStringAttribute("databaseId");
    if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
         ....
        if (this.configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        }
        //最終到這里了
        this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
}
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
    if (this.unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    } else {
      .....
        //進(jìn)入這里
        this.configuration.addMappedStatement(statement);
        return statement;
    }
}
public void addMappedStatement(MappedStatement ms) {
    //最終結(jié)果
    this.mappedStatements.put(ms.getId(), ms);
}
protected final Map mappedStatements;
this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
protected static class StrictMap extends HashMap {

Mybatis中Mapper與接口綁定原理的源碼分析

Mybatis中Mapper與接口綁定原理的源碼分析

通過上面的代碼執(zhí)行流程,最終我們知道,mapper.xml中的配置文件里的每條sql語句是如何轉(zhuǎn)化為對象保存起來的。最終都是封裝成一個MappedStatement對象,再通過一個HashMap集合保存起來。

Mybatis中Mapper與接口綁定原理的源碼分析

通過源碼可知:HadhMap被put了兩次

后面我們來分析getMapper()方法:默認(rèn)走的是DefaultSqlSession

// 5.操作Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

Mybatis中Mapper與接口綁定原理的源碼分析

public  T getMapper(Class type) {
    return this.configuration.getMapper(type, this);
}
public  T getMapper(Class type, SqlSession sqlSession) {
    return this.mapperRegistry.getMapper(type, sqlSession);
}
public  T getMapper(Class type, SqlSession sqlSession) {
    MapperProxyFactory 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);
        }
    }
}

由上面代碼可知:通過configuration.getMapper()去查下我們之前有沒有注冊過mapper接口,沒有則會報:沒用綁定接口錯誤。

再看看上篇文章中介紹的mapperRegistery里面的東西:存放的是mapper接口,key為:接口,value為:MapperProxyFactory

Mybatis中Mapper與接口綁定原理的源碼分析

這里我們mapper接口注冊過,會進(jìn)入else分支的這段代碼:使用mapperProxyFactory創(chuàng)建代理類:

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

對比:mybatis的jdk動態(tài)代理和我們自己實(shí)現(xiàn)的jdk動態(tài)代理:

public class MapperProxy implements InvocationHandler, Serializable {//mybatis的實(shí)現(xiàn)
public class JdkMapperProxy implements InvocationHandler {//我們的實(shí)現(xiàn)
protected T newInstance(MapperProxy mapperProxy) {//mybatis的實(shí)現(xiàn)
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
OrderService orderService = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader()//我們的實(shí)現(xiàn)
        , OrderServiceImpl.class.getInterfaces(), new JdkMapperProxy(new OrderServiceImpl()));

最后返回mapper信息如下:mapper為:我們通過:mapperProxyFactory創(chuàng)建的代理類MapperProxy

Mybatis中Mapper與接口綁定原理的源碼分析

所以當(dāng)我們調(diào)用mapper的getUser()方法時候,就會執(zhí)行MapperProxy代理類的invoke()方法

UserEntity user = mapper.getUser(2);
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {    //判斷mapper接口有沒有實(shí)現(xiàn)類,顯然我們mapper沒用實(shí)現(xiàn)類
        try {
            return method.invoke(this, args);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    } else {    //會執(zhí)行這個分支
        MapperMethod mapperMethod = this.cachedMapperMethod(method);    //緩存中獲取method
        return mapperMethod.execute(this.sqlSession, args);    //執(zhí)行sql語句
    }
}

思考問題:Mybatis里面,mapper接口中有多個方法,每次調(diào)用會走同一個invoke()方法嗎?

答案:不會的,因?yàn)槟愕拿總€MapperRegistry里面的class為mapper接口,都有獨(dú)立的MapperProxyFactory,因?yàn)镸apperRegistry中key存放的是mapper接口,value為MapperProxyFactory。

我們使用MapperProxyFactory創(chuàng)建MapperProxy去創(chuàng)建的代理,所以每次調(diào)用getMapper()方法取到同一個mapper則會走同一個invoke()方法,反過來每次調(diào)用mapper時候,就會走不同invoke()方法。

Mybatis中Mapper與接口綁定原理的源碼分析

一般我們把Mapper接口定義為全局,則會走同一個invoke()方法,除非設(shè)=設(shè)置為多例,就每次都會new 不同,走不同invoke()方法。

Mybatis是基于多個不同的mapper接口生產(chǎn)的代理類,不同的mapper接口走不同的invoke方法,如果是相同的mapper接口,不同的方法,肯定是走同一個invoke方法。

那么就有問題了,多個不同mapper接口會產(chǎn)生多個代理類( new MapperProxy()),占太多的內(nèi)存,后面會詳解。

MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);

上面我們把mapper接口看完了,執(zhí)行 mapper.getUser(2) 會走invoke(),下面看invoke()方法

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 {
        //進(jìn)入這里
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}
private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);    //去緩存中查看是否有method,我們這里是沒用的
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());    //會走到這里
            this.methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
}
public MapperMethod(Class mapperInterface, Method method, Configuration config) {
    this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
    this.method = new MapperMethod.MethodSignature(config, method);
}

先看下這塊

this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
public enum SqlCommandType {
    UNKNOWN,
    INSERT,
    UPDATE,
    DELETE,
    SELECT,
    FLUSH;

SqlCommandType 是和sql語句相關(guān)的

public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
    String statementName = mapperInterface.getName() + "." + method.getName();
    MappedStatement ms = null;
    if (configuration.hasStatement(statementName)) {//進(jìn)入這里
 
        ms = configuration.getMappedStatement(statementName);
    } else if (!mapperInterface.equals(method.getDeclaringClass())) {
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
            ms = configuration.getMappedStatement(parentStatementName);
        }
    }
    if (ms == null) {
        if (method.getAnnotation(Flush.class) == null) {
            throw new BindingException("Invalid bound statement (not found): " + statementName);
        }
        this.name = null;
        this.type = SqlCommandType.FLUSH;
    } else {    //ms不為null,則執(zhí)行到這里
        this.name = ms.getId();
        this.type = ms.getSqlCommandType();
        if (this.type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + this.name);
        }
    }
}
configuration.hasStatement(statementName)
public boolean hasStatement(String statementName) {
    return this.hasStatement(statementName, true);
}

Mybatis中Mapper與接口綁定原理的源碼分析

Mybatis中Mapper與接口綁定原理的源碼分析

getId()為namespace+id

Mybatis中Mapper與接口綁定原理的源碼分析

將mapper.xml里面配置的sql語句和對應(yīng)的mapper接口方法進(jìn)行關(guān)聯(lián)并放入map緩存中,后期直接走緩存了。最后執(zhí)行execute()方法

Mybatis中Mapper與接口綁定原理的源碼分析

public Object execute(SqlSession sqlSession, Object[] args) {
    Object param;
    Object result;
    if (SqlCommandType.INSERT == this.command.getType()) {
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
    } else if (SqlCommandType.UPDATE == this.command.getType()) {
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
    } else if (SqlCommandType.DELETE == this.command.getType()) {
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
    } else if (SqlCommandType.SELECT == this.command.getType()) {    //select類型走這里
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {    //判斷方法是否沒用返回結(jié)果的,不是
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {    //判斷返回結(jié)果是不是返回多個結(jié)果集,不是
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {    //是否返回map集合?不是
            result = this.executeForMap(sqlSession, args);
        } else {                //所以走這里
            param = this.method.convertArgsToSqlCommandParam(args);    //轉(zhuǎn)換參數(shù)
            result = sqlSession.selectOne(this.command.getName(), param); //重點(diǎn)在這:selectOne()
        }
    } else {
        if (SqlCommandType.FLUSH != this.command.getType()) {
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        result = sqlSession.flushStatements();
    }

    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;
    }
}
public  T selectOne(String statement, Object parameter) {
    List 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;
    }
}

通過源碼我們可以改成下面這樣:selectOne(),后面我們針對selectOne()進(jìn)行源碼分析

//UserEntity user = mapper.getUser(2);
sqlSession.selectOne("com.mayikt.mapper.UserMapper.getUser",2);

總結(jié):

MybatisMapper接口綁定原理分析流程

1、mapper.xml中的配置文件里的每條sql語句,最終都是封裝成一個MappedStatement對象,再通過一個HashMap集合保存起來。

2、執(zhí)行g(shù)etMapper()方法,判斷是否注冊過mapper接口,注冊了就會使用mapperProxyFactory去生成代理類MapperProxy

3、執(zhí)行目標(biāo)方法時,會調(diào)用MapperProxy代理類的invoke()方法

4、將mapper.xml里面配置的sql語句和對應(yīng)的mapper接口方法進(jìn)行關(guān)聯(lián)并放入map緩存中,后期直接走緩存了。最后執(zhí)行execute()方法

5、執(zhí)行execute()方法最終調(diào)用selectOne()方法,執(zhí)行結(jié)果。

看完上述內(nèi)容,你們對Mybatis中Mapper與接口綁定原理的源碼分析有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。


本文標(biāo)題:Mybatis中Mapper與接口綁定原理的源碼分析
網(wǎng)址分享:http://weahome.cn/article/ijiiij.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部