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.1
和1.2.2
代码 1.2.3
是userMapper.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.xml
和UserMapper.xml
中的各种配置解析到Configuration
实例中去。
1.2.2 构造UserMapper实例
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
openSession()
来进行实例的初始化
1.2.2.1 通过此处可暂时略过。
getMapper()
方法来获取Mapper实例
1.2.2.2 通过此处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是SimpleExecutor
,SimpleExecutor.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);
}
}
- 执行sql,获得结果
- 把结果转换成接口中声明的类型然后返回
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);
openSession()
来进行实例的初始化。
2.2.2.1 通过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();
}
}
主要业务逻辑为
- 开启一个事务
- 构造一个
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)的嵌入。
getMapper()
方法来获取Mapper实例。
2.2.2.2 通过同上 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是SimpleExecutor
,SimpleExecutor.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);
}
}
- 执行sql,获得结果
- 把结果转换成接口中声明的类型然后返回
其中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.xml
和1.
. 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());
.....
}
.....
}
这里的主要逻辑是把每个BeanDefinitionHolder
的BeanClass
都设置成了MapperFactoryBean
,而MapperFactoryBean
的getObject
逻辑如下:
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-start
的spring.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提前预留的拓展点也为后续的增强提供了极大的便利。