从接口如何调用到SQL
上文中,我们了解了我们的存放在XML里面的SQL是如何被解析到Mybatis的框架中了。
但是我们实际的Spring项目中使用是定义的一个接口,然后与通过这个接口中的方法来调用的实际的SQL。那在这其中Mybatis又为我们做了哪写事儿呢。
修改项目为一个Spring项目
主要的修改:
- 增加SpringBoot 的主类
- Mapper.java中增加标签@Repository
- 增加application.yaml
主入口类:
package com.example.mybatis.mybatiddemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mybatis.mybatiddemo.mapper")
public class MybatiddemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatiddemoApplication.class, args);
}
}
Mapper类:
package com.example.mybatis.mybatiddemo.mapper;
import com.example.mybatis.mybatiddemo.module.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
User getUser(@Param("id") Integer id);
User getUserName(@Param("id") Integer id);
}
application.yaml:
server:
port: 8080
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapping/*Mapper.xml
type-aliases-package: com.example.mybatis.mybatiddemo.module
#showSql
logging:
level:
com:
example:
mapper: debug
Controller类
package com.example.mybatis.mybatiddemo.controller;
import com.example.mybatis.mybatiddemo.mapper.UserMapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserMapper userMapper;
@RequestMapping("")
public Object userInfo() {
return userMapper.getUser(1);
}
}
启动这个demo,访问localhost:8080/user 可以获取到结果数据。
代码分析
在这个Demo中,在Application类中有一个标签
@MapperScan("com.example.mybatis.mybatiddemo.mapper")
以及Mapper中心增加的标签:
@Repository
MapperScan标签的处理类为 org.mybatis.spring.annotation.MapperScannerRegistrar, 这个类中生成 org.mybatis.spring.mapper.MapperScannerConfigurer 的Configuration处理类。
接着来看 org.mybatis.spring.mapper.MapperScannerConfigurer 这个类
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
private String lazyInitialization;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
这个Configuration的类实现了几个接口,最为重要的是BeanDefinitionRegistryPostProcessor。它可以 动态的注册Bean信息,方法为postProcessBeanDefinitionRegistry()。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//构建ClassPathMapperScanner用于扫描Mapper包
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
//设置Scanner的Filter
scanner.registerFilters();
//扫描包
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
而ClassPathMapperScanner继承自Spring中的ClassPathBeanDefinitionScanner,所以在上面的scan方法,实际上是调用的父类的ClassPathBeanDefinitionScanner.scan方法。在scan方法中又会回调子类的doScan 方法,也就是 ClassPathMapperScanner.doScan .
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
}
其中会调用父类的 super.doScan() 方法,获取BeanDefinitionHolder,也就是扫描到带有 @Repository 的类。
然后在 processBeanDefinitions(beanDefinitions) 中对Holder进行实例化,初始化。
Mybatis始化Mapper类
ClassPathMapperScanner.processBeanDefinitions(beanDefinitions)
截取出比较重要的:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
}
}
其中最重要的点是:
// 设置mapper接口的名称到构造参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 设置BeanDefinition的class
definition.setBeanClass(this.mapperFactoryBeanClass);
//设置属性addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
//设置属性sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
设置beanClass
设置BeanDefinition对象的BeanClass为MapperFactoryBean<?>。这意味着什么呢?以UserMapper为例,意味着当前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那么在IOC初始化的时候,实例化的对象就是MapperFactoryBean对象( 这一点是关键点 ).这说明在Spring实例化该对象的时候是去创建这个对象。设置参数 sqlSessionFactory , 这个参数会在初始化Mapper的时候使用到。
生成代理Bean
上面初始化BeanDefinition的时候说到BeanDefinition对象就是beanClass为MapperFactoryBean,所以我肯定要去看这个类。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
//初始化的使用调用这个方法
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
在实例化UserMapper的时候,就会调用到MapperFactoryBean的构造方法。
而MapperFactoryBean它继承自FactoryBean,自然也实现了接口方法getObject() , 于是spring会通过这个FactoryBean调用getObject 创建的Bean实例.
其中接口调用getSqlSession 。
看到这里又可以回到上一节中,这个SqlSession就是上节中的SqlSessionFactory。
这里的调用链就是。
MapperFactoryBean.getObject
-> SqlSessionTemplate.getMapper
-> MapperRegistry.getMapper
-> MapperProxyFactory.newInstance
-> Proxy.newProxyInstance
public class MapperProxyFactory<T> {
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
所以最终获取到的Mapper类是由Proxy生成的代理类,代理的接口是 UserMapper,而代理类为MapperProxy。
MapperProxy代理处理
接着看这个MapperProxy代理类。
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}
会先获取MapperMethodInvoker ,然后调用invoke。
而MapperMethodInvoker这个类封装了MapperMethod。在构造MapperMethod的过程可以看如下:
private MapperMethodInvoker cachedInvoker(Method method) {
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
});
}
在构造MapperMethod的参数中传入了,sqlSession.getConfiguration
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
其中构造了 SqlCommand
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//获取方法名
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
//从Config中获取MappedStatement 。这个就是第二节中,我们解析xml后保存在config中的数据了。
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
接下来就是调用invoke的时候会去执行MapperMethod的execute函数。
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
在execute方法中根据Command的类型,路由到不同的调用方法。比如下面是Select中的路由调用。
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;
接下来以SelectOne为实例
然后开始调用SqlSessionTemplate.selectOne -> DefaultSqlSession.selectOne -> DefaultSqlSession.selectList
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
这里也很熟悉。也是第二节中保存在Config中的MappedStatement。然后通过statement获取到之后用于Executor来执行。
接下来才是找到重点:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在上面的代码中,通过ms以及参数获取到了BoundSql,这个BoundSql也就是在第二节中,通过xml解析出来的sql 。
这时候,SQL也解析出来了。就可以最后调用
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
return list;
}
可以看到这个queryFromDatabase ,看着名字就知道终于找到调用数据库的了。