最近看一些对mybatis plus的扩展的实现,但之前没怎么看过mybatis与plus,做应用就按示例直接用就行。而做组件扩展就要研究源码了。为了快速弄清楚,跟踪了一下代码,梳理了一下实现逻辑。
看懂是一方面,仔细思考plus扩展mybatis时的各种技巧更重要,比如extends改写,或者用新的interface实现,这些后续再补吧。
大概调用过程图,展示重要的对象,以及主要过程。里面两个细节,一个是plus的injector,用通用的SQL结合mapper接口的方法,产生对应的语句。另一个是interceptor拦截执行过程。
一、主要处理过程
主要过程,先从xml文件读取各种配置,包括mapper的类与sql,把得到的配置对象,特别是用mapper得到的MappedStatement(包括解析xml,inject固定方法,产生对应的MappedStatement),都存在一个config对象中。另外,对于获取mapper接口实现类时,会动态代理接口,内部找到接口方法对应的MappedStatement,最后用一个execator执行(包括用一个handler处理statement,并plugin来代理实现拦截的handler)。
本文分析时,混合mybatis/plus进行。先找到使用组件的启动入口。
非spring时,mybatis一个使用方式如下:
//不考虑spring时
@Test
public void testMyBatisBuild() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = factory.openSession();
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
Ttest one = mapper.getOne(1L);
System.out.println(one);
sqlSession.close();
}
使用spring时,上面的一些类要自动产生,比如SqlSessionFactory/TestMapper等,启动spring后直接装配好。所以关注:mybatis-plus-boot-starter-3.3.1.jar 中的MybatisPlusAutoConfiguration.java。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
...
//静态类:AutoConfiguredMapperScannerRegistrar 会注册一个mapperscan来处理mapper接口
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
...
//上面的MapperScannerConfigurer实现了bean定义注册接口,所以它可以注册新的bean到IOC中,这时又New一个类,委托它来处理注册mapper。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
...
//它在doScan时,对于每一个接口,增加一个bean定义,定义中类都是一个FactoryBean(这点重要)
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);!!!!!!都是同一个类
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
//所有的接口,注册到ioc中,都是这个 FactoryBean。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
//最重要的方法就是getObject,可以看到这里与非spring的代码中sqlSession.getMapper(TestMapper.class)一样了。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
1、入口 -- MybatisSqlSessionFactoryBuilder
先从plus的MybatisSqlSessionFactoryBuilder作为入口进行代码跟踪。它extends重写了mybatis的SqlSessionFactoryBuilder。
它的核心过程就两步:
-
用MybatisXMLConfigBuilder来处理从xml文件中读到的配置,产生MybatisConfiguration。
-
另外用MybatisConfiguration再产生SqlSessionFactory。
plus的MybatisConfiguration 也是extends Configuration(内容非常多),plus主要加了MybatisMapperRegistry属性。
这一点也容易理解,持久化工具层次要使用sqlSession,扩展的组件,最好把新功能放在构建这个sqlSession的过程中,产生自己要用的,而且包含了sqlSession的新持久化对象mapper。
//虽然MybatisSqlSessionFactoryBuilder是改写的,但返回的还是supper类产生的DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = super.build(configuration);
...
//supper中返回的是这样
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
//DefaultSqlSessionFactory作为一个工厂,得到的SqlSession如下:
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);//配备了一个执行器
//前面看到,要用DefaultSqlSession得到一个接口的实现类。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
//configuration是被改写过的MybatisConfiguration,还是原来的那个。方法也不一样了,发现用的是改写时新增加的mybatisMapperRegistry。
/**
* 使用自己的 MybatisMapperRegistry
*/
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
//这里又是用plus的MybatisMapperProxyFactory来产生代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
//动态代理Proxy.newProxyInstance核心的就是handler,这里就是
new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
//MybatisMapperProxy作为InvocationHandler,它的invoker方法中核心是:
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
//MybatisMapperMethod执行时,比如
sqlSession.insert(command.getName(), param)
//前面提到这里是mybatis的DefaultSqlSession,没有plus的
//比如public int update(String statement, Object parameter)中,还是交executor。
//另外,这里说明MappedStatement是核心语句对象。
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
//好几个executor,看配置的是哪个,plus主要是MybatisCachingExecutor,它代理包装了mybaits的其它executor。
//CachingExecutor 主要修改了分页缓存逻辑
//它核心还是靠mybatis代理的executor-delegate来工作。
// 切勿将这提取至上方,如果先查的话,需要提前将boundSql拷贝一份
result = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//所以看一下SimpleExecutor的例子,它使用StatementHandler具体工作。
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
//看到mybatis的config中相关方法。看到是RoutingStatementHandler,不过有一个interceptorChain。
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
//pluginAll中的一个个interceptor中,就是wrap成动态代理。
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;
}
//Plugin又是动态代理的invocationHandler,它的核心方法invoke,实际是用interceptorChain中的每个interceptor,执行intercept。参数就是Invocation。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
//自己实现一个 interceptor,它核心方法就是intercept,可以对请求方法/对象/参数/返回值进行自己想要的切入逻辑。
public Object intercept(Invocation invocation) throws Throwable {
..//before
Object object = invocation.proceed();
...//after
//interceptor不说了,介绍里面的StatementHandler,前面RoutingStatementHandler中又有其它的,比如
SimpleStatementHandler extends BaseStatementHandler
//最简单的情况,就是执行sql了。
statement.execute(sql);
rows = statement.getUpdateCount();
2、解析--MybatisXMLConfigBuilder
MybatisSqlSessionFactoryBuilder 中包括从xml解析出config,再进行build()的方法。所以主要环节有xml解析,解析出xml文件中很多类,多数放在MybatisConfiguration(扩展configuration)类中,因为都是配置出来的相关工具。核心看处理xml的mappers节点:mapperElement(root.evalNode("mappers"));。里面用XMLMapperBuilder 进一步进行Parse。主要是:bindMapperForNamespace(); 对mapper目录中的xml与接口文件,分别进行解析。最后是 MybatisConfiguration(扩展configuration) 中的 MybatisMapperRegistry 中,按类型,放入mapper解析的结果,就是产生MappedStatement并放在配置类中。configuration.addMappedStatement(statement);
解析的重点-injector
如果是plus的继承Mapper,就会有一些injector,为这个类型注入一些基本的操作方法。DefaultSqlInjector中,可以给mapper类提供默认的几十个操作类AbstractMethod的实现。这里可以作为未来增强通用方法时扩展改造。
//DefaultSqlInjector中,内置了一些方法,这些方法与特定的类组合,可以拼出一个具体表的sql出来,也就是产生 MappedStatement.
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
return Stream.of(
new Insert(),
new Delete(),
new DeleteByMap(),
...
//AbstractSqlInjector
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
//为getMethodList中的几十个操作类,进行注入,产生MappedStatement
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
//每个操作类,比如Delete(),它继承AbstractMethod,核心方法inject在每个类中具体实现的方法是injectMappedStatement:
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
/* 注入自定义方法 */
injectMappedStatement(mapperClass, modelClass, tableInfo);
}
//比如Insert的injectMappedStatement,就是String.format中,把表插到SQL模板中,最后产生MappedStatement,记录下来。
ublic class Insert extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = new NoKeyGenerator();
...
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
}
}
//SqlMethod中的枚举方法中,有SQL模板,要替换其它的%s,比如对应的表。不过这只是产生一个sql,后面具体执行时,还要拼参数才是具体的SQL。
DELETE_BY_ID("deleteById", "根据ID 删除一条数据", "<script>\nDELETE FROM %s WHERE %s=#{%s}\n</script>"),
所以想扩展公共的方法,不管用什么方式写,最后都是产生各个类型下的各个操作方法相关的MappedStatement。比如解析type时,用自己加的注解,可以拼到特定的SQL中。
3、DefaultSqlSession
前面初始化的MybatisSqlSessionFactory工厂,解析了半天,最后的目的就是生成一个DefaultSqlSession(SqlSession),用它就可以得到一个mapper对象了
interface SqlSession:
* Through this interface you can execute commands, get mappers and manage transactions.
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
我们知道mapper类是一个interface,真正要执行的话,要有一个实现这个接口的类,常见的办法就是动态代理。而动态代理中实现代理就要看产生代理对象时传入的InvocationHandler,即MybatisMapperProxy。
它的invoke方法中,最主要就是这里:
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
MybatisMapperMethod中,真正的执行还是 return executor.update(ms, wrapCollection(parameter));
3、执行-MybatisSimpleExecutor
执行器内部用一个new的handler处理数据库的query/update等操作。 StatementHandler就是用statement来执行sql。而sql可以用对应的MappedStatement与查询参数得到。
StatementHandler有一个重要的功能点是,加入了 interceptorChain,这样给扩展提供了便利。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
包装原来的target时,传入增强的interceptor,又会用到动态代理。
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;
}
调用到Plugin的invoke()代理方法里,就传入实际的【执行对象/方法/参数】作为参数,在实现上,可以切入自己的逻辑。类似于AOP中的joinpoint。
另外 pluginAll 是一个个interceptor进行增强的。与filterChain实现原理不一样。Plugin就是动态代理的invocationHandler,它的核心invoke方法,还是调用interceptor来执行。
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
二、扩展plus的思考
自己实现 Interceptor,可以扩展执行SQL前的一些操作。比如日志什么的。就是mybatis的原理中的。
考虑到mapper上的方法,是按名字找到对应的AbstractMethod实现类,这样就可以产生对应方法的MappedStatement。所以需要操作对象类上的特定字段(自己注解)与放入一些mapper特定的查询方法中时,可以继承AbstractMethod
在实现其实抽象方法 injectMappedStatement时,可以查到注解的特定字段,拼接产生sql,最后产生新的MappedStatement。