【Mybatis學習】查詢映射過程解析

1.功能架構

Mybatis的功能架構分爲三層:

(1)API接口層:提供給外部使用的接口API,比如dao層接口。

(2)數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次數據庫操作。

(3)基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理、日誌,這些都是共用的東西,將他們抽取出來作爲最基礎的組件。爲上層的數據處理層提供最基礎的支撐。

2.核心原理:JAVA動態代理

       代理模式是常用的Java設計模式,它的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。分爲動態代理和靜態代理。

       動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。

實現方式:

  • JDK動態代理實現

           效率相對低,被代理類需實現對應的接口

  • cglib動態代理實現

          效率相對高,生成目標類的子類

public class UserProxy<T> implements InvocationHandler {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
 
    public UserProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }
 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + ":" + Arrays.toString(args));
        return null;
    }
}
  
public interface UserMapper {
    UserInfo getUserById(long id);
}
  
public class ProxyTest {
    public static void main(String[] args) {
        UserProxy userProxy = new UserProxy(null, null);
        UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),
                new Class[]{UserMapper.class},
                userProxy);
        System.out.println(userMapper.getUserById(1l));
    }
}
簡單查詢過程:

public class OrderInfoTest {
    static Logger log = LogManager.getLogger(OrderInfoTest.class);
    public static void main(String[] args) throws IOException {
        // 加載配置文件,並獲取session
        String resource = "mybatis-config.xml";
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
        SqlSession session = sessionFactory.openSession();
        // 獲取可操作的接口
        OrderInfoMapper orderInfoMapper = session.getMapper(OrderInfoMapper.class); // 生成動態代理類
        //  執行查詢
        // OrderInfo orderInfo = session.selectOne("mybatis.study.customer.mapper.OrderInfoMapper.getOrderInfoById", 1L);
        OrderInfo orderInfo = orderInfoMapper.getOrderInfoById(1l);
        System.out.println(orderInfo.getMoney());
    }
}

通過調用DAO接口的方法,與直接使用statementId標識的方式,結果是一致的,中間肯定是做了映射關係。

這就說明了爲什麼mapper文件中的namespace必須要與dao層接口的全限定名一致。下面看下映射的過程。

3.mapper(dao)到session使用statementId查詢的映射過程

3.1 sqlSession創建過程主要類的說明

SqlSessionFactoryBuilder:用於創建SqlSessionFactory的實例,build方法入參配置文件的數據流
SqlSessionFactory是創建SqlSession實例的工廠接口,實現類有兩個

默認的實現是DefaultSqlSessionFactory,調用openSession獲取session對象,進行操作

/**
 *
 * 從配置文件獲取環境、數據源、事務類型來創建 sqlSession
 *
 * @param execType 執行器類型
 * @param level 事務級別
 * @param autoCommit 是否自動提交
 * @return session
 */
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();  // 獲取配置的環境
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);  // 環境的事務工廠, 默認事務管理ManagedTransactionFactory
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);  // 新建事務對象
        final Executor executor = configuration.newExecutor(tx, execType);    // 建立執行器
        return new DefaultSqlSession(configuration, executor, autoCommit);  // 返回默認的sqlSession
    } catch (Exception e) {
        closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

而SqlSessionManager,實現了SqlSessionFactory和SqlSession接口,直接具有session的功能,內部封裝 DefaultSqlSessionFactory,是DefaultSqlSessionFactory的加強版。

總之,通過上述的過程得到可操作的session,其中最重要的就是Configuration的構建,下面說明下Configuration的解析過程

3.2 配置文件的解析

  從上面的代碼可以看到,配置文件的解析是通過XMLConfigBuilder實現的。

public class XMLConfigBuilder extends BaseBuilder {
 
    private boolean parsed;
    private final XPathParser parser;
    private String environment;
    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
 
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
 
    private void parseConfiguration(XNode root) {
        try {
            propertiesElement(root.evalNode("properties"));  // 解析屬性
            Properties settings = settingsAsProperties(root.evalNode("settings"));   // 解析設置項
            loadCustomVfs(settings);
            typeAliasesElement(root.evalNode("typeAliases"));   // 解析別名
            pluginElement(root.evalNode("plugins"));      // 解析插件
            objectFactoryElement(root.evalNode("objectFactory"));    // 解析對象工廠
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));   // objectWrapper工廠,可以對結果進行一些特殊的處理
            reflectorFactoryElement(root.evalNode("reflectorFactory"));  // 反射工廠
            settingsElement(settings);
            environmentsElement(root.evalNode("environments"));   // 解析環境
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));   // 解析數據庫提供廠商
            typeHandlerElement(root.evalNode("typeHandlers"));    // 解析配置的類型處理器
            mapperElement(root.evalNode("mappers"));    // 解析sql映射文件
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
}

通過mybatis配置文件和mapper文件的解析,

1.將mapper接口信息,註冊到了mapperRegistry,  // 以class<T> 和代理工廠註冊到mapper庫


public class MapperProxyFactory<T> {
 
  private final Class<T> mapperInterface; 
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); 
 
  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
 
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }
 
  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }
 
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
 
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

這樣,針對session.getMapper(OrderInfoMapper.class)生成動態代理類,就對應上了

2.Statement信息註冊到了mappedStatements

public final class MappedStatement {
 
    private String resource;
    private Configuration configuration;
    private String id;
    private Integer fetchSize;
    private Integer timeout;
    private StatementType statementType;
    private ResultSetType resultSetType;
    private SqlSource sqlSource;
    private Cache cache;
    private ParameterMap parameterMap;
    private List<ResultMap> resultMaps;
    private boolean flushCacheRequired;
    private boolean useCache;
    private boolean resultOrdered;
    private SqlCommandType sqlCommandType;
    private KeyGenerator keyGenerator;
    private String[] keyProperties;
    private String[] keyColumns;
    private boolean hasNestedResultMaps;
    private String databaseId;
    private Log statementLog;
    private LanguageDriver lang;
    private String[] resultSets;
  
    public BoundSql getBoundSql(Object parameterObject) {
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            String rmId = pm.getResultMapId();
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }
        return boundSql;
    }
}

3.3 調用映射

session獲取mapper,調用了Configuration中從mapper庫中查詢到MapperProxyFactory對象,調用方法,執行MapperProxy中invoke方法

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;
  }
 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
 
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

執行mapperMethod的execute方法

public class MapperMethod {
 
  private final SqlCommand command;
  private final MethodSignature method;
 
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
 
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
}
public static class SqlCommand {
 
  private final String name;
  private final SqlCommandType type;
 
  public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
        configuration);
    if (ms == null) {
      if (method.getAnnotation(Flush.class) != null) {
        name = null;
        type = SqlCommandType.FLUSH;
      } else {
        throw new BindingException("Invalid bound statement (not found): "
            + mapperInterface.getName() + "." + methodName);
      }
    } else {
      name = ms.getId();
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
    }
  }
}

4.總結

    主要是梳理了下從DAO接口到Mybatis查詢的過程,解釋了Mybatis採用的動態代理模式,將DAO接口的方法映射到session使用statementId查詢的過程,Mybatis源碼中涉及了很多工廠類和建造類,可以借鑑。







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