MyBatis && MyBatisPlus 源码分析

1. MyBatis Simple Demo

一个最简单的MyBatis Demo

1.1. 使用Demo

源码下载:mybatis-demo
对应DB表:mybatis-demo

public class UserService {
    public User getUserById(Integer userId) {
        SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            return userMapper.getUserById(userId);
        } finally {
            sqlSession.close();
        }
    }
}
public class MyBatisUtil
{
    private static SqlSessionFactory factory;

    private MyBatisUtil() {
    }

    static
    {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);
    }

    public static SqlSessionFactory getSqlSessionFactory()
    {
        return factory;
    }
}
  • mybatis-config.xml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE configuration
        PUBLIC '-//mybatis.org//DTD Config 3.0//EN'
        'http://mybatis.org/dtd/mybatis-3-config.dtd'>
<configuration>
    <properties resource='jdbc.properties'/>
    <typeAliases>
        <typeAlias type='com.sivalabs.mybatisdemo.domain.User' alias='user'></typeAlias>
    </typeAliases>
    <environments default='development'>
        <environment id='development'>
            <transactionManager type='JDBC'/>
            <dataSource type='POOLED'>
                <property name='driver' value='${jdbc.driverClassName}'/>
                <property name='url' value='${jdbc.url}'/>
                <property name='username' value='${jdbc.username}'/>
                <property name='password' value='${jdbc.password}'/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource='com/sivalabs/mybatisdemo/mappers/UserMapper.xml'/>
    </mappers>
</configuration>
  • UserMapper.xml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE mapper PUBLIC '-//mybatis.org//DTD Mapper 3.0//EN'
        'http://mybatis.org/dtd/mybatis-3-mapper.dtd'>

<mapper namespace='com.sivalabs.mybatisdemo.mappers.UserMapper'>

    <select id='getUserById' parameterType='int' resultType='com.sivalabs.mybatisdemo.domain.User'>
        SELECT
        user_id as userId,
        email_id as emailId ,
        password,
        first_name as firstName,
        last_name as lastName
        FROM USER
        WHERE USER_ID = #{userId}
    </select>
    <!-- Instead of referencing Fully Qualified Class Names we can register Aliases in mybatis-config.xml and use Alias names. -->
    <resultMap type='com.sivalabs.mybatisdemo.domain.User' id='UserResult'>
        <id property='userId' column='user_id'/>
        <result property='emailId' column='email_id'/>
        <result property='password' column='password'/>
        <result property='firstName' column='first_name'/>
        <result property='lastName' column='last_name'/>
    </resultMap>

    <select id='getAllUsers' resultMap='UserResult'>
        SELECT * FROM USER
    </select>

1.2. 代码实现

  • 在执行userMapper.getUserById(userId)之前,会先执行1.2.11.2.2代码
  • 1.2.3userMapper.getUserById(userId)真正执行的代码

1.2.1 解析配置文件(#hello-world-demo-2-1)

        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);

该段代码主要把mybatis-config.xmlUserMapper.xml中的各种配置解析到Configuration实例中去。

1.2.2 构造UserMapper实例

SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
1.2.2.1 通过openSession()来进行实例的初始化

此处可暂时略过。

1.2.2.2 通过getMapper()方法来获取Mapper实例

此处getMapper()方法的内部实现是为UserMapper生成一个动态代理,并且返回:

  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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

此处的意思为:
MapperProxy这个类的invoke()方法来实现对应的函数调用流程。

1.2.3 执行对应查询代码

            return userMapper.getUserById(userId);

因为这里的userMapper对象是动态代理对象,所以其实这里执行的是MapperProxy.invoke()方法。
MapperProxy.invoke()方法内容如下:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

这里生成了一个MapperMethod 对象,并且调用mapperMethod.execute(sqlSession, args)来进行真正的DB查询逻辑。

mapperMethod.execute(sqlSession, args)的内部逻辑如下所示

1.2.3.1 找到接口对应的sql

DefaultSqlSession#selectList()方法内容如下:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

根据接口找到对应的sql,然后交由executor执行

1.2.3.2 执行sql,封装结果

缺省情况下executor是SimpleExecutorSimpleExecutor.doQuery()方法内容如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 执行sql,获得结果
  2. 把结果转换成接口中声明的类型然后返回

2. MyBatis Plugin Demo

演示 MyBatis Plugin 功能的 Demo。

2.1. 使用Demo

源码下载:mybatis-demo
对应DB表:mybatis-demo

添加Interceptor类:

@Intercepts({@Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class})})
public class SQLStatsInterceptor implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        System.out.println("mybatis intercept sql:" + sql);
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        String dialect = properties.getProperty("dialect");
        System.out.println("mybatis intercept dialect:" + dialect);
    }
}

添加interceptor配置:

mybatis-config.xml 添加以下配置,

    <plugins>
        <plugin interceptor="com.sivalabs.mybatisdemo.interceptor.SQLStatsInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

2.2. 代码实现

2.2.1 解析配置文件

同上。2.1 解析配置文件

2.2.2 构造UserMapper实例

SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
2.2.2.1 通过openSession()来进行实例的初始化。

openSession()的内部内部实现如下:

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

主要业务逻辑为

  1. 开启一个事务
  2. 构造一个 newExecutor

configuration.newExecutor()的内部实现如下:

    executor = (Executor) interceptorChain.pluginAll(executor);

把所有的插件(Interceptor)嵌入到Executor中,具体实现如下:

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

细节已经展现,MyBatis 内部通过 Java 的动态代理来实现 Plugin(Interceptor)的嵌入。

2.2.2.2 通过getMapper()方法来获取Mapper实例。

同上 1.2.2.2

2.2.3 执行对应查询代码

同上 1.2.3

2.2.3.1 找到接口对应的sql

同上 1.2.3.1

2.2.3.2 执行sql,封装结果

缺省情况下executor是SimpleExecutorSimpleExecutor.doQuery()方法内容如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 执行sql,获得结果
  2. 把结果转换成接口中声明的类型然后返回

其中1.调用了configuration.newStatementHandler()来构造一个 statement,其内部实现如下:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

我们看到了interceptorChain.pluginAll(statementHandler),这里的逻辑便和2.2.2.1一致了,用 Java 的动态代理,把 Interceptor的代码嵌入到Handler实例中。

这样一来,如 SQLStatsInterceptor 的注解中声明的那样,在调用 StatementHandler.parameterize时,便会回调 Interceptor的代码实现了。

3. Mybatis With Spring

MyBatis 配合Spring使用时,MyBatis类库会把Mapper对象注册在Spring容器中,这样用户使用Mapper对象就十分方便了。

3.1. 使用Demo

源码下载:mybatis-spring-demo
对应DB表:mybatis-demo

UserMapper.java. UserMapper.xml1. . 2. 中的逻辑一样,主要的区别如下:

  • 数据库配置(如dataSource. sqlSessionFactory)由原本的mybatis-config.xml中移动到Spring配置文件中
  • 添加Spring拓展点,把Mapper对象注册到Spring容器中
  • 获取Mapper对象时,由代码获取改为@AutoWired用Spring注入。

Spring配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--获取数据库配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <property name="filters" value="stat"/>

        <property name="maxActive" value="20"/>
        <property name="initialSize" value="1"/>
        <property name="maxWait" value="60000"/>
        <property name="minIdle" value="1"/>

        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
    </bean>

    <!--sqlsessionFactory bean-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--自动扫描mapper接口,并注入sqlsession-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="org.mybatis.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

</beans>

代码测试用例如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-dao.xml")
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testGetUserById() {
        User user = userMapper.getUserById(1);
        Assert.assertNotNull(user);
        System.out.println(user);
    }
}

3.2. 代码实现

3.2.1 把Mapper对象注册到Spring容器中

这部分逻辑主要是通过以下Spring拓展点实现的:

    <!--自动扫描mapper接口,并注入sqlsession-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="org.mybatis.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

在Spring中添加以上Beam即可注册Mapper对象到Spring容器。

因为MapperScannerConfigurer实现了Spring的BeanDefinitionRegistryPostProcessor这个拓展点,该拓展点作用如下:

Spring允许在Bean创建之前,读取Bean的元属性,并根据自己的需求对元属性进行改变 1

Spring会回调BeanDefinitionRegistryPostProcessor这个接口的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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

这里的逻辑是,扫描basePackage包,找出所有符合要求的Bean注册到Spring容器中。

更具体的分为以下两步:

3.2.1.1 容器中添加Mapper对象

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan内容如下:

	 * Perform a scan within the specified base packages,
	 * returning the registered bean definitions.
	 * <p>This method does <i>not</i> register an annotation config processor
	 * but rather leaves this up to the caller.
	 * @param basePackages the packages to check for annotated classes
	 * @return set of beans registered if any for tooling registration purposes (never {@code null})
	 */
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

这个类的主要逻辑是找出符合要求的bean,包装成一个BeanDefinitionHolder,然后丢进Spring容器中。

3.2.1.2 指定Mapper对象的内部逻辑

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

  /**
   * Calls the parent search that will search and register all the candidates.
   * Then the registered objects are post processed to set them as
   * MapperFactoryBeans
   */
  @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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
        .....
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        .....
    }
    .....
}

这里的主要逻辑是把每个BeanDefinitionHolderBeanClass都设置成了MapperFactoryBean,而MapperFactoryBeangetObject逻辑如下:

org.mybatis.spring.mapper.MapperFactoryBean#getObject

  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

getMapper的底层实现为为Mapper对象生成一个动态代理对象:

  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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

这里和1.2.2.2便一样了。

3.2.2 执行对应查询代码

此部分内容和 1.2.3的逻辑一致。

4. MyBatis-Plus Demo

MyBatis-Plus 相比于直接使用 MyBatis 更简化了一步,连XML配置文件都不用写了,直接就可以使用内置的 Mapper 实现CRUD。

官方文档关于MyBatis-Plus 的介绍是:

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发. 提高效率而生。

MyBatis-Plus是如何做到这点的呢?
奥秘就在于,MyBatis-Plus增强了MyBatis,在MyBatis进行初始化的时候,直接使用反射生成了对应的XML丢尽了mybatis 容器中,从而实现了mybatis的增强!

接下来从代码的层面进行详细介绍。

4.1. 使用Demo

  • 启动配置
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(QuickStartApplication.class, args);
    }
}
  • 实体类
@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
  • Mapper类
public interface UserMapper extends BaseMapper<User> {
}
  • 开始使用
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
    }
}

4.2. 代码实现

MyBatis 的 configration中保存了 mapper方法 和 对应sql的映射关系,只要在 xml解析的时候,动态生成 mapper方法对应的 sql并且丢进 configuration中,就可以实现MyBatis的增强!

4.2.1. 在MyBatis-Plus的starter中替换掉sqlSessionFactory

  • spring.factories 配置了AutoConfiguration
    mybatis-plus-boot-startspring.factories的配置如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
  • MybatisPlusAutoConfiguration 中注册了 sqlSessionFactory

4.2.2. 注册Mapper的时候,添加mapper中方法对应的statement

  • com.baomidou.mybatisplus.MybatisMapperRegistry#addMapper 调用 MybatisMapperAnnotationBuilder
    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // TODO 这里就不抛异常了
//                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
                knownMappers.put(type, new MybatisMapperProxyFactory<>(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 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
  • MybatisMapperAnnotationBuilder#parse() 调用 ISqlInjector#inspectInject 进行 statement注入
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            String mapperName = type.getName();
            assistant.setCurrentNamespace(mapperName);
            parseCache();
            parseCacheRef();
            InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
            for (Method method : type.getMethods()) {
                if (!canHaveStatement(method)) {
                    continue;
                }
                if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                    && method.getAnnotation(ResultMap.class) == null) {
                    parseResultMap(method);
                }
                try {
                    parseStatement(method);
                    // TODO 加入 SqlParser 注解过滤缓存
                    InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
                    SqlParserHelper.initSqlParserInfoCache(mapperName, method);
                } catch (IncompleteElementException e) {
                    // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                    configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
                }
            }
            // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
            if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
        }
        parsePendingMethods();
    }
  • AbstractSqlInjector#inspectInject 调用 AbstractMethod#inject 进行注入

4.2.3. 后续其他逻辑同mybatis一样

4.3. mybatis-plus总结

mybatis通过巧妙的方式,增强了mybatis,而没有改变mybatis的原先的执行流程,非常的巧妙。一方面是mybatis-plus作者设计非常巧妙,另一方便mybatis提前预留的拓展点也为后续的增强提供了极大的便利。

  1. http://www.cnblogs.com/xrq730/p/5721366.html

/** * RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS. * LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/ /* var disqus_config = function () { this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable }; */ (function() { // DON'T EDIT BELOW THIS LINE var d = document, s = d.createElement('script'); s.src = 'https://chenzz.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); })();