mybatis核心組件之MapperMethod


註釋: 跟蹤mapper執行接口方法到數據庫執行sql語句的源碼過程

// mybatis-spring
UserMapper mapper = context.getBean(UserMapper.class);
System.out.println(mapper.selectAny());

可以看到mapperMapperProxy產生的代理類,那麼MapperProxy中可定有invok方法對目標方法進行了增強處理
在這裏插入圖片描述

MapperProxy

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 如果方法是Object類的方法,則直接反射執行
    if (Object.class.equals(method.getDeclaringClass())) {
        try {
            return method.invoke(this, args);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    } else {
       // 獲取MapperMethod
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        // 執行sql語句
        return mapperMethod.execute(this.sqlSession, args);
    }
}
  1. 先判斷執行的方法是不是Object類的方法,比如tostringhashcode等方法,是的話則直接反射執行這些方法
  2. 如果不是,從緩存中獲取MapperMethod,如果爲空則創建並加入緩存,然後執行sql語句
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;
}

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);
}

SqlCommand一個內部類 封裝了SQL標籤的類型 insert update delete select

MethodSignature一個內部類 封裝了方法參數信息 返回類型信息等

execute執行

判斷sql的執行類型,執行相應的方法
在這裏插入圖片描述

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()) { // 返回類型爲void
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) { // 返回類型爲集合或數組
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {// 由@MapKey控制返回
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {// 返回類型爲Cursor<T>,採用遊標
            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);

convertArgsToSqlCommandParam解析入參

ParamNameResolver.getNamedParams

public Object convertArgsToSqlCommandParam(Object[] args) {
    return this.paramNameResolver.getNamedParams(args);
}

解析@Param註解

namesSortedMap,在構造函數中賦值的,判斷入參有沒有@Param註解。
key是入參的順序,從0開始;value@Param中的值
如果沒有@Param註解,則value值爲arg0arg1

將入參名與值匹配

public Object getNamedParams(Object[] args) {
    int paramCount = this.names.size();
    if (args != null && paramCount != 0) {
        // 只有一個入參時,返回入參值
        if (!this.hasParamAnnotation && paramCount == 1) {
            return args[((Integer)this.names.firstKey()).intValue()];
        } else {
            // 多個入參時,返回一個Map
            Map<String, Object> param = new ParamMap();
            int i = 0;
            for(Iterator i$ = this.names.entrySet().iterator(); i$.hasNext(); ++i) {
                Entry<Integer, String> entry = (Entry)i$.next();
                param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
                String genericParamName = "param" + String.valueOf(i + 1);
                if (!this.names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
                }
            }
            return param;
        }
    } else {
        return null;
    }
}

示例1: 多個入參,沒有加@Param註解

@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{type}")
public Integer selectAny(int num,String type);

這樣執行sql會報錯,找不到num,可以改爲#{arg1}#{param1}
在這裏插入圖片描述
示例2:多個入參,加@Param註解

@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{type}")
public Integer selectAny(@Param("num")int num,@Param("type")String type);

在這裏插入圖片描述
示例3:多個入參,部分加@Param註解,部分不加

@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{arg1}")
public Integer selectAny(@Param("num")int num,String type);

在這裏插入圖片描述
提示:只有一個入參時不存在這些問題,因爲一個入參直接返回的是它的值,是一個String不是Map

執行SqlSessionTemplate中的方法

這裏的sqlSession就是SqlSessionTemplatemybatis與spring的整合之SqlSessionTemplate

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

在這裏插入圖片描述

public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.selectOne(statement, parameter);
}

sqlSessionProxy是動態代理生成的,每一次執行方法時都會重新去 new 一個DefaultSqlSession,可以看下invoke方法部分代碼

// 獲取session,這裏有個事物的判斷
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
    // 真正執行sql語句的地方
    Object result = method.invoke(sqlSession, args);
    if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        sqlSession.commit(true);
    }

    unwrapped = result;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章