Mybatis之执行SQL过程

概述

MyBatis执行Sql的一个过程当中是怎么样的呢,涉及到了那些类呢,让我们不妨来一一过一遍。MyBatis的用法可以采用纯MyBatis、或者使用Spring集成的用法。

纯MyBatis

原生MyBatis对外暴露的接口层是SqlSession,所有的Sql操作都是靠它来引导完成的,我们先看下SqlSession是怎么创建出来的,看下面这段伪代码:

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

1、通过创建SqlSessionFactoryBuilder来生成SqlSessionFactory;

2、通过SqlSessionFactory工厂创建SqlSession;

执行Sql

从开发者的角度来看下怎么使用MyBatis来执行一次Sql:

  1. 获取某个Mapper接口的实现示例
  2. 调用这个Mapper接口的方法

就只要两步和普通方法的调用一样,MyBatis将Sql的执行隐藏了起来。

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

我们看下底层MyBatis是怎么实现的吧:

1、获取的是Mapper接口的代理实现,基于JDK动态代理

// 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接口的代理实现

2、MapperProxy的invoker方法

  • 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是一一映射的关系

3、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;
}

4、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动态代理。

转载于:https://my.oschina.net/cregu/blog/3030357

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值