概述
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:
- 获取某个Mapper接口的实现示例
- 调用这个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:
- 获取某个Mapper接口的实现示例,是Spring的bean
- 调用这个Mapper接口的方法
这里和原生MyBatis的使用方式的区别是,这里的Mapper接口的实现是Spring实现的,是Spring的bean,其他的用法不变。
让我们看Spring是如何实现的?
总结
原生MyBatis和Spring集成MyBatis,在执行Sql的过程中,唯一的区别是Mapper接口实现的获取方式不同。
原生的需要自己从SqlSession中获取Mapper接口,并且需要管理这个Mapper接口(为了避免每次都重试获取生成)。Spring集成的方式,由Spring的IoC容器负责管理Mapper接口,在项目启动的时候,Spring已经将所有的Mapper接口的代理实现都注册到了容器里,并且都是单例的,使用到了MapperFactoryBean。
共同点是,获取的都是Mapper接口的代理实现,都用到了JDK动态代理。