Mybatis之執行SQL過程

概述

MyBatis執行Sql的一個過程中是怎麼樣的呢,涉及到了那些類呢,讓咱們不妨來一一過一遍。MyBatis的用法能夠採用純MyBatis、或者使用Spring集成的用法。java

純MyBatis

原生MyBatis對外暴露的接口層是SqlSession,全部的Sql操做都是靠它來引導完成的,咱們先看下SqlSession是怎麼建立出來的,看下面這段僞代碼:sql

// mybatis 配置文件
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
// 建立SqlSessionFactory,SqlSession的工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();

一、經過建立SqlSessionFactoryBuilder來生成SqlSessionFactory;mybatis

二、經過SqlSessionFactory工廠建立SqlSession;app

執行Sql

從開發者的角度來看下怎麼使用MyBatis來執行一次Sql:ide

  1. 獲取某個Mapper接口的實現示例
  2. 調用這個Mapper接口的方法

就只要兩步和普通方法的調用同樣,MyBatis將Sql的執行隱藏了起來。ui

BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
Book book = bookMapper.findByName("Learn Java");

咱們看下底層MyBatis是怎麼實現的吧:this

一、獲取的是Mapper接口的代理實現,基於JDK動態代理spa

// SqlSession

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

// Configuration

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

// MapperRegistry

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
	if (mapperProxyFactory == null) {
	  throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
	}
	try {
      // 每次get都從新建立
	  return mapperProxyFactory.newInstance(sqlSession);
	} catch (Exception e) {
	  throw new BindingException("Error getting mapper instance. Cause: " + e, e);
	}
}

// MapperProxyFactory 使用JDK動態代理,生成代理對象

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<>(sqlSession, mapperInterface, methodCache);
	return newInstance(mapperProxy);
}
  • Mapper接口和MapperProxyFactory是一一映射的關係,維護在MapperRegistry對象中
  • MapperRegistry對象在Configuration對象的屬性
  • Mapper接口和MapperProxyFactory的關係,在addMapper()方法中添加
public <T> void addMapper(Class<T> type) {
	if (type.isInterface()) {
	  if (hasMapper(type)) {
	    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
	  }
	  boolean loadCompleted = false;
	  try {
	    knownMappers.put(type, new MapperProxyFactory<>(type));
	    // It's important that the type is added before the parser is run
	    // otherwise the binding may automatically be attempted by the
	    // mapper parser. If the type is already known, it won't try.
	    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
	    parser.parse();
	    loadCompleted = true;
	  } finally {
	    if (!loadCompleted) {
	      knownMappers.remove(type);
	    }
	  }
	}
}
  • MapperProxyFactory的做用是生成Mapper接口的代理實現

二、MapperProxy的invoker方法.net

  • MapperProxy不是代理對象,而是實現了InvocationHandler的處理器
@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) {
	return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
  • 關鍵點是MapperMethod對象的execute,MapperMethod和Method是一一映射的關係代理

三、MapperMethod的execute方法,來路由,調用SqlSession的相關方法

public Object execute(SqlSession sqlSession, Object[] args) {
	Object result;
	switch (command.getType()) {
	  case INSERT: {
	  	...
        sqlSession.insert(command.getName(), param)
	    break;
	  }
	  case UPDATE: {
	    ...
        sqlSession.update(command.getName(), param)
	    break;
	  }
	  case DELETE: {
	    ...
        sqlSession.delete(command.getName(), param)
	    break;
	  }
	  case SELECT:
	    ...
        sqlSession.selectXXX(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;
}

四、SqlSession的增刪改查方法

  • SqlSession的這些方法,咱們能夠直接拿來使用的
  • MyBatis對Mapper接口的處理,兜兜轉轉又回到了SqlSession的這些方法上

PS:Mapper接口的方法都會造成一個ID,來和MapperStatement一一映射

String statementId = mapperInterface.getName() + "." + methodName;

集成Spring

@Autowired
private BookMapper bookMapper;

@Test
public void test(){
    Assert.notNull(bookMapper);

    System.out.println(bookMapper.findByName("Learn Java"));
}

執行Sql

從開發者的角度來看下怎麼使用MyBatis來執行一次Sql:

  1. 獲取某個Mapper接口的實現示例,是Spring的bean
  2. 調用這個Mapper接口的方法

這裏和原生MyBatis的使用方式的區別是,這裏的Mapper接口的實現是Spring實現的,是Spring的bean,其餘的用法不變

讓咱們看Spring是如何實現的?

MyBatis之Spring集成

總結

原生MyBatis和Spring集成MyBatis,在執行Sql的過程當中,惟一的區別是Mapper接口實現的獲取方式不一樣。

原生的須要本身從SqlSession中獲取Mapper接口,而且須要管理這個Mapper接口(爲了不每次都重試獲取生成)。Spring集成的方式,由Spring的IoC容器負責管理Mapper接口,在項目啓動的時候,Spring已經將全部的Mapper接口的代理實現都註冊到了容器裏,而且都是單例的,使用到了MapperFactoryBean。

共同點是,獲取的都是Mapper接口的代理實現,都用到了JDK動態代理。

相關文章
相關標籤/搜索