【mybatis】SqlSession运行过程——映射器Mapper的动态代理

1、什么是SqlSession?

  • SqlSession是mybaits的核心接口,在mybatis中有两个实现类,DefaultSqlSession和SqlSessionManager。DefaultSqlSession是单线程使用的,而SqlSessionManager在多线程环境下使用。

  • SqlSession的作用类似于一个JDBC中的Connection对象,代表着一个连接资源的启用。具体而言,它的作用有3个:获取Mapper接口,发送SQL给数据库,控制数据库事务。


2、在mybatis中如何发送SQL语句?

  • 在mybaits中可以通过两种方式发送SQL语句,一种是直接使用SqlSession发送SQL,一种是通过SqlSession获取Mapper接口来发送SQL。mybaits官方建议直接使用Mapper来发送SQL。

3、mybaits中如何获取SqlSession?

  • 通过SqlSessionFactory获取SqlSession
SqlSession sqlSession = SqlSessionFactory.openSession();

4、为什么只有Mapper接口就能够执行SQL语句?

接口是不能直接运行的,但是mybatis运用了动态代理技术使得接口能运行起来,mybatis会为这个接口生成一个代理对象,代理对象会去处理相关的逻辑。


5、mybaits是如何实现Mapper的动态代理?

我们知道,在spring中使用Mapper时,我们在需要使用Mapper时,只需要使用@Autowrired、@Resource和构造器等方式就可以注入Mapper,这里我们先不讨论spring中注入Mapper的原理,而是通过以下方式获取Mapper。

ItemMapper itemMapper = sqlSession.getMapper(ItemMapper.class);

从sqlSession的getMapper()方法开始,我们将看到mybatis是如何实现Mapper的动态代理。


5.1、跟踪sqlSession的getMapper()方法,最终可以看到是运用映射器的注册器MapperRegistry来获取对应的接口对象。
//上面我们已经说过,SqlSession有两个实现类,默认情况下是使用DefaultSqlSession,即单线程使用
public class DefaultSqlSession implements SqlSession {
	//...
	@Override
	public <T> T getMapper(Class<T> type) {
	return configuration.getMapper(type, this);
	}
	//...
}

DefaultSqlSession中的getMapper()用到的是Configuration中的getMapper()。继续跟踪代码,如下:

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

可以看到,最终使用的是MapperRegistry中的getMapper()。继续跟踪代码,如下:

public class MapperRegistry {
	/**
	* 注册Mapper,存储Mapper的容器
	*/
	private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
	//...
	@SuppressWarnings("unchecked")
	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
		final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
		//判读是否注册了该Mapper,没有则抛出异常信息
		if (mapperProxyFactory == null) {
		throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
		}
		try {
			//已经注册了该Mapper,启用MapperProxyFactory工厂生成一个Mapper代理实例。
			return mapperProxyFactory.newInstance(sqlSession);
		} catch (Exception e) {
			throw new BindingException("Error getting mapper instance. Cause: " + e, e);
		}
	}
	
	public <T> boolean hasMapper(Class<T> type){
	//...
	}
	
	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);
				}
			}
		}
	}
}

5.2、在程序正常情况下,继续跟踪代码,进入MapperProxyFactory

跟踪MapperRegistry中getMapper()方法中的mapperProxyFactory.newInstance(sqlSession)代码,如下:

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 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<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

在MapperProxyFactory中有两个newInstance()方法,分别是newInstance(SqlSession)newInstance(MapperProxy)newInstance(SqlSession)用来生成动态代理对象MapperProxy的实例,即代理方法invoke()所在的类的实例;newInstance(MapperProxy)则是用来绑定Mapper和动态代理对象。


5.3、接着继续看动态代理对象(即MapperProxy)中的invoke()方法
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
	  //判断该类是不是接口,一般为false
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
	  //判断调用的方法是不是接口中的默认方法
      } else if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
	//生成MapperMethod对象,通过cacheMapperMethod方法对其初始化
    final MapperMethod mapperMethod = cachedMapperMethod(method);
	//执行execute方法,把sqlSession和当前运行的参数传递进去
    return mapperMethod.execute(sqlSession, args);
  }

通常情况下,invoke()方法中只用到了最后两行代码,对于最后第二行代码,使用cacheMapperMethod(Method)生成MapperMethod对象,来看看cacheMapperMethod(Method)是如何生成MapperMethod对象的。

  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method,
        k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
    default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }

mappingFunction是一个函数式接口,代码如下:

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

从methondCache调用computeIfAbsent开始,先使用lambda表示式实现了mappingFunction中的apply方法,这个方法的实现只是生成并返回一个MapperMethod对象。接着,进入到computeIfAbsent方法内部,先判断methodCache中是否存在对应的MapperMethod对象,如果存在则直接返回,不存在则调用mappingFunction的apply方法生成并返回MapperMethod对象,接着将这个对象放入mapperCache中。这一步的目的主要是为了提高性能,将MapperMethod对象放入缓存,而不是每次调用都对其进行初始化。


完成对MapperMethod的初始化之后,调用MapperMethod的execute()方法,将sqlSession和当前运行的参数传递进去,跟踪代码如下:

  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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

跟踪其中的executeForMany()方法,代码如下:

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

在上述代码中,采用命令模式运行,根据上下文跳转到需要的方法中。至此,根据源码我们可以很清除,实际上最后就是通过SqlSession对象去运行对象的SQL。


【面试题】mybatis为什么只用Mapper接口便能够运行了?

因为Mapper的XML文件的命名空间namespace对应的是这个接口的全限定名,而方法就是那条SQL的id,mybatis根据全限定名和方法名,将其和代理对象绑定起来,通过动态代理技术,让这个接口运行起来,而后采用命令模式,最终使用SqlSession接口的方法使得它能够执行对应的SQL。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值