详解spring-mybatis是如何进行整合的
前言:
本篇文章只会分析spring和mybatis是如何进行解析的,如果你是想了解mybatis的底层源码实现,可能不会带给你很大的帮助。还是以0xml的方式进来解析
测试环境:
@MapperScan("com.lihuia.mybatis.mapper")
@Configuration
public class AppConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("admin");
driverManagerDataSource.setUrl("jdbc:mysql:///banksys?serverTimezone=UTC&characterEncoding=utf-8");
return driverManagerDataSource;
}
}
@MapperScan 可以说这一个注解完成spring与mybatis的整合
sqlSessionFactory这个类对于Mybatis来说是无与伦比的重要,他就相当于spring的beanFactory,至于具体的功能本篇文章不会进行说明,他以sqlSessionFactoryBean交给spring管理,并且必须要手动进行初始化然后交给spring,至于为什么?你总不能修改Mybatis的源码把。
//sqlSessionFactorybean实现了FactoryBean接口,所以直接看getObject()方法
public org.apache.ibatis.session.SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//省略的代码就是把配置文件中的内容保存在configuration,然后通过configuration构建sqlSessionFaactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
//使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
再讲一下spring为什么能够扫描@MapperScan()注解中的内容
//直接把@MapperScan上的注释拿过来
* Use this annotation to register MyBatis mapper interfaces when using Java
* Config. It performs when same work as {@link MapperScannerConfigurer} via
* {@link MapperScannerRegistrar}.
* 使用Java时,使用此注释注册MyBatis映射器接口配置。{@link MapperScannerConfigurer}和{@link mapperscannerregistar}有着相同的功能
* 所以我们直接去看 MapperScannerRegistrar 这个东西
//再看MapperScannerRegistrar上的注释
* A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of
* MyBatis mapper scanning. Using an @Enable annotation allows beans to be
* registered via @Component configuration, whereas implementing
* {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
*
* @author Michael Lanyon
* @author Eduardo Macarron
*
* @see MapperFactoryBean
* @see ClassPathMapperScanner
* @since 1.2.0
*/
//感觉这个ClassPathMapperScanner扫描器好像和我们想看的差不多,直接找这个类
/**
* 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;
}
DEBUG一下,可以看到这个就是扫描Mapper接口的方法,返回@MapperScan注解的value
ok,重点来了@MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class) //通过import注解,添加进spring ioc容器
public @interface MapperScan
//仅仅只是一个注解,直接看@import方法导入的类
/**
* 实现了ImportBeanDefinitionRegistrar接口,扫描该类的时候会执行registerBeanDefinitions方法
* spring提供的扩展点
*/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/**
* 获取 @MapperScan("xxxx")上的内容
*/
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
/**
* 扩展spring中ClassPathBeanDefinitionScanner类将类扫描成BeanDefinition
*/
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
/**
* 设置资源加载器,作用:扫描指定包下的class文件。
*/
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
/**
* 把MapperScan中的属性设置到ClassPathMapperScanner
*/
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(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
/**
* 设置SqlSessionFactory的名称
* 这也证实了使用的 sqlSession 是 sqlSessionTemplate
*/
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
//设置集合保存路径值
List<String> basePackages = new ArrayList<String>();
//拿到 @MpperScan("xxx") 中的value值,添加进basePackages中
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//拿到 @MpperScan("xxx")中的basePackages值,添加进basePackages中
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
// 拿到 @MpperScan("xxx") 中的basePackageClasses值,解析成该类的全限定名,添加进basePackages中
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 注册过滤器,过滤掉不符合规范的mapper
scanner.registerFilters();
/**
* 重点!!!!
* 扫描指定包
*/
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
//分析一下过滤器
public void registerFilters() {
//添加一个标识
boolean acceptAllInterfaces = true;
/**
* 对于annotationClass属性的处理
* 如果annotationClass不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,
* 而封装此属性的过滤器就是AnnotationTypeFilter。
* AnnotationTypeFilter保证在扫描对应Java文件时只接受标记有注解为annotationClass的接口。
*/
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
/**
* 对于markerInterface属性的处理
* 如果markerInterface不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,
* 而封装此属性的过滤器就是实现AssignableTypeFilter接口的局部类。
* 表示扫描过程中只有实现markerInterface接口的接口才会被接受。
*/
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
//默认对所有接口都扫描
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
/**
对于命名为package-info的Java文件,默认不作为逻辑实现接口,将其排除掉,
使用TypeFilter接口的局部类实现match方法。
从上面的函数我们看出,控制扫描文件Spring通过不同的过滤器完成,
这些定义的过滤器记录在了includeFilters和excludeFilters属性中。
*/
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
/**
* 调用父类的扫描器,也就是spring提供的扫描器
* 根据传入的路径,把路径下的class文件变成beanDefinition,注入beanDefinitionMap中
* 返回所有成功注入的beanDefinition集合
*/
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 {
//如果不为空,继续对beanDefinitions中的所有beanDefinition进行属性的修改
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//遍历所有的beanDefinition
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
/**
* 把beanDefinition的beanName设置为实例化所需构造器的参数
* 也就说当该beanDefinition进行实例化时,推断带参数的构造方法
*/
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
/**
* 原始的接口类型的bean替换成一个MapperFactoryBean的类型。
* 所以它会根据 MapperFactoryBean这个类来实例化对象
* 又因为该类实现了FactoryBean接口,所以会在spring ioc容器中创建两个实例
* xxMapper &xxMapper
*/
definition.setBeanClass(this.mapperFactoryBean.getClass());
//向beanDefinition添加属性 addToConfig=false
definition.getPropertyValues().add("addToConfig", this.addToConfig);
//定义一个标识
boolean explicitFactoryUsed = false;
//判断sqlSessionFactory是否为null,
if (this.sqlSessionFactory != null) {
//向beanDefinition添加属性 sqlSessionFactory = DefaultSqlSessionFactory definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}else if (this.sqlSessionTemplate != null) {
//向beanDefinition中添加属性 sqlSessionTemplat = SqlSessionTemplate
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
//修改标识
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
/**
* 设置注入模型为 BY_TYPE
* 进行属性赋值时populateBean(beanName, mbd, instanceWrapper);
* 会通过反射调用所有set方法进行赋值,通过类型从单例池找
*
* 扩展一下其他的注入模型
* AUTOWIRE_NO:不进行自动注入,只注入加了@Autowire注解的,先通过类型从单例池找.如果找到多个就根据名字选择一个
* AUTOWIRE_BY_NAME:也是通过set方法只不过,通过名字从单例池中找
* AUTOWIRE_CONSTRUCTOR:通过实例化时候的构造方法进行注入
*/
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
@MapperScan差不多了解了,把你规定的包路径下的所有类变成beanDefinition放到beanDefinitionMap中,并且设置他的beanClass为MapperFactoryBean而且它实现了MapperFactory,所以直接看这个类,就能清楚spring-mybatis的整合
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
/**
* 使用BY_TYPE类型,先拿到所有的属性,如果有get/set方法就证明该属性需要被注入
* 但是会经过过滤,过滤掉某些属性 1.Class类型 2.显示赋值了,所以下面这两个属性都不会赋值
* 但是别忘记此时还有父类的属性被注入
* private SqlSession sqlSession;
*
* 因为是By_Type注入模型,所以会调用父类所有setXX()方法,因为只设置了sqlSessionFactory的属性参数,所以实际上只会调用这一个setXx()方法
* public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
* if (!this.externalSqlSession) {
* this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
* }
* }
* //该方法由于大部分情况没有设置方法参数,所以不会执行
* public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
* this.sqlSession = sqlSessionTemplate;
* this.externalSqlSession = true;
* }
*/
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
/**
* 构造器,此时beanDefinition中已经设置了构造器输入参数
* 所以在通过反射调用构造器实例化时,一定会通过该构造方法去实例化对象
* 并且获取在BeanDefinition设置的构造器输入参数
* 也就是对应得每个Mapper接口的全限定类名(com.mapper.xxMapper)赋给mapperInterface属性
*/
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* 因为该类实现了 InitializingBean 接口,会在属性注入(@Autowired)之后、执行afterPropertiesSet方法
* afterPropertiesSet方法执行了checkDaoConfig方法
* 这个方法就是完成了方法和sqlSource建立映射
* 为什么要在属性注入之后才执行该方法
* 因为当属性注入完成之后,该对象的属性才有值,才能实现映射
*/
@Override
protected void checkDaoConfig() {
//不重要的代码直接删除了
/**
* 此时sqlSession是存在的,因为我们对beanDefinition设置了sqlSessionFactory属性
* 在实例化后的属性注入时,通过setSqlSessionFactory() 设值 sqlSessionTemplate
* sqlSessionTemplate对象也是一个单例,全局唯一,供所有的Mapper代理类使用
*
* getConfiguration()通过sqlSessionTemplate.DefaultSqlSessionFactory拿到的Configuration此时仅仅完成初始化
*/
Configuration configuration = getSqlSession().getConfiguration();
//addToConfig一定为true && 一定不存在取反一定为true
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
//往 MappedStatement 中添加了全限定名+方法名作为key,sqlSource作为value代理对象
configuration.addMapper(this.mapperInterface);
}
}
@Override
public T getObject() throws Exception {
/**
* 获取父类setSqlSessionFactory方法中创建的 SqlSessionTemplate
* 通过SqlSessionTemplate获取mapperInterface的代理类
* 也就是说不会在单例池中存在,singletonObjects只存在MapperFactoryBean这个类,不存在Mapper接口的代理类
* 所以说该方法只会执行一次,除非清除掉缓存,才会再次执行
*/
return getSqlSession().getMapper(this.mapperInterface);
}
先看一下this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);是个什么东西
/**
* SqlSessionTemplate在内部访问数据库时,其实是委派给sqlSessionProxy来执行数据库操作的,
* SqlSessionTemplate不是自身重新实现了一套mybatis数据库访问的逻辑。
*
* SqlSessionTemplate通过静态代理机制来提供SqlSession接口的行为,
* 即实现SqlSession接口来获取SqlSession的所有方法;SqlSessionTemplate的定义如下:标准的静态代理实现模式,
* 即实现SqlSession接口并在内部包含一个SqlSession接口实现类引用sqlSessionProxy。
*/
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;
//创建了一个代理对象,重点看一下用来增强的方法,至于为什么JDK动态代理自己去看
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 开启 sqlSession,
* 获取一个sqlSession来执行proxy的method对应的SQL,
* 每次调用都获取创建一个sqlSession线程局部变量,故不同线程相互不影响,
* 在这里实现了SqlSessionTemplate的线程安全性
*
*/
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
/**
* 直接通过新创建的SqlSession反射调用method,在下面的知识就是Mybatis源码了
*/
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
/**
* 如果当前操作没有在一个Spring事务中,则手动commit一下
* 如果当前业务没有使用@Transation,那么每次执行了Mapper接口的方法直接commit
* 还记得我们前面讲的Mybatis的一级缓存吗,这里一级缓存不能起作用了,
* 因为每执行一个Mapper的方法,sqlSession都提交了
* sqlSession提交,会清空一级缓存
*/
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 #22
closeSqlSession(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) {
/**
* session关闭,所以session是交给了spring管理的
* 我们手动开关session是没什么作用的。这样一级缓存就失效了
*/
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
在看一下是如何建立映射关系的configuration.addMapper(this.mapperInterface);
//看一下如何添加一个映射
public <T> void addMapper(Class<T> type) {
//mapper必须是接口!才会添加
if (type.isInterface()) {
if (hasMapper(type)) {
//如果重复添加了,报错
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
/**
* 实现了往 MapperStatement 中添加数据,至于这是干啥的,Mybatis源码自己看,反正方法和具体的sql语句都保存在这个集合中
*/
parser.parse();
loadCompleted = true;
} finally {
//如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//加载xml资源
loadXmlResource();
//添加加载资源
configuration.addLoadedResource(resource);
//设置当前命名空间
assistant.setCurrentNamespace(type.getName());
//解析缓存
parseCache();
//解析缓存引用
parseCacheRef();
Method[] methods = type.getMethods();
/**
* 循环所有方法
*/
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
/**
* 解析方法中的sql注解语句
*/
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
最后看一下MapperFactory.getObject()方法
@Override
public <T> T getMapper(Class<T> type) {
/**
* 通过sqlSessionFactory创建代理类,sqlSessionTemplate作为代理类的模板,并且该类还有一个内部类
* InvocationHandler(),作为对每个方法的增强,通过拿到方法上的注解 进行具体的执行操作
*
* 实现SqlSession接口,单例、线程安全,使用spring的事务管理器的sqlSession,
* 具体的SqlSession的功能,则是通过内部包含的sqlSessionProxy来来实现,这也是静态代理的一种实现。
* 同时内部的sqlSessionProxy实现InvocationHandler接口,则是动态代理的一种实现,而线程安全也是在这里实现的。
* 注意mybatis默认的sqlSession不是线程安全的,需要每个线程有一个单例的对象实例。
* SqlSession的主要作用是提供SQL操作的API,执行指定的SQL语句,mapper需要依赖SqlSession来执行其方法对应的SQL
*
* Configuration就像是Mybatis的总管,Mybatis的所有配置信息都存放在这里
* 比如:结果集处理器,是否开启缓存,驼峰表示法,slf4j,各种缓存等等
* 此外,它还提供了设置这些配置信息的方法。
* Configuration可以从配置文件里获取属性值,也可以通过程序直接设置。
*
* 他把自己传进去了重点
*/
return getConfiguration().getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
/**
* 把sqlSessionTemplate当参数传进去
*/
return mapperRegistry.getMapper(type, sqlSession);
}
//返回代理类
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 {
/**
* 创建代理类
* 重点!!!!!!!
* 最外层的动态代理是MapperProxy,然后该代理类封装了sqlSessionTemplate
* 当执行完 MapperProxy 的 invoke方法后,还要执行 sqlSessionTemplate的sql执行语句
* 又因为sql执行语句调用了sqlSessionProxy的sql执行语句,
* 而sqlSessionProxy是一个代理类,所有先执行他的invoke方法,
* 最终执行完毕
*/
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//用JDK自带的动态代理生成映射器
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//这就是代理完成后的某一个增强方法,明显看出它是通过sqlSessionProxy代理类实现的SQL语句执行
public int update(String statement) {
return this.sqlSessionProxy.update(statement);
}
OK第一遍不理解很正常,跟着DEBUG多走几遍就能明白,最后小总结一下
/**
* 这里大概讲一下Mapper代理类调用方法执行逻辑:
*
* 1、SqlSessionTemplate生成Mapper代理类时,将本身传进去做为Mapper代理类的属性,调用Mapper代理类的方法时,
* 最后会通过SqlSession类执行,也就是调用SqlSessionTemplate中的方法。
*
* 2、SqlSessionTemplate中操作数据库的方法中又交给了sqlSessionProxy这个代理类去执行,
* 那么每次执行的方法都会回调其SqlSessionInterceptor这个InvocationHandler的invoke方法
*
* 3、在invoke方法中,为每个线程创建一个新的SqlSession,并通过反射调用SqlSession的method。
* 这里sqlSession是一个线程局部变量,不同线程相互不影响,实现了SqlSessionTemplate的线程安全性
*
* 4、如果当前操作并没有在Spring事务中,那么每次执行一个方法,都会提交,相当于数据库的事务自动提交,
* Mysql的一级缓存也将不可用
*/
/**
* mybatis和spring整合后一级缓存失效的问题
*
* 一级缓存: MyBatis会创建出一个SqlSession对象表示一次数据库会话。
* 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,
* 由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
* 为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,
* 将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,
* 会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了,
* 当sqlSession关闭的时候就会清除缓存
*
*为什么spring-mybatis会手动关闭一级缓存
* 1.spring把所有的bean都放在spring ioc容器中,他并没有提供给用户 applicationContext
* 所以用户无法拿到Mapper对象,更拿不到sqlSession,也就无法做到手动关闭
* mybatis提供了api使用户能随时关闭
* 2.mybatis的一级缓存很鸡肋,因为一级缓存是存在每个线程中,每次的请求都是一个新的线程
* 那么这个一级缓存可以说是完全没有用,但是如果他是进程级别的,就很牛b了,能节省大量的时间
*/