MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析

首先根据MyBatis Plus入门实践详解 搭建好工程。然后创建数据库表与相关的类。

表结构如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第1张图片
EmployeeMapper接口继承自BaseMapper

public interface EmployeeMapper extends BaseMapper {
}

这个BaseMapper是com.baomidou.mybatisplus.mapper.BaseMapper。这里测试的MyBatis Plus版本是:


   com.baomidou
   mybatis-plus
   2.3
		

BaseMapper定义了常用的增删改查接口,继承该接口后无需编写mapper.xml文件,即可获得通用的CRUD功能。
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第2张图片

当然你可以在这个employeeMapper里面自定义方法,方法通过注解或者mapper.xml里面insert|update|select|delete实现。

【1】通用插入数据

① insert

测试代码如下:

@Test
public void testCommonInsert() {
	//初始化Employee对象
	Employee employee  = new Employee();
	employee.setLastName("MP");
	employee.setEmail("[email protected]");
	employee.setSalary(20000.0);
	//插入到数据库   
	Integer result = employeeMapper.insert(employee);  
	System.out.println("result: " + result );
	//获取当前数据在数据库中的主键值
	Integer key = employee.getId();
	System.out.println("key:" + key );
}

测试结果如下:
在这里插入图片描述
结论:

insert方法在插入时, 会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现到SQL语句中

② insertAllColumn

测试代码如下:

@Test
public void testCommonInsert() {
	//初始化Employee对象
	Employee employee  = new Employee();
	employee.setLastName("MP");
	employee.setEmail("[email protected]");
	employee.setSalary(20000.0);
	//插入到数据库   
	Integer result = employeeMapper.insertAllColumn(employee);  
	System.out.println("result: " + result );
	//获取当前数据在数据库中的主键值
	Integer key = employee.getId();
	System.out.println("key:" + key );
}

测试结果如下:
在这里插入图片描述
结论

insertAllColumn方法在插入时, 不管属性是否非空, 属性所对应的字段都会出现到SQL语句中。


【2】通用更新数据

① updateById

测试代码如下:

@Test
public void testCommonUpdate() {
	//初始化修改对象
	Employee employee = new Employee();
	employee.setId(7);
	employee.setLastName("小泽老师");
	employee.setEmail("[email protected]");
	employee.setGender(0);
	Integer result = employeeMapper.updateById(employee);
	System.out.println("result: " + result );
}

测试结果如下:
在这里插入图片描述

结论

updateById方法在更新时, 会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现到SQL语句中

② updateAllColumnById

测试代码如下:

@Test
public void testCommonUpdate() {
	//初始化修改对象
	Employee employee = new Employee();
	employee.setId(7);
	employee.setLastName("小泽老师");
	employee.setEmail("[email protected]");
	employee.setGender(0);
	Integer result = employeeMapper.updateAllColumnById(employee);
	System.out.println("result: " + result );
}

测试结果如下:
在这里插入图片描述
结论

updateAllColumnById方法在更新时, 不管属性是否非空, 属性所对应的字段都会出现到SQL语句中。


【3】通用查询数据

① selectById

测试代码如下:

Employee employee = employeeMapper.selectById(7);
System.out.println(employee);

测试结果如下:
在这里插入图片描述

② selectOne多列查询

测试代码如下:

Employee  employee = new Employee();
employee.setId(7);
employee.setLastName("小泽老师");
employee.setGender(0);

Employee result = employeeMapper.selectOne(employee);
System.out.println("result: " +result );

测试结果如下:

在这里插入图片描述
这里需要注意的是使用selectOne时需要保证数据库中顶多查询出一条数据,否则就会报错nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2

③ selectBatchIds

测试代码如下:

List idList = new ArrayList<>();
idList.add(7);
idList.add(8);
idList.add(9);
List emps = employeeMapper.selectBatchIds(idList);
System.out.println(emps);

测试结果如下:
在这里插入图片描述

④ selectByMap

测试代码如下:

Map columnMap = new HashMap<>();
//这里注意是列名, 非对象属性名
columnMap.put("last_name", "MP");
columnMap.put("gender", 1);

List emps = employeeMapper.selectByMap(columnMap);
System.out.println(emps);

测试结果如下:
在这里插入图片描述

⑤ 分页查询selectPage

测试代码如下:

List emps = employeeMapper.selectPage(new Page<>(2, 2), null);
System.out.println(emps);

测试结果如下:
在这里插入图片描述
可以看到SQL语句上面并没有limit关键字进行分页,但是返回的数据结果确实是经过分页处理的。这说明其并非是真正的物理分页,而是借助于RowBounds实现的内存分页。如果想实现物理分页,可以考虑使用pageHelper插件。

故而上述分页实例并不推荐,推荐使用MyBatis Plus的分页插件。


【4】通用删除数据

① deleteById

测试代码如下:

Integer result = employeeMapper.deleteById(13);
System.out.println("result: " + result );

测试结果如下:
在这里插入图片描述
将会返回删除条数,没有删除返回0。

② deleteByMap

根据组合条件删除,返回删除条数,没有删除返回0.

测试代码如下:

Map columnMap = new HashMap<>();
columnMap.put("last_name", "MP");
columnMap.put("email", "[email protected]");
Integer result = employeeMapper.deleteByMap(columnMap);
System.out.println("result: " + result );

测试结果如下:
在这里插入图片描述

③ deleteBatchIds

根据ID批量删除,返回删除的记录数,没有删除返回0。

测试代码如下:

List idList = new ArrayList<>();
idList.add(13);
idList.add(14);
idList.add(15);
Integer result = employeeMapper.deleteBatchIds(idList);
System.out.println("result: " + result );

测试结果如下:

在这里插入图片描述


【5】条件构造器使用

① 条件查询

如下,分页查询tbl_employee表中,年龄在18~50之间且性别为男且姓名为Tom的所有用户。

测试代码如下:

List emps =employeeMapper.selectPage(new Page(1, 2),
					new EntityWrapper()
					.between("age", 18, 50)
					.eq("gender", 1)
					.eq("last_name", "Tom")
				);
System.out.println(emps);

这里使用new EntityWrapper传一个Wrapper实例对象进去,使用between、eq这些关键字来封装查询条件(在JPA里面也可以看到这种思想)

查询SQL如下:

SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee 
WHERE (age BETWEEN ? AND ? AND gender = ? AND last_name = ?)

还可以使用Wrapper的另外一个子类Condition来实现上述效果,condition和EntityWrapper都wrapper的子类:

List emps = employeeMapper.selectPage(
							new Page(1,2),
							Condition.create()
							.between("age", 18, 50)
							.eq("gender", "1")
							.eq("last_name", "Tom")

							);
System.out.println(emps);

查询SQL如下:

SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee 
WHERE (age BETWEEN ? AND ? AND gender = ? AND last_name = ?) 

② 组合条件模糊查询

查询tbl_employee表中, 性别为女并且名字中带有"老师" 或者 邮箱中带有"a"。

测试代码如下:

List emps = employeeMapper.selectList(
				new EntityWrapper()
				.eq("gender", 0)
				.like("last_name", "老师")
				.orNew()   // SQL: (gender = ? AND last_name LIKE ?) OR (email LIKE ?)
				.like("email", "a")
				);
System.out.println(emps);

查询SQL如下:

SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee 
WHERE (gender = ? AND last_name LIKE ?) OR (email LIKE ?) 

Parameters: 0(Integer), %老师%(String), %a%(String)

上面使用的是orNew,下面对比使用or的时候查询SQL:

List emps = employeeMapper.selectList(
	new EntityWrapper()
	.eq("gender", 0)
	.like("last_name", "老师")
	.or()    // SQL: (gender = ? AND last_name LIKE ? OR email LIKE ?)
	.like("email", "a")
	);
System.out.println(emps);

SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee 
WHERE (gender = ? AND last_name LIKE ? OR email LIKE ?)

Parameters: 0(Integer), %老师%(String), %a%(String)

可以发现OR只是作为一个查询条件,而orNew则封装了两个子查询。针对本例效果是一致的,但是在某些情况下结果会不一致。


③ 条件查询排序

查询性别为女的, 根据age进行排序(asc/desc), 简单分页。

测试代码如下:

List emps  = employeeMapper.selectList(
				new EntityWrapper()
				.eq("gender", 0)
				.orderBy("age")//默认升序 可以使用orderAsc
				//.orderDesc(Arrays.asList(new String [] {"age"}))
				.last("desc limit 1,3")
				);

查询SQL如下:

SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee 
WHERE (gender = ?) ORDER BY age desc limit 1,3

④ 更新数据

测试代码如下:

@Test
public void testEntityWrapperUpdate() {
	
	Employee employee = new Employee();
	employee.setLastName("苍老师");
	employee.setEmail("[email protected]");
	employee.setGender(0);
	
	
	employeeMapper.update(employee, 
				new EntityWrapper()
				.eq("last_name", "Tom")
				.eq("age", 44)
				);
}

更新SQL如下:

Preparing: UPDATE tbl_employee SET last_name=?, email=?, gender=? WHERE (last_name = ? AND age = ?) 
 
Parameters: 苍老师(String), [email protected](String), 0(Integer), Tom(String), 44(Integer)

Updates: 0

⑤ 删除数据

测试代码如下:

@Test
public void testEntityWrapperDelete() {
	
	employeeMapper.delete(
				new EntityWrapper()
				.eq("last_name", "Tom")
				.eq("age", 22)
			);
}

测试结果如下:
在这里插入图片描述
为什么继承了BaseMapper后,不需要写SQL,不需要mapper.xml配置文件,即可使用大多CRUD方法?


【6】MP 启动注入 SQL原理分析

以如下代码为例跟踪一下究竟发生了什么:

@Test
public void testEntityWrapperUpdate() {
	
	Employee employee = new Employee();
	employee.setLastName("苍老师");
	employee.setEmail("[email protected]");
	employee.setGender(0);


	Integer update = employeeMapper.update(employee,
			new EntityWrapper()
					.eq("last_name", "Tom")
					.eq("age", 44)
	);
	System.out.println(update);
}

① 此时的employeeMapper对象是个什么?

此时的employeeMapper是个代理对象org.apache.ibatis.binding.MapperProxy@7728643a,如下图所示其是一个代理对象。
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第3张图片
这里sqlsessionTemplate是sqlsession实现类:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第4张图片

主要属性如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第5张图片
继续跟踪其sqlSessionFactory属性,即employeeMapper.sqlSession.sqlSessionFactory:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第6张图片
sqlSessionFactory只有一个属性configuration,其是MybatisConfiguration实例对象,该实例对象有MybatisMapperRegistry注册中心实例对象、 mappedStatements(Configuration$StrictMap)。那么什么是mappedStatements?我们看一下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第7张图片
似乎找到了employeeMapper那么多默认功能的来源!项目启动的时候初始化employeeMapper对象时会自动注入这些方法!那么具体如何注入的呢?我们跟下代码看看。

② debug运行代码

如下图所示,在启动的时候根据type注入了employeeMapper,在AbstractAutowireCapableBeanFactory类中调用了afterPropertiesSet方法,然后将一系列mapper的方法注入。

那么有两个问题:

  • 1.调用了谁的afterPropertiesSet方法?afterPropertiesSet方法是InitializingBean接口的。
  • 2.MappedStatement究竟是什么?
    MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第8张图片

1.1 AbstractAutowireCapableBeanFactory.initializeBean

如下所示我们跟踪源码从这里开始,主要跟踪其invokeInitMethods方法。

	protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged(new PrivilegedAction() {
				@Override
				public Object run() {
					invokeAwareMethods(beanName, bean);
					return null;
				}
			}, getAccessControlContext());
		}
		else {
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
		//循环执行那些bean的后置处理器的postProcessBeforeInitialization方法
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
//调用初始化方法:如InitializingBean's {@code afterPropertiesSet}
//or a custom init-method
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
//循环执行那些bean的后置处理器的postProcessBeforeInitialization方法
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}
		return wrappedBean;
	}
 
  

1.2 AbstractAutowireCapableBeanFactory.invokeInitMethods

方法源码如下所示:

#这里的bean 是wrappedBean
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
			throws Throwable {

		boolean isInitializingBean = (bean instanceof InitializingBean);
		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
			}
			if (System.getSecurityManager() != null) {
				try {
					AccessController.doPrivileged(new PrivilegedExceptionAction() {
						@Override
						public Object run() throws Exception {
							((InitializingBean) bean).afterPropertiesSet();
							return null;
						}
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				((InitializingBean) bean).afterPropertiesSet();
			}
		}

		if (mbd != null) {
			String initMethodName = mbd.getInitMethodName();
			if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
					!mbd.isExternallyManagedInitMethod(initMethodName)) {
				invokeCustomInitMethod(beanName, bean, mbd);
			}
		}
	}
 
  

MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第9张图片
invokeInitMethods方法作用是什么?

在bean属性被设置后告诉bean的bean factory是什么并检测其是否实现了InitializingBean接口或者自定义了一个init method。如果是,则尝试调用其afterPropertiesSet以及invokeCustomInitMethod(调用自定义的init method方法)。

为什么要从这里开始?

这里是bean实例化过程的一步,执行栈信息如下(可以看到是spring初始化bean的过程):
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第10张图片
这里方法参数具体值如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第11张图片
具体看下此时的包装bean:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第12张图片
MapperFactoryBean类继承图如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第13张图片
可以看到其实现了InitializingBean接口,afterPropertiesSet方法会在bean属性设置完后进行一系列操作。关于该接口可以参考博文:Spring中bean的初始化和销毁几种实现方式详解

我们跟踪进afterPropertiesSet方法,这里是DaoSupport.afterPropertiesSet方法,源码如下:

 public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
 //从这里跟进去 参考1.3
        this.checkDaoConfig();
        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

1.3 MapperFactoryBean.checkDaoConfig

  protected void checkDaoConfig() {
    super.checkDaoConfig();
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    //获取到configuration --MyBatisConfiguration实例
    Configuration configuration = getSqlSession().getConfiguration();
    	//如果该configuration没有该mapperInterface,则进行addMapper操作
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
      //这个是核心,参考1.4
        configuration.addMapper(this.mapperInterface);
      } 
      //...
      }
    }
  }

configuration.addMapper(this.mapperInterface);直接调用了mybatisMapperRegistry的addMapper方法:

 public  void addMapper(Class type) {
        this.mybatisMapperRegistry.addMapper(type);
    }

1.4 MybatisMapperRegistry.addMapper

 public  void addMapper(Class type) {
 //判断是否接口
        if (type.isInterface()) {
        //判断是否已经存在
            if (this.hasMapper(type)) {
                return;
            }
            boolean loadCompleted = false;
            try {
            //type:new MapperProxyFactory(type) 放到knownMappers这个hashmap中
                this.knownMappers.put(type, new MapperProxyFactory(type));
                //获取一个解析器,进行对象方法注入
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(this.config, type);
                //重点来了 参考1.5
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

这里我们重点看下MybatisMapperAnnotationBuilder 是什么,其构造函数如下:

public MybatisMapperAnnotationBuilder(Configuration configuration, Class type) {
        super(configuration, type);
        String resource = type.getName().replace('.', '/') + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
        this.sqlAnnotationTypes.add(Select.class);
        this.sqlAnnotationTypes.add(Insert.class);
        this.sqlAnnotationTypes.add(Update.class);
        this.sqlAnnotationTypes.add(Delete.class);
        this.sqlProviderAnnotationTypes.add(SelectProvider.class);
        this.sqlProviderAnnotationTypes.add(InsertProvider.class);
        this.sqlProviderAnnotationTypes.add(UpdateProvider.class);
        this.sqlProviderAnnotationTypes.add(DeleteProvider.class);
    }

其首先调用了父类MapperAnnotationBuilder的构造函数,如下所示:

  public MapperAnnotationBuilder(Configuration configuration, Class type) {
  //资源符号路径转换
        String resource = type.getName().replace('.', '/') + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
        this.sqlAnnotationTypes.add(Select.class);
        this.sqlAnnotationTypes.add(Insert.class);
        this.sqlAnnotationTypes.add(Update.class);
        this.sqlAnnotationTypes.add(Delete.class);
        this.sqlProviderAnnotationTypes.add(SelectProvider.class);
        this.sqlProviderAnnotationTypes.add(InsertProvider.class);
        this.sqlProviderAnnotationTypes.add(UpdateProvider.class);
        this.sqlProviderAnnotationTypes.add(DeleteProvider.class);
    }

调用完父类的构造函数后,当前MybatisMapperAnnotationBuilder如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第14张图片
然后继续走MybatisMapperAnnotationBuilder的构造函数,最终如下所示:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第15张图片


1.5 MybatisMapperAnnotationBuilder.parse

public void parse() {
//拿到接口完整路径
        String resource = this.type.toString();
        if (!this.configuration.isResourceLoaded(resource)) {
        //获取mapper.xml文件 这个很有意思,参考1.6
            this.loadXmlResource();
            this.configuration.addLoadedResource(resource);
            //设置当前的Namespace--命名空间
            this.assistant.setCurrentNamespace(this.type.getName());
            this.parseCache();
            this.parseCacheRef();
            //获取接口的方法
            Method[] methods = this.type.getMethods();
            //如果是BaseMapper的子类,则会调用inspectInject方法
            if (BaseMapper.class.isAssignableFrom(this.type)) {
            //获取注入器进行注入 参考1.7
                GlobalConfigUtils.getSqlInjector(this.configuration).inspectInject(this.assistant, this.type);
            }

            Method[] arr$ = methods;
            int len$ = methods.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                Method method = arr$[i$];

                try {
                    if (!method.isBridge()) {
                        this.parseStatement(method);
                    }
                } catch (IncompleteElementException var8) {
                    this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }

        this.parsePendingMethods();
    }

这里可以看下获取的接口的方法:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第16张图片
对比下我们在employeeMapper里面看到的方法(可以猜测,将会把这些方法一个个注入进去,具体注入什么进去到哪里呢?):
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第17张图片

1.6 MybatisMapperAnnotationBuilder.loadXmlResource

   private void loadXmlResource() {
        if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
        // 这里xmlResource 是com/jane/mp/mapper/EmployeeMapper.xml
            String xmlResource = this.type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = null;
            try {
            //尝试读取文件流 具体可参考
                inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
            } catch (IOException var4) {
                ;
            }
            if (inputStream != null) {
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
                xmlParser.parse();
            }
        }
    }

1.7 AutoSqlInjector.inspectInject

   public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) {
   //interface com.jane.mp.mapper.EmployeeMapper
        String className = mapperClass.toString();
        Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
        //如果缓存中没有className   则调用inject,然后放到mapperRegistryCache
        if (!mapperRegistryCache.contains(className)) {
        //参考1.8
            this.inject(builderAssistant, mapperClass);
 //将会放到GlobalConfiguration.mapperRegistryCache中(Set),下次即可从其获取
            mapperRegistryCache.add(className);
        }
    }

1.8 AutoSqlInjector.inject(MapperBuilderAssistant builderAssistant, Class mapperClass)

  public void inject(MapperBuilderAssistant builderAssistant, Class mapperClass) {
        this.configuration = builderAssistant.getConfiguration();
        this.builderAssistant = builderAssistant;
        this.languageDriver = this.configuration.getDefaultScriptingLanguageInstance();
        Class modelClass = this.extractModelClass(mapperClass);
        if (null != modelClass) {
      
            if (this.getGlobalConfig().isSqlParserCache()) {
                PluginUtils.initSqlParserInfoCache(mapperClass);
            }
//获取表信息
            TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
            this.injectSql(builderAssistant, mapperClass, modelClass, table);
        }

    }

看下这里TableInfo 是个什么?
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第18张图片

1.9 AutoSqlInjector.inject(MapperBuilderAssistant builderAssistant, Class mapperClass, Class modelClass, TableInfo table)

 protected void injectSql(MapperBuilderAssistant builderAssistant, Class mapperClass, Class modelClass, TableInfo table) {
        /**
         * 表信息包含主键,注入主键相关方法
         */
        if (StringUtils.isNotEmpty(table.getKeyProperty())) {
            /** 删除 */
            this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
            this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
            /** 修改 */
            this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
            this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
            /** 查询 */
            this.injectSelectByIdSql(false, mapperClass, modelClass, table);
            this.injectSelectByIdSql(true, mapperClass, modelClass, table);
        } else {
            // 表不包含主键时 给予警告
            logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
                modelClass.toString()));
        }
        /**
         * 正常注入无需主键方法
         */
        /** 插入 */
        this.injectInsertOneSql(true, mapperClass, modelClass, table);
        this.injectInsertOneSql(false, mapperClass, modelClass, table);
        /** 删除 */
        this.injectDeleteSql(mapperClass, modelClass, table);
        this.injectDeleteByMapSql(mapperClass, table);
        /** 修改 */
        this.injectUpdateSql(mapperClass, modelClass, table);
        /** 修改 (自定义 set 属性) */
        this.injectUpdateForSetSql(mapperClass, modelClass, table);
        /** 查询 */
        this.injectSelectByMapSql(mapperClass, modelClass, table);
        this.injectSelectOneSql(mapperClass, modelClass, table);
        this.injectSelectCountSql(mapperClass, modelClass, table);
        this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
        this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
        this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
        this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
        this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
        /** 自定义方法 */
        this.inject(configuration, builderAssistant, mapperClass, modelClass, table);
    }

③ 以 injectDeleteByIdSql为例查看具体如何注入了进去

MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第19张图片
① 调用AutoSqlInjector.injectDeleteByIdSql方法:

 protected void injectDeleteByIdSql(boolean batch, Class mapperClass, Class modelClass, TableInfo table) {
        SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID;
        String idStr = table.getKeyProperty();
        //判断是否批量删除,若是则封装foreach 语句
        if (batch) {
            sqlMethod = SqlMethod.DELETE_BATCH_BY_IDS;
            StringBuilder ids = new StringBuilder();
            ids.append("\n");
            ids.append("#{item}");
            ids.append("\n");
            idStr = ids.toString();
        }
//SQL格式化 
        String sql = String.format(sqlMethod.getSql(), table.getTableName(), table.getKeyColumn(), idStr);
      //根据SQL创建sqlSource 
        SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
    //封装一个个MappedStatement放到
        this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource);
    }

sqlMethod.getSql如下:


创建的SqlSource为:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第20张图片


② this.addDeleteMappedStatement

AutoSqlInjector.addDeleteMappedStatement方法源码如下:

public MappedStatement addDeleteMappedStatement(Class mapperClass, String id, SqlSource sqlSource) {
        return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, (Class)null, (String)null, Integer.class, new NoKeyGenerator(), (String)null, (String)null);
    }

其调用了addMappedStatement方法:

public MappedStatement addMappedStatement(Class mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class parameterClass, String resultMap, Class resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) {
        String statementName = mapperClass.getName() + "." + id;
        //判断是否已经存在
        if (this.hasMappedStatement(statementName)) {
            System.err.println("{" + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL.");
            return null;
        } else {
            boolean isSelect = false;
            //判断是否查询语句
            if (sqlCommandType == SqlCommandType.SELECT) {
                isSelect = true;
            }
//从这里继续跟 参考③
            return this.builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, (Integer)null, (Integer)null, (String)null, parameterClass, resultMap, resultType, (ResultSetType)null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, this.configuration.getDatabaseId(), this.languageDriver, (String)null);
        }
    }

③ MapperBuilderAssistant.addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets)

是的,这个方法的参数多到了难以想象(xml中标签属性都有)。主要参数如下所示:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第21张图片
方法源码如下:

public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
        if (this.unresolvedCacheRef) {
            throw new IncompleteElementException("Cache-ref not yet resolved");
        } else {
        //给方法名拼接上name space: com.jane.mp.mapper.EmployeeMapper.deleteById
            id = this.applyCurrentNamespace(id, false);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//获取statementBuilder 
            org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);

//获取参数对象
            ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
            if (statementParameterMap != null) {
                statementBuilder.parameterMap(statementParameterMap);
            }

            MappedStatement statement = statementBuilder.build();
            //从这里跟进去,参考④
            this.configuration.addMappedStatement(statement);
            return statement;
        }
    }

看一下创建的MappedStatement对象:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第22张图片


④ MybatisConfiguration.addMappedStatement(MappedStatement ms)

 public void addMappedStatement(MappedStatement ms) {
        logger.debug("addMappedStatement: " + ms.getId());
        //判断是否刷新,如果是就移除掉
        if (GlobalConfigUtils.isRefresh(ms.getConfiguration())) {
            this.mappedStatements.remove(ms.getId());
        } else if (this.mappedStatements.containsKey(ms.getId())) {
            logger.error("mapper[" + ms.getId() + "] is ignored, because it's exists, maybe from xml file");
            return;
        }
//从这里继续跟进去
        super.addMappedStatement(ms);
    }

留意日志发现其打印了addMappedStatement: com.jane.mp.mapper.EmployeeMapper.deleteById
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第23张图片
父类Configuration的addMappedStatement方法很简单:

 public void addMappedStatement(MappedStatement ms) {
 //以 ms.getId():ms  放入MybatisConfiguration的mappedStatements属性中
        this.mappedStatements.put(ms.getId(), ms);
    }

MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第24张图片
如下可以看到刚添加的deleteById :
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第25张图片
同理注入其他:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第26张图片
在上面的图示中我们还看到了许多SqlRunner.XXX 方法,那么这些是什么时候注入的呢?是在实例化MybatisSqlSessionFactoryBean过程中注入的。这又是一个漫长的过程,参考【7】


【7】sqlSessionFactoryBean的实例化过程

如下图所示,我们这里仍旧从AbstractAutowireCapableBeanFactory.invokeInitMethods方法中的afterPropertiesSet方法开始开始:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第27张图片

① MybatisSqlSessionFactoryBean.afterPropertiesSet

方法源码如下:

 public void afterPropertiesSet() throws Exception {
 //几个断言
        notNull(dataSource, "Property 'dataSource' is required");
        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");
//创建一个sqlsessionfactory,从这里跟进去② 
        this.sqlSessionFactory = buildSqlSessionFactory();
    }

② MybatisSqlSessionFactoryBean.buildSqlSessionFactory

这个是核心方法,用来创建Configuration 实例并根据configuration实例获取sqlsessionFactory实例。

    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

        Configuration configuration;

        // TODO 加载自定义 MybatisXmlConfigBuilder
        MybatisXMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
            configuration = this.configuration;
            if (configuration.getVariables() == null) {
                configuration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
                configuration.getVariables().putAll(this.configurationProperties);
            }
            //这里,如果configLocation 不为null,则获取MybatisXMLConfigBuilder并得到Configuration
        } else if (this.configLocation != null) {
            xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            }
            // TODO 使用自定义配置,注意这里直接new了一个MybatisConfiguration对象
            configuration = new MybatisConfiguration();
            if (this.configurationProperties != null) {
                configuration.setVariables(this.configurationProperties);
            }
        }

        if (this.objectFactory != null) {
            configuration.setObjectFactory(this.objectFactory);
        }

        if (this.objectWrapperFactory != null) {
            configuration.setObjectWrapperFactory(this.objectWrapperFactory);
        }

        if (this.vfs != null) {
            configuration.setVfsImpl(this.vfs);
        }

        if (hasLength(this.typeAliasesPackage)) {
            // TODO 支持自定义通配符
            String[] typeAliasPackageArray;
            if (typeAliasesPackage.contains("*") && !typeAliasesPackage.contains(",")
                && !typeAliasesPackage.contains(";")) {
                typeAliasPackageArray = PackageHelper.convertTypeAliasesPackage(typeAliasesPackage);
            } else {
                typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            }
            if (typeAliasPackageArray == null) {
                throw new MybatisPlusException("not find typeAliasesPackage:" + typeAliasesPackage);
            }
            for (String packageToScan : typeAliasPackageArray) {
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                    typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }

        // TODO 自定义枚举类扫描处理
        if (hasLength(this.typeEnumsPackage)) {
            Set classes = null;
            if (typeEnumsPackage.contains("*") && !typeEnumsPackage.contains(",")
                && !typeEnumsPackage.contains(";")) {
                classes = PackageHelper.scanTypePackage(typeEnumsPackage);
            } else {
                String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
                if (typeEnumsPackageArray == null) {
                    throw new MybatisPlusException("not find typeEnumsPackage:" + typeEnumsPackage);
                }
                classes = new HashSet();
                for (String typePackage : typeEnumsPackageArray) {
                    classes.addAll(PackageHelper.scanTypePackage(typePackage));
                }
            }
            // 取得类型转换注册器
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            for (Class cls : classes) {
                if (cls.isEnum()) {
                    if (IEnum.class.isAssignableFrom(cls)) {
                        typeHandlerRegistry.register(cls.getName(), com.baomidou.mybatisplus.handlers.EnumTypeHandler.class.getCanonicalName());
                    } else {
                        // 使用原生 EnumOrdinalTypeHandler
                        typeHandlerRegistry.register(cls.getName(), org.apache.ibatis.type.EnumOrdinalTypeHandler.class.getCanonicalName());
                    }
                }
            }
        }

        if (!isEmpty(this.typeAliases)) {
            for (Class typeAlias : this.typeAliases) {
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type alias: '" + typeAlias + "'");
                }
            }
        }

        if (!isEmpty(this.plugins)) {
            for (Interceptor plugin : this.plugins) {
                configuration.addInterceptor(plugin);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered plugin: '" + plugin + "'");
                }
            }
        }

        if (hasLength(this.typeHandlersPackage)) {
            String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeHandlersPackageArray) {
                configuration.getTypeHandlerRegistry().register(packageToScan);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
                }
            }
        }

        if (!isEmpty(this.typeHandlers)) {
            for (TypeHandler typeHandler : this.typeHandlers) {
                configuration.getTypeHandlerRegistry().register(typeHandler);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type handler: '" + typeHandler + "'");
                }
            }
        }

        if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
            try {
                configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
            }
        }

        if (this.cache != null) {
            configuration.addCache(this.cache);
        }

        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
                }
            } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
        }

        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
        // 设置元数据相关
        GlobalConfigUtils.setMetaData(dataSource, globalConfig);
        SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);
        // TODO SqlRunner
        SqlRunner.FACTORY = sqlSessionFactory;
        // TODO 缓存 sqlSessionFactory
        globalConfig.setSqlSessionFactory(sqlSessionFactory);
        // TODO 设置全局参数属性
        globalConfig.signGlobalConfig(sqlSessionFactory);
        if (!isEmpty(this.mapperLocations)) {
            if (globalConfig.isRefresh()) {
                //TODO 设置自动刷新配置 减少配置
                new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
                    2, true);
            }
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }

                try {
 // 解析mapper.xml中的配置,并将CRUD等创建为一个个mappedstatement对象放到configuration中
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                        configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                }
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
            }
        }
        return sqlSessionFactory;
    }

其他咱们先不管,由于本文测试的时候使用了mybatis-config.xml,所以这里会走到xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);,我们继续跟踪其构造函数:

public MybatisXMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }

#从这里继续跟踪进去
    private MybatisXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //TODO 自定义 Configuration
        super(new MybatisConfiguration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
#可以看到首先调用了   super(new MybatisConfiguration());
#还是走到了new MybatisConfiguration()

③ new MybatisConfiguration()如此重要

那么我们看下MybatisConfiguration这个类,其实MyBatis 或者 MP全局配置对象,其属性如下:

public class MybatisConfiguration extends Configuration {

    private static final Log logger = LogFactory.getLog(MybatisConfiguration.class);

    /**
     * Mapper 注册
     */
    public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);

    /**
     * 初始化调用
     */
    public MybatisConfiguration() {
        this.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        logger.debug("Mybatis-plus init success.");
    }
    //...
 }   

方法列表如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第28张图片
可以看到该类主要做三件事情:获取一个MybatisMapperRegistry注册中心、往mappedStatements中添加MappedStatement对象以及往mybatisMapperRegistry注册中心添加mapper: mybatisMapperRegistry.addMappers(packageName, superType);

需要擦亮眼睛的是,public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);这是一个用final修饰的变量,也就是常量。那么常量是在何时被赋值的呢?

静态变量和常量(如果有初始值)在类加载过程中就已经初始化了,在编译的时候存储在class的常量池中,在加载后又被放进方法区的运行时常量池中。

这里常量mybatisMapperRegistry被赋予了初始值,那么在类MybatisConfiguration加载过程中的编译阶段就会将初始值存入constantValue属性(class文件的常量池)中,在准备阶段就将constantValue的值赋给mybatisMapperRegistry。

这里对类加载、对象创建过程不懂的同学可以参考博文:Java类的加载过程详解(加载验证准备解析初始化使用卸载)、https://blog.csdn.net/J080624/article/details/82116500

ok,咱们继续回去看下MybatisMapperRegistry。


④ MybatisMapperRegistry

类源码如下:

public class MybatisMapperRegistry extends MapperRegistry {
//如果一个mapper已经被加载过,将会被放到knownMappers 中
    private final Map, MapperProxyFactory> knownMappers = new HashMap<>();

    //MybatisConfiguration实例对象的引用
    private final Configuration config;

    public MybatisMapperRegistry(Configuration config) {
        super(config);
        this.config = config;
        // TODO注入SqlRunner
        GlobalConfigUtils.getSqlInjector(config).injectSqlRunner(config);
    }

    @Override
    public  T getMapper(Class type, SqlSession sqlSession) {
        final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
        }
    }

    @Override
    public  boolean hasMapper(Class type) {
        return knownMappers.containsKey(type);
    }

    @Override
    public  void addMapper(Class type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // throw new BindingException("Type " + type +
                // " is already known to the MybatisPlusMapperRegistry.");
            }
            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.
                // TODO 自定义无 XML 注入---解析mapper中方法上面使用了注解SQL
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

    /**
     * @since 3.2.2
     */
    @Override
    public Collection> getMappers() {
        return Collections.unmodifiableCollection(knownMappers.keySet());
    }

}

核心在这里:

    GlobalConfigUtils.getSqlInjector(config).injectSqlRunner(config);

这里将会调用AutoSqlInjector.injectSqlRunner(Configuration configuration)注入sqlrunner相关的mappedStatement对象。

   @Override
    public void injectSqlRunner(Configuration configuration) {
        this.configuration = configuration;
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        initSelectList();
        initSelectObjs();
        initInsert();
        initUpdate();
        initDelete();
        initCount();
    }

最后看下日志:
在这里插入图片描述


【8】mapperLocation设置的那些mapper.xml配置文件何时被解析?

同样是在MybatisSqlSessionFactoryBean实例化过程中。代码如下实例:

if (!isEmpty(this.mapperLocations)) {
         if (globalConfig.isRefresh()) {
             //TODO 设置自动刷新配置 减少配置
             new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
                 2, true);
         }
         for (Resource mapperLocation : this.mapperLocations) {
             if (mapperLocation == null) {
                 continue;
             }
             try {
                 // 获取一个XMLMapperBuilder 然后解析那些mapper.xml配置文件
                 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                     configuration, mapperLocation.toString(), configuration.getSqlFragments());
                 xmlMapperBuilder.parse();
             }
             //...
         }
     }
     //...

① XMLMapperBuilder.parse

方法源码如下所示:

  public void parse() {
  //如果当前mapper.xml配置文件没有加载过,就会解析并加载
    if (!configuration.isResourceLoaded(resource)) {
    //从这里跟进去,参考2
      configurationElement(parser.evalNode("/mapper"));
      
      //解析完xml文件后,将该String resource放到loadedResources这个hashset中
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

② XMLMapperBuilder.configurationElement

方法源码如下:

  private void configurationElement(XNode context) {
    try {
    //获取namespace 
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //解析那些CRUD标签,并创建一个个MappedStatement,然后放到MappedStatements这个map中 参考3
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

③ XMLMapperBuilder.buildStatementFromContext(List list)

如下图所示,这里每一个NODE就对应了了一条SQL。
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第29张图片
其有继续调用了如下方法:

 private void buildStatementFromContext(List list, String requiredDatabaseId) {
   for (XNode context : list) {
   //获取一个XMLStatementBuilder ,然后解析结点
     final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
     try {
     //这里参考4
       statementParser.parseStatementNode();
     } catch (IncompleteElementException e) {
       configuration.addIncompleteStatement(statementParser);
     }
   }
 }

MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第30张图片

④ XMLStatementBuilder.parseStatementNode()

这个方法获取了标签(insert|update|delete|select)的相关属性,并在最后调用了builderAssistant这个助手将其添加MybatisConfiguration的 protected final Map mappedStatements = new StrictMap("Mapped Statements collection")

  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre:  and  were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
//看到这个是不是很熟悉?ok下面就不再重复过程了
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

【9】如果我在mapper接口中放上自定义方法并使用注解呢?

如下所示:

public interface EmployeeMapper extends BaseMapper {
    @Select("select * from tbl_employee")
    List selectEmployee();
}

注解在哪里被解析然后注入?

① 当你没有xml配置文件时

① MapperFactoryBean.checkDaoConfig

  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        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();
      }
    }
  }

这个核心入口configuration.addMapper(this.mapperInterface);,会调用如下方法:

@Override
    public  void addMapper(Class type) {
        mybatisMapperRegistry.addMapper(type);
    }

② MybatisMapperRegistry.addMapper(Class type)

    @Override
    public  void addMapper(Class type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // throw new BindingException("Type " + type +
                // " is already known to the MybatisPlusMapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                knownMappers.put(type, new MapperProxyFactory<>(type));
               //这里会对方法上面注解进行解析
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

③ MybatisMapperAnnotationBuilder.parse

 public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            parseCache();
            parseCacheRef();
            Method[] methods = type.getMethods();
            // TODO 注入 CURD 动态 SQL (应该在注解之前注入)
            if (BaseMapper.class.isAssignableFrom(type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
            for (Method method : methods) {
                try {
                    // issue #237
                    if (!method.isBridge()) {
                        parseStatement(method);
                    }
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }

② 如果有mapper.xml配置文件时(!isEmpty(this.mapperLocations))

在sqlSessionFactoryBean实例化过程中,会调用XMLMapperBuilder的parse方法。

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
    //解析那些mapper.xml配置文件
      configurationElement(parser.evalNode("/mapper"));
      //标记资源已经被解析
      configuration.addLoadedResource(resource);
      //这里这里这里!
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

这里我们重点看下bindMapperForNamespace方法:

  private void bindMapperForNamespace() {
  //获取命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
       //如果没有被解析过就执行如下方法
          configuration.addLoadedResource("namespace:" + namespace);
          //这里是核心入口
          configuration.addMapper(boundType);
        }
      }
    }
  }

configuration.addMapper(boundType);会调用MybatisMapperRegistry.addMapper(Class type):

@Override
public  void addMapper(Class type) {
     if (type.isInterface()) {
         if (hasMapper(type)) {
             // TODO 如果之前注入 直接返回
             return;
         }
         boolean loadCompleted = false;
         try {
             knownMappers.put(type, new MapperProxyFactory<>(type));
             // TODO 自定义无 XML 注入---解析方法上面的注解!!!
             MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
             parser.parse();
             loadCompleted = true;
         } finally {
             if (!loadCompleted) {
                 knownMappers.remove(type);
             }
         }
     }
 }

当然,因为首先会做判断是否已经解析。所以当你配置了mapperLocations且其不为空的时候,在实例化sqlSessionFactoryBean的过程中就会完成mappedStatement对象的注入。当你实例化employeeMapper时调用其包装类的afterPropertiesSet方法以及其后一系列过程最终走到上面方法的时候,就不会再次注入!

③ MybatisMapperAnnotationBuilder.parse

为什么要看这个方法呢?上面我们分析了没有mapper.xml、有mapper.xml(配置了mapperLocations属性)的情况,但是如果有mapper.xml但是没有配置mapperLocations属性呢?

public void parse() {
     String resource = type.toString();
     if (!configuration.isResourceLoaded(resource)) {
     //在这里,会尝试加载接口下面的同名xml文件进行解析!!!
         loadXmlResource();
         configuration.addLoadedResource(resource);
         assistant.setCurrentNamespace(type.getName());
         parseCache();
         parseCacheRef();
         Method[] methods = type.getMethods();
         // TODO 注入 CURD 动态 SQL (应该在注解之前注入)
         if (BaseMapper.class.isAssignableFrom(type)) {
             GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
         }
         for (Method method : methods) {
             try {
                 // issue #237
                 if (!method.isBridge()) {
                     parseStatement(method);
                 }
             } catch (IncompleteElementException e) {
                 configuration.addIncompleteMethod(new MethodResolver(this, method));
             }
         }
     }
     parsePendingMethods();
 }

MybatisMapperAnnotationBuilder.loadXmlResource

 private void loadXmlResource() {
//判断xml是否已经加载过
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        //接口同名xml文件
            String xmlResource = type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
            } catch (IOException e) {
                // ignore, resource is not required
            }
            if (inputStream != null) {
//获取XMLMapperBuilder进行解析
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
                xmlParser.parse();
            }
        }
    }

也就是说即使你调用了MybatisMapperAnnotationBuilder而非MapperXmlBuilder的parse方法时,同样给xml了一个解析机会!当然,当你调用MapperXmlBuilder的parse方法时,在bindMapperForNamespace()也给了注解解析的机会!


【10】什么是mappedStatements?

其是Configuration的一个属性,类型为StrictMap:

protected final Map mappedStatements =
 new StrictMap("Mapped Statements collection");

一 个 MappedStatement对象对应 Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条 SQL语句。

其主要属性如下:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第31张图片
看到这些属性是否很熟悉?我们对照下mapper.xml中一个select标签的属性:
MyBatis Plus通用CRUD与条件构造器使用及SQL自动注入原理分析_第32张图片


【11】SqlMethod是什么?

这是一个SQL模板的枚举类,根据这些模板解析出一个个具体的SQL

public enum SqlMethod {
    /**
     * 插入
     */
    INSERT_ONE("insert", "插入一条数据(选择字段插入)", ""),
    INSERT_ONE_ALL_COLUMN("insertAllColumn", "插入一条数据(全部字段插入)", ""),

    /**
     * 删除
     */
    DELETE_BY_ID("deleteById", "根据ID 删除一条数据", ""),
    DELETE_BY_MAP("deleteByMap", "根据columnMap 条件删除记录", ""),
    DELETE("delete", "根据 entity 条件删除记录", ""),
    DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量删除数据", ""),

    /**
     * 逻辑删除
     */
    LOGIC_DELETE_BY_ID("deleteById", "根据ID 逻辑删除一条数据", ""),
    LOGIC_DELETE_BY_MAP("deleteByMap", "根据columnMap 条件逻辑删除记录", ""),
    LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", ""),
    LOGIC_DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量逻辑删除数据", ""),

    /**
     * 修改
     */
    UPDATE_BY_ID("updateById", "根据ID 选择修改数据", ""),
    UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 修改全部数据", ""),
    UPDATE("update", "根据 whereEntity 条件,更新记录", ""),
    UPDATE_FOR_SET("updateForSet", "根据 whereEntity 条件,自定义Set值更新记录", ""),

    /**
     * 逻辑删除 -> 修改
     */
    LOGIC_UPDATE_BY_ID("updateById", "根据ID 修改数据", ""),
    LOGIC_UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 选择修改数据", ""),


    /**
     * 查询
     */
    SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s}"),
    SELECT_BY_MAP("selectByMap", "根据columnMap 查询一条数据", ""),
    SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", ""),
    SELECT_ONE("selectOne", "查询满足条件一条数据", ""),
    SELECT_COUNT("selectCount", "查询满足条件总记录数", ""),
    SELECT_LIST("selectList", "查询满足条件所有数据", ""),
    SELECT_PAGE("selectPage", "查询满足条件所有数据(并翻页)", ""),
    SELECT_MAPS("selectMaps", "查询满足条件所有数据", ""),
    SELECT_MAPS_PAGE("selectMapsPage", "查询满足条件所有数据(并翻页)", ""),
    SELECT_OBJS("selectObjs", "查询满足条件所有数据", ""),

    /**
     * 逻辑删除 -> 查询
     */
    LOGIC_SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s} %s"),
    LOGIC_SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", "");

    private final String method;
    private final String desc;
    private final String sql;

    SqlMethod(final String method, final String desc, final String sql) {
        this.method = method;
        this.desc = desc;
        this.sql = sql;
    }

    public String getMethod() {
        return this.method;
    }

    public String getDesc() {
        return this.desc;
    }

    public String getSql() {
        return this.sql;
    }

}

你可能感兴趣的:(#,MyBatis,Plus)