Mybatis Plus源码解析系列篇之Mapper的奇妙之旅
一、MybatisPlus初体验
MybatisPlus是一个基于mybatis的开源orm框架,其内置的Mapper、Service让开发者仅需简单的配置,就能获得强大的CRUD能力;其强大的条件构造器,可以满足各类需求。所以越来越多的开发者使用MybatisPlus来替代基础的Mybatis。
和SpringBoot的集成
MybatisPlus可以无缝集成在SpringBoot中,常用方式如下:
1、引入MybatisPlus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
2、定义一个数据库实体
package cn.javayuli.demo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 实体类
* @author GuiLin Han
* @since 1.0.0
*/
@Data
@TableName("t_java_coder")
public class JavaCoder {
private String name;
private Integer age;
}
3、定义一个Mapper接口
package cn.javayuli.demo.mapper;
import cn.javayuli.demo.entity.JavaCoder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author GuiLin Han
* @since 1.0.0
*/
public interface JavaCoderMapper extends BaseMapper<JavaCoder> {
}
4、定义一个Service和实现类
package cn.javayuli.demo.service;
import cn.javayuli.demo.entity.JavaCoder;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author GuiLin Han
* @since 1.0.0
*/
public interface JavaCoderService extends IService<JavaCoder> {
}
package cn.javayuli.demo.service.impl;
import cn.javayuli.demo.entity.JavaCoder;
import cn.javayuli.demo.mapper.JavaCoderMapper;
import cn.javayuli.demo.service.JavaCoderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* @author GuiLin Han
* @since 1.0.0
*/
@Service
public class JavaCoderServiceImpl extends ServiceImpl<JavaCoderMapper, JavaCoder> implements JavaCoderService {
}
5、提供一个Controller
package cn.javayuli.demo.controller;
import cn.javayuli.demo.entity.JavaCoder;
import cn.javayuli.demo.service.JavaCoderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author GuiLin Han
* @since 1.0.0
*/
@RestController
public class JavaCoderController {
@Resource
private JavaCoderService javaCoderService;
@GetMapping("/java-coder/all")
public List<JavaCoder> getList() {
return javaCoderService.list();
}
}
5、在SpringBoot启动类上使用Mapper扫描注解
package cn.javayuli.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan(basePackages = "cn.javayuli.demo.mapper")
@SpringBootApplication
public class MybatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisDemoApplication.class, args);
}
}
那MybatisPlus是在哪里和SpringBoot进行集成的呢?
二、MybatisPlus集成原理
可以看到,启动类上加入了一个@MapperScan的注解,这个注解是Mybatis的,所以猜测这个注解大概率起到了关键作用,那我们就一起来看看:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
.....
}
这里有个@Import
注解,引入的MapperScannerRegistrar
是ImportBeanDefinitionRegistrar
的子类,通过Spring中bean注册机制可以知道,@Import引入的ImportBeanDefinitionRegistrar子类,会在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
中通过ImportBeanDefinitionRegistrar的addImportBeanDefinitionRegistrar
方法,直接注册BeanDefinition
到registry
中,从而可以生成bean。
MapperScannerRegistrar
撸下源码:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
.......
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取MapperScan注解
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// 创建一个MapperScannerConfigurer的BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
......
// 向registry中注册BeanDefinition
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
......
}
以上就是为了构造一个MapperScannerConfigurer
对象的BeanDefinition,它的属性大部分都是从@MapperScan
这个注解中取出来的,它的作用就是收集各种参数,方便后续扫描。
MapperScannerConfigurer
通过MapperScannerConfigurer的继承关系可以得出以下几个信息:
- 实现了
BeanDefinitionRegistryPostProcessor
接口,Spring会主动调用其postProcessBeanDefinitionRegistry
和postProcessBeanFactory
方法。 - 实现了
InitializingBean
接口,所以在初始化bean时,Spring会主动调用其afterPropertiesSet
方法。
其中就postProcessBeanDefinitionRegistry
起到了关键作用,来具体看看:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
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));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
这里就是为了使用ClassPathMapperScanner
的扫描功能,将Mybatis的mapper扫描出来,让Spring进行管理。
ClassPathMapperScanner
@Override
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 {
// 处理扫描出来的beanDefinition
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
super.doScan
调用的是父类ClassPathBeanDefinitionScanner
的scan方法,大致就是通过basePackages路径,扫描本地文件中的.class
文件,得到类元数据,再根据excludeFilter、includeFilter进行过滤后,最后组装成BeanDefinition。
在processBeanDefinitions中,最关键的步骤是:
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);
这个mapperFactoryBeanClass是ClassPathMapperScanner类中定义的一个常量,是一个FactoryBean
:
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
也就是说,Context在实例化我们的Mapper的时候,解析到的需要实例化的类其实是MapperFactoryBean,最终初始化后得到的单例bean其实是MapperFactoryBean.getObject()的对象。
MapperFactoryBean
既然知道了我们自己写的Mapper的实例是由MapperFactoryBean产生的,那我们来究其原因,它是怎么一个流程。
先来看下类图:
顶层就两个接口,一个是FactoryBean
,赋予其生产bean的能力,另一个是InitializingBean
,肯定也是想利用afterPropertiesSet
方法来做一些操作。那我们来分别对这两个接口进行分析。
1、InitializingBean
public abstract class DaoSupport implements InitializingBean {
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
protected abstract void checkDaoConfig() throws IllegalArgumentException;
protected void initDao() throws Exception {
}
}
可以看到,就是定义了两个步骤,一个是检查dao(数据库操作对象,亦称mapper)的配置,另一个是初始化dao,具体操作由子类实现。
那继续往下走,看看SqlSessionDaoSupport
:
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
@SuppressWarnings("WeakerAccess")
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
public final SqlSessionFactory getSqlSessionFactory() {
return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
public SqlSessionTemplate getSqlSessionTemplate() {
return this.sqlSessionTemplate;
}
@Override
protected void checkDaoConfig() {
notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
可以看到,其中定义了SqlSessionTemplate
这个变量,就说明SqlSessionDaoSupport
的子类可以拿到SqlSessionTemplate
进行一些操作,SqlSessionTemplate是对SqlSession操作定义的模板方法,可以执行数据库增删改查。
回到主流程,MapperFactoryBean重写了checkDaoConfig方法:
@Override
protected void checkDaoConfig() {
// 调用父类的checkDaoConfig
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
// 如果需要添加到Configuration,且Configuration中还没有当前mapper
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 添加当前mapper到Configuration中
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
在MybatisPlus中,此处configuration是MybatisConfiguration
,最终调用的是MybatisMapperRegistry
的addMapper方法:
@Override
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
return;
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
可以看到,在MybatisMapperRegistry中,会为每一个mapper分配在一个MybatisMapperProxyFactory
。
InitializingBean这个接口这条路就分析完了,接下来对FactoryBean这边进行分析。
2、FactoryBean
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()
返回的是SqlSessionTemplate
,
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
getConfiguration()
返回的是MybatisConfiguration
,
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
MybatisMapperRegistry中:
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看到,mapper实例是从MybatisMapperProxyFactory创建来的。
MybatisMapperProxyFactory
public class MybatisMapperProxyFactory<T> {
@Getter
private final Class<T> mapperInterface;
@Getter
private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MybatisMapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@SuppressWarnings("unchecked")
protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
此factory中,是创建了一个jdk动态代理的对象,而jdk动态代理的灵魂就是InvocationHandler
,MybatisMapperProxy
就是InvocationHandler的子类,所以,搞清楚MybatisMapperProxy的动作,就知道了mapper接口能具体执行的重要步骤。
MybatisMapperProxy
@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 {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
这里也没啥大的逻辑,就是将mapper的具体调用交给这个sqlSession去执行,而这个sqlSession,其实就是上面一直传递下来的SqlSessionTemplate
对象。
三、总结
Mybatis通过@MapperScan指定mapper包路径,并据此扫描BeanDefinition,并设置其class为MapperFactoryBean。在实例化时,得到的是factoryBean生成的一个jdk动态代理对象。在使用mapper时,实际是使用SqlSessionTemplate的能力。