Mybatis核心对象解释
-
Configuration:初始化基础配置,比如mybatis的别名、一些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中。
-
SqlSessionFactory: SqlSession工厂,默认是DefaultSqlSessionFactory。
-
SqlSession:mybatis工作的主要顶级API,每一个SqlSession对象表示一次与数据库交互的回话,完成必要的增删改查功能。
-
Executor: mybatis执行器,是mybatis的调度中心,负责SQL语句的生成和查询缓存的维护
-
StatementHandler: 封装了JDBC Statement操作,作用是设置参数、将结果集转成list集合
-
ParameterHandler: 将用户传递的参数转换成JDBC Statement 所需要的参数
-
ResultSetHandler: 将JDBC返回的ResultSet结果集对象转换成List类型集合
-
TypeHandler: 负责java数据类型和jdbc数据类型之间的映射和转换
-
MappedStatement: MappedStatement维护了一条<select|update|delete|insert>节点的封装
-
SqlSource: 根据用户传递参数,动态生成SQL语句,将信息封装到BoundSql对象
-
BoundSql: 包含已生成的sql信息、及参数信息
Mybatis的Mapper对象创建过程
暂以Springboot项目为例,浅谈下Springboot项目启动后Mybatis的初始化加载过程。
我们先看一个最基础的Springboot和Mybatis的整合案例:https://gitee.com/wangkeqiang118706/poem.git。
我们知道Springboot项目特点是通过自动配置原理,通过扫描各jar包classpath/META-INF下的spring.factories文件,将application.properties或application.yml配置文件的配置信息自动注入到spring.factories文件中的各AutoConfig类中。
我们看下Mybatis的jar包META-INF/spring.factories究竟有什么内容,如下:
Mybatis的自动配置类MybatisAutoConfiguration,我们进到该类中会发现该配置类有声明了SqlSessionFactory、SqlSessionTemplate两个bean对象,这两个对象参考上文,我们都比较熟悉,SqlSessionFactory是会话工厂,SqlSessionTemplate就是特殊的SqlSession,它内部维护了一个特殊SqlSession(动态代理生成,auto_commit设置为true。
至于第三个AutoConfiguredMapperScannerRegistrar对象,其实我们通过代码能看到,只有当前工厂中不存在MapperFactoryBean对象时,MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar对象才会被创建,那么我们先考虑下MapperFactoryBean究竟是什么?
MapperFactoryBean: 一言蔽之,MapperFactoryBean是用以创建Mybatis mapper对象的工厂bean,该类实现了FactoryBean接口,我们知道凡是实现FactoryBean接口的对象,在Spring工厂实例化对象时,是通过getObject()方法来完成的,那么Mapper的实例化与此方法有关。
断点1:在此留一个断点,我们稍后往下深究MapperFactoryBean的getObject()方法。
我们以debug模式启动项目时,会发现,当SqlSessionFactory、SqlSessionTemplate两个bean对象都已经加载完成时,第三个对象AutoConfiguredMapperScannerRegistrar对象却不会被创建,这说明MapperFactoryBean已经被创建过了,那么它是在什么时间被创建的呢?
原因是项目中配置了@MapperScan的注解。
@MapperScan(basePackages = “com.sclience.poem.dao”)
我们看下@MapperScan这个注解,该注解导入了一个bean对象MapperScannerRegistrar,MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,并实现了registerBeanDefinitions方法,看名称我们能揣摩出大半,个人理解在spring工厂对bean实例化之前,需要先将各种bean定义为BeanDefinition结构,BeanDefinition可以理解为是对一个bean基本信息的描述。而MapperScannerRegistrar正是要将mapper对象先转换为BeanDefinition结构,为下一步的bean对象实例化做准备。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList();
String[] var10 = annoAttrs.getStringArray("value");
int var11 = var10.length;
int var12;
String pkg;
for(var12 = 0; var12 < var11; ++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
var10 = annoAttrs.getStringArray("basePackages");
var11 = var10.length;
for(var12 = 0; var12 < var11; ++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
var11 = var14.length;
for(var12 = 0; var12 < var11; ++var12) {
Class<?> clazz = var14[var12];
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
方法内容较简单,最终的目的就是扫描所配置的包名,我们深究下scanner.doScan(StringUtils.toStringArray(basePackages))这个方法。
scanner是一个ClassPathMapperScanner对象,该类继承了Spring的ClassPathBeanDefinitionScanner,也就是Spring原生的包扫描类,项目中常见@Controller、@Service等Spring原生的注解均是该类扫描并转换为BeanDefinition结构。
ClassPathMapperScanner的doScan方法先调用了父类ClassPathBeanDefinitionScanner的doScan方法,完成了Mapper对象转BeanDefinition结构。
大家注意,这个地方有一个小细节,及其容易忽略: BeanDefinition有一个关键属性beanClassName,bean对象实例化时与此有关。而Mapper在转BeanDefinition结构时,beanClassName对应的是mapper接口的限定名,如com.xxx.xxx.mapper.PoetMapper, 那么问题来了,我们都知道接口是不能被直接实例化的,Spring工厂是如何实例化这些mapper接口对象的呢?
断点2:mapper接口怎么实例化?
继续看ClassPathMapperScanner的doScan方法,如下图,processBeanDefinitions方法将刚生成的BeanDefinition的beanClassName设置为了MapperFactoryBean.class。
看到这里,断点1的问题我们就可以接着说了,各mapper bean已经注册完成,下一步就可以开始实例化了,
而mapper对象的实例化就在MapperFactoryBean的getObject()方法中实现的。我们沿着MapperFactoryBean的getObject()方法往下深究,最终到了MapperRegistry的getMapper方法上。 通俗地讲,MapperRegistry的作用是注册和获取mapper接口的代理服务站,MapperRegistry的getMapper方法如下,该方法通过MapperProxyFactory 即mapper代理工厂基于jdk动态代理生成了mapper接口的一个MapperProxy代理对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
//mapperProxyFactory.newInstance代码
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
至此第二个断点疑问也解决了,是通过MapperProxyFactory生成了mapper接口的一个MapperProxy代理对象,完成了mapper bean的实例化。
不过我们要继续深究下MapperProxy,也就是我们要搞清楚mapper代理对象到底干了些什么:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
// 通过MapperMethod来执行mapper接口中方法
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
// mapperMethod.execute方法如下
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
//如果有结果处理器
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
//如果结果有多条记录
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
//如果结果是map
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
//否则就是一条记录
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
MapperMethod 该类是一个映射器方法,封装了insert|update|delete|select四种常见,分别调用SqlSession的4大类方法。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置,对应的sql语句通过mapper对应的class文件名+方法名从Configuration对象中获得。
看到这里是不是有种恍然大明白的感觉,原来所有的mapper接口操作,都转化为了操作SqlSession的方法了,这样一来,我们也不用再手动维护SqlSession对象了,全部交由spring工厂管理,代码量大大减少了,更利于维护,国安民乐,岂不美哉?!
补充一下,如果不使用@MapperScan注解,也可以在每个mapper接口上增加@Mapper注解,这样的话会走MybatisAutoConfiguration的
AutoConfiguredMapperScannerRegistrar类中的registerBeanDefinitions方法,扫描@Mapper注解,与上述过程无异。
Mybatis的加载过程
Mybatis四大组件
1. Executor
Executor是mybatis执行器,是mybatis的调度中心,负责SQL语句的生成和查询缓存的维护。SqlSession都会拥有一个Executor对象,这个对象负责增删改查的具体操作,我们可以简单的将Executor理解为JDBC中Statement的封装版。Executor继承结构如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvpertUj-1598490828892)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\1597293403345.png)]
- BaseExecutor是一个抽象类,有三个子类:
- SimpleExecutor: 简单执行器,是MyBatis中默认使用的执行器,每执行一次update或select,就开启一个Statement对象,用完就直接关闭Statement对象(可以是Statement或者是PreparedStatment对象)
- ReuseExecutor: 可重用执行器,这里的重用指的是重复使用Statement,它会在内部使用一个Map把创建的Statement都缓存起来,每次执行SQL命令的时候,都会去判断是否存在基于该SQL的Statement对象,如果存在Statement对象并且对应的connection还没有关闭的情况下就继续使用之前的Statement对象,并将其缓存起来。因为每一个SqlSession都有一个新的Executor对象,所以我们缓存在ReuseExecutor上的Statement作用域是同一个SqlSession。
- BatchExecutor: 批处理执行器,用于将多个SQL一次性输出到数据库
- CachingExecutor:缓存执行器,,先从缓存中查询结果,如果存在,就返回;如果不存在,再委托给Executor delegate 去数据库中取,delegate可以是上面任何一个执行器
2. StatementHandler
StatementHandler: 封装了JDBC Statement操作,作用是设置参数、将结果集转成list集合
- BaseStatementHandler是一个抽象类,有三个子类:
- SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理;
- PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口
- CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口
- RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责根据MappedStatement中的StatementType来上面三个StatementHandler的创建及调用。
3. ParameterHandler
ParameterHandler: 将用户传递的参数转换成JDBC Statement 所需要的参数 。ParameterHandler只有一个实现类DefaultParameterHandler,较其他组件简单,负责为PreparedStatement 的 sql 语句参数动态赋值。它实现了两个方法。
-
getParameterObject: 用于读取参数
-
setParameters: 用于对 PreparedStatement 的参数赋值
参数处理器对象是在创建 StatementHandler 对象的同时被创建的,由 Configuration 对象负责创建,具体参照BaseStatementHandler类的构造方法:
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 创建参数处理器
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 创建结果映射器
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
我们可以细致梳理下ParameterHandler的创建过程,对应的Configuration 逻辑代码如下:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
它实际上是交由 LanguageDriver
来创建具体的参数处理器,LanguageDriver 默认的实现类是 XMLLanguageDriver
,由它调用 DefaultParameterHandler
中的构造方法完成 ParameterHandler 的创建工作。
此时可能会存在一个疑问,参数是如何而来的呢?我们知道Parameter中的参数是由Mapper接口和xml中的sql对应而来的,那这个参数是如何映射的呢? 我们继续往下深究:
我们知道Mybatis的sql主程序是MapperProxy的invoke方法,该类是基于jdk动态代理实现,通过处理sql及参数并封装成MappedStatement的对象,该类继承了HashMap。最终MapperProxy将执行交给了Executor 、StatementHandler进行对应的参数解析和执行,因为是带参数的sql语句,最终会创建PreparedStatementHandler对象并创建ParameterHandler参数解析器进行参数解析。最终在生成jdbc Statement对象方法中,会调用ParameterHandler的setParameters方法。
4. ResultSetHandler
ResultSetHandler: 将JDBC返回的ResultSet结果集对象转换成List类型集合,该接口只有一个实现类DefaultResultSetHandler.
该接口方法解释如下:
public interface ResultSetHandler {
// 将Statement执行后产生的结果集(可能有多个结果集)映射为结果列表
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 处理存储过程执行后的输出参数
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
这个就不多解释了,主要功能就是:
- 获取结果集ResultSet
- 获取rersultMap,即结果列和实体类成员变量的对应关系
- 将结果集处理为对应的ResultMap对象
- 将RresultMap对象添加至集合
- 返回list集合
Mybatis拦截器
基础概念:
mybatis可以在执行语句的过程中对特定对象进行拦截调用,由上可得知主要有四个组件的方法可做拦截处理:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 处理增删改查
- ParameterHandler (getParameterObject, setParameters) 设置预编译参数
- ResultSetHandler (handleResultSets, handleOutputParameters) 处理结果
- StatementHandler (prepare, parameterize, batch, update, query) 处理sql预编译,设置参数。
我们需要了解如下概念:
mybatis拦截器需要实现Interceptor接口,并实现其方法:
1. intercept: 拦截器核心逻辑,通过Invocation可获取到具体的拦截对象
2. setProperties:用于初始化
-
plugin:包装目标对象供拦截器处理,基于动态代理实现,一般方法返回Plugin.wrap(target, this); this代指 拦截器对象。
我们需要着重了解下plugin.wrap方法,该方法代码如下,主要逻辑是获取拦截器@Intercepts注解中包含了哪些@Signature,, 一个Signature包含了需要拦截的类及方法名和参数类型。
Plugin类是动态代理类,对实现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); return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target; }
上面有说到Configuration类是mybatis的核心配置类,其中ParameterHandler、ResultSetHandler、StatementHandler、Executor都有对应的创建方法,换句话讲,该四大组件对象的创建必过Configuration类来完成,大致如下:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return this.newExecutor(transaction, this.defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
我们可以看到每个组件对象在创建时都使用了this.interceptorChain.pluginAll() , 该句柄含义是返回一个经过代理链处理的对象。
使用场景
场景1:对某列关键数据进行特殊处理
拦截ResultSetHandler的handleResultSets方法, 对执行结果进行遍历,处理需要处理的列。
@Intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)})
场景2: 计算sql执行时间
多种方法可实现,只展示一种:
@Intercepts({
@Signature(
method = "query",
type = Executor.class,
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
在原方法执行前后加时间判断即可。
场景3:实现分页插件
多种方法可实现,只展示一种:
@Intercepts({
@Signature
(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature
(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
),
})
该功能已有成熟方案,我们可以拿来复用
场景4:数据分表
场景5: 对带参数执行的sql进行拼接参数处理
@Intercepts(
@Signature(
type = ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class}
)
)