当前位置: 代码迷 >> 综合 >> Spring整合Mybatis源码流程分析
  详细解决方案

Spring整合Mybatis源码流程分析

热度:79   发布时间:2023-09-05 18:36:37.0

本文主要结合源码分析流程,整合步骤请查看之前博文https://blog.csdn.net/qq_43792385/article/details/90017967

为了使用Spring整合mybatis,需要引入mybatis为接入spring开发的包,为了方便这里直接引入springboot整合包

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.1</version>
</dependency>

然后在主类或者配置类加上扫描注解

@MapperScan("com.qqxhb.mybatis.mapper")

MapperScan 源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;String sqlSessionTemplateRef() default "";String sqlSessionFactoryRef() default "";Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;}

从上面的源码中知道,我们在的注解可以设置mapper接口包、CLass以及SQLSession等,关键在注解上面有导入一个类@Import(MapperScannerRegistrar.class)

MapperScannerRegistrar部分源码

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {private ResourceLoader resourceLoader;@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}/*** 通过参数中传过Bean定义注册器BeanDefinitionRegistry,将需要交给spring容器管理的Bean注册进去,以便管理和实例化。*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//MapperScan注解信息AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {registerBeanDefinitions(mapperScanAttrs, registry);}}void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {//创建Mapper扫描器ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// 下面都是在获取前面说过的MapperScan注解中设置属性值,省略了部分源码Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);..............scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList<>();basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));	basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));scanner.registerFilters();//调用ClassPathMapperScanner的方法去扫描需要注册的beanscanner.doScan(StringUtils.toStringArray(basePackages));}
}

可以看到该类实现了spring的接口ImportBeanDefinitionRegistrar,该类的用法大体和BeanDefinitionRegistryPostProcessor相同,但是只能通过由其它类import的方式来加载。则会调用接口registerBeanDefinitions方法,将其中要注册的类注册成bean,达到动态注册bean到spring容器之中的效果。
从源码最后看到真正扫描注册的操作是交给ClassPathMapperScanner 的doScan方法去做,顾继续看ClassPathMapperScanner 的源码。

ClassPathMapperScanner 部分源码,有省略

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class);private boolean addToConfig = true;private SqlSessionFactory sqlSessionFactory;private SqlSessionTemplate sqlSessionTemplate;private String sqlSessionTemplateBeanName;private String sqlSessionFactoryBeanName;private Class<? extends Annotation> annotationClass;private Class<?> markerInterface;private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;public ClassPathMapperScanner(BeanDefinitionRegistry registry) {super(registry, false);}@Overridepublic 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;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59definition.setBeanClass(this.mapperFactoryBeanClass);definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}}
}

从源码中可以看到ClassPathMapperScanner 继承了spring的ClassPathBeanDefinitionScanner。首先调用父类的doScan方法,拿到扫描包下的所有Bean转换为BeanDefinitionHolder,该类就是三个属性BeanDefinition、beanName、aliases。然后遍历所有的BeanDefinitionHolder,设置额外的属性sqlSessionFactory、sqlSessionTemplate等,需要特别注意的是 definition.setBeanClass(this.mapperFactoryBeanClass);,他的意思所有BeanDefinition的bean类是MapperFactoryBean,也就意味着最后实例化的是MapperFactoryBean。

上面代码中最后有一句definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);即设置根据类型自动装配。一位置spring会根据装载bean的需要根绝类型去装载依赖Bean。

MapperFactoryBean部分源码

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}@Overridepublic Class<T> getObjectType() {return this.mapperInterface;}/*** {@inheritDoc}*/@Overridepublic boolean isSingleton() {return true;}}

从源码看到MapperFactoryBean实现了spring的接口FactoryBean。当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&。也就是说所有的mapper接口实例是通过MapperFactoryBean的getObject()方法生成的。

Mapper接口的实例化过程分析

 //MapperFactoryBean方法public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}//SqlSession的方法,原生mybatis使用的DefaultSqlSession实现类,与spring整合使用的是SqlSessionTemplate实现类public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}//Configuration 方法public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mybatisMapperRegistry.getMapper(type, sqlSession);}
//MapperRegistry方法public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}//MapperProxyFactory方法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<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}

从上面源码的调用链可以看到MapperFactoryBean的getObject()方法生成所有mapper实例,是通过JDK动态代理实现的。

补充——mybatis缓存见org.apache.ibatis.session.Configuration类

 //扫描之后会将扫描结果存放在这个Map中,Key是ID也就是全类名加方法名,value中存的是sql、参数、缓存等protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) ->". please check " + savedValue.getResource() + " and " + targetValue.getResource());protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

MappedStatement 部分源码

public final class MappedStatement {private String resource;private Configuration configuration;private String id;private Integer fetchSize;private Integer timeout;private StatementType statementType;private ResultSetType resultSetType;private SqlSource sqlSource;private Cache cache;private ParameterMap parameterMap;private List<ResultMap> resultMaps;private boolean flushCacheRequired;private boolean useCache;private boolean resultOrdered;private SqlCommandType sqlCommandType;private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private boolean hasNestedResultMaps;private String databaseId;private Log statementLog;private LanguageDriver lang;private String[] resultSets;MappedStatement() {// constructor disabled}public static class Builder {private MappedStatement mappedStatement = new MappedStatement();public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {mappedStatement.configuration = configuration;mappedStatement.id = id;mappedStatement.sqlSource = sqlSource;mappedStatement.statementType = StatementType.PREPARED;mappedStatement.resultSetType = ResultSetType.DEFAULT;mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();mappedStatement.resultMaps = new ArrayList<>();mappedStatement.sqlCommandType = sqlCommandType;mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;String logId = id;if (configuration.getLogPrefix() != null) {logId = configuration.getLogPrefix() + id;}mappedStatement.statementLog = LogFactory.getLog(logId);mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}//省略部分代码}

源码分析 spring整合mybatis一级缓存为什么会失效
一级缓存是SqlSession级别的缓存,不同的sqlSession之间的缓存数据区域是互相不影响的。
二级缓存是mapper级别的缓(按namespace分),多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
(二级缓存坑:当你在两个mapper操作同一个表的时候,就会出现缓存不同步。因为mapper1更新了某个表,mapper2的缓存不会更新它读取的数据还是修改之前的)

上面提到过spring整合mybatis中使用的是SqlSessionTemplate实现类,部分源码:


public class SqlSessionTemplate implements SqlSession, DisposableBean {private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;private final PersistenceExceptionTranslator exceptionTranslator;public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {this(sqlSessionFactory, executorType,new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;//sqlSessionProxy代理类this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());}public SqlSessionFactory getSqlSessionFactory() {return this.sqlSessionFactory;}public ExecutorType getExecutorType() {return this.executorType;}@Overridepublic void select(String statement, ResultHandler handler) {this.sqlSessionProxy.select(statement, handler);}/*** {@inheritDoc}*/@Overridepublic void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);}@Overridepublic int delete(String statement) {return this.sqlSessionProxy.delete(statement);}private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {Object result = method.invoke(sqlSession, args);if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);}return result;} catch (Throwable t) {Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {// release the connection to avoid a deadlock if the translator is no loaded. See issue #22closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);if (translated != null) {unwrapped = translated;}}throw unwrapped;} finally {if (sqlSession != null) {//执行结束后,关闭sqlSessioncloseSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}}}

从源码看到,所有的SQL操作都是通过sqlSessionProxy操作,这个代理类也是使用JDK动态代理生成的。所以所有的SQL操作实际是通过SqlSessionInterceptor的invoke方法调用的,而SqlSessionInterceptor每次执行完就调用closeSqlSession将SqlSession关闭了,因此会导致整合spring后mybatis的一级(SqlSession级)缓存失效。

  相关解决方案