Mybaits 源码解析 (十一)----- 设计模式精妙使用:静态代理和动态代理结合使用:@MapperScan将Mapper接口生成代理注入到Spring
上一篇文章我们讲了SqlSessionFactoryBean,通过这个FactoryBean创建SqlSessionFactory并注册进Spring容器,这篇文章我们就讲剩下的部分,通过MapperScannerConfigurer将Mapper接口生成代理注入到Spring
扫描Mapper接口
我们上一篇文章介绍了扫描Mapper接口有两种方式,一种是通过bean.xml注册MapperScannerConfigurer对象,一种是通过@MapperScan("com.chenhao.mapper")注解的方式,如下
方式一:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.chenhao.mapper" />
</bean>
方式二:
@Configuration
@MapperScan("com.chenhao.mapper")
public class AppConfig {
@MapperScan
我们来看看@MapperScan这个注解
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
MapperScan使用@Import
将MapperScannerRegistrar
导入。
MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取MapperScan 注解,如@MapperScan("com.chenhao.mapper")
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 创建路径扫描器,下面的一大段都是将MapperScan 中的属性设置到ClassPathMapperScanner ,做的就是一个set操作
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
// 设置资源加载器,作用:扫描指定包下的class文件。
scanner.setResourceLoader(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(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
//设置SqlSessionFactory的名称
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
//获取配置的包路径,如com.chenhao.mapper
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 注册过滤器,作用:什么类型的Mapper将会留下来。
scanner.registerFilters();
// 扫描指定包
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
ClassPathMapperScanner
接着我们来看看扫描过程 scanner.doScan(StringUtils.toStringArray(basePackages));
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用父类进行扫描,并将basePackages下的class都封装成BeanDefinitionHolder,并注册进Spring容器的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做处理,额外设置一些属性
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
//遍历basePackages进行扫描
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 对象
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//将BeanDefinition对象注入spring的BeanDefinitionMap中,后续getBean时,就是从BeanDefinitionMap获取对应的BeanDefinition对象,取出其属性进行实例化Bean
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
我们重点看下第4行和第10行代码,第4行是调用父类的doScan方法,获取basePackages下的所有Class,并将其生成BeanDefinition,注入spring的BeanDefinitionMap中,也就是Class的描述类,在调用getBean方法时,获取BeanDefinition进行实例化。此时,所有的Mapper接口已经被生成了BeanDefinition。接着我们看下第10行,对生成的BeanDefinition做一些额外的处理。
processBeanDefinitions
上面BeanDefinition已经注入进Spring容器了,接着我们看对BeanDefinition进行哪些额外的处理
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // 设置definition构造器的输入参数为definition.getBeanClassName(),这里就是com.chenhao.mapper.UserMapper
// 那么在getBean实例化时,通过反射调用构造器实例化时要将这个参数传进去
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName())
// 修改definition对应的Class
// 看过Spring源码的都知道,getBean()返回的就是BeanDefinitionHolder中beanClass属性对应的实例
// 所以我们后面ac.getBean(UserMapper.class)的返回值也就是MapperFactoryBean的实例
// 但是最终被注入到Spring容器的对象的并不是MapperFactoryBean的实例,根据名字看,我们就知道MapperFactoryBean实现了FactoryBean接口
// 那么最终注入Spring容器的必定是从MapperFactoryBean的实例的getObject()方法中返回
definition.setBeanClass(this.mapperFactoryBean.getClass()); 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设置属性值sqlSessionFactory,那么在MapperFactoryBean实例化后,进行属性赋值时populateBean(beanName, mbd, instanceWrapper);,会通过反射调用sqlSessionFactory的set方法进行赋值
//也就是在MapperFactoryBean创建实例后,要调用setSqlSessionFactory方法将sqlSessionFactory注入进MapperFactoryBean实例
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
看第19行,将definition的beanClass属性设置为MapperFactoryBean.class,我们知道,在getBean的时候,会通过反射创建Bean的实例,也就是创建beanClass的实例,如下Spring的getBean的部分代码:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// 没有覆盖
// 直接使用反射实例化即可
if (!bd.hasMethodOverrides()) {
// 重新检测获取下构造函数
// 该构造函数是经过前面 N 多复杂过程确认的构造函数
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
// 获取已经解析的构造函数
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
// 如果为 null,从 class 中解析获取,并设置
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
//利用反射获取构造器
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
} // 通过BeanUtils直接使用构造器对象实例化bean
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// 生成CGLIB创建的子类对象
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
看到没,是通过bd.getBeanClass();从BeanDefinition中获取beanClass属性,然后通过反射实例化Bean,如上,所有的Mapper接口扫描封装成的BeanDefinition的beanClass都设置成了MapperFactoryBean,我们知道在Spring初始化的最后,会获取所有的BeanDefinition,并通过getBean创建所有的实例注入进Spring容器,那么意思就是说,在getBean时,创建的的所有Mapper对象是MapperFactoryBean这个对象了。
我们看下第13行处,设置了BeanDefinition构造器参数,那么当getBean中通过构造器创建实例时,需要将设置的构造器参数definition.getBeanClassName(),这里就是com.chenhao.mapper.UserMapper传进去。
还有一个点要注意,在第30行处,往BeanDefinition的PropertyValues设置了sqlSessionFactory,那么在创建完MapperFactoryBean的实例后,会对MapperFactoryBean进行属性赋值,也就是Spring创建Bean的这句代码,populateBean(beanName, mbd, instanceWrapper);,这里会通过反射调用MapperFactoryBean的setSqlSessionFactory方法将sqlSessionFactory注入进MapperFactoryBean实例,所以我们接下来重点看的就是MapperFactoryBean这个对象了。
MapperFactoryBean
接下来我们看最重要的一个类MapperFactoryBean
//继承SqlSessionDaoSupport、实现FactoryBean,那么最终注入Spring容器的对象要从getObject()中取
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true; public MapperFactoryBean() {
} //构造器,我们上一节中在BeanDefinition中已经设置了构造器输入参数
//所以在通过反射调用构造器实例化时,会获取在BeanDefinition设置的构造器输入参数
//也就是对应得每个Mapper接口Class
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
} }
//最终注入Spring容器的就是这里的返回对象
public T getObject() throws Exception {
//获取父类setSqlSessionFactory方法中创建的SqlSessionTemplate
//通过SqlSessionTemplate获取mapperInterface的代理类
//我们例子中就是通过SqlSessionTemplate获取com.chenhao.mapper.UserMapper的代理类
//获取到Mapper接口的代理类后,就把这个Mapper的代理类对象注入Spring容器
return this.getSqlSession().getMapper(this.mapperInterface);
} public Class<T> getObjectType() {
return this.mapperInterface;
} public boolean isSingleton() {
return true;
} public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return this.mapperInterface;
} public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
} public boolean isAddToConfig() {
return this.addToConfig;
}
} public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession; public SqlSessionDaoSupport() {
}
//还记得上一节中我们往BeanDefinition中设置的sqlSessionFactory这个属性吗?
//在实例化MapperFactoryBean后,进行属性赋值时,就会通过反射调用setSqlSessionFactory
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
//创建一个SqlSessionTemplate并赋值给sqlSession
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
} public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
} public SqlSession getSqlSession() {
return this.sqlSession;
} protected void checkDaoConfig() {
Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
我们看到MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean,那么getBean获取的对象是从其getObject()中获取,并且MapperFactoryBean是一个单例,那么其中的属性SqlSessionTemplate对象也是一个单例,全局唯一,供所有的Mapper代理类使用。
这里我大概讲一下getBean时,这个类的过程:
1、MapperFactoryBean通过反射调用构造器实例化出一个对象,并且通过上一节中definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName())设置的构造器参数,在构造器实例化时,传入Mapper接口的Class,并设置为MapperFactoryBean的mapperInterface属性。
2、进行属性赋值,通过上一节中definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);设置的属性值,在populateBean属性赋值过程中通过反射调用setSqlSessionFactory方法,并创建SqlSessionTemplate对象设置到sqlSession属性中。
3、由于MapperFactoryBean实现了FactoryBean,最终注册进Spring容器的对象是从getObject()方法中取,接着获取SqlSessionTemplate这个SqlSession调用getMapper(this.mapperInterface);生成Mapper接口的代理对象,将Mapper接口的代理对象注册进Spring容器
至此,所有com.chenhao.mapper中的Mapper接口都生成了代理类,并注入到Spring容器了。接着我们就可以在Service中直接从Spring的BeanFactory中获取了,如下
SqlSessionTemplate
还记得我们前面分析Mybatis源码时,获取的SqlSession实例是什么吗?我们简单回顾一下
/**
* ExecutorType 指定Executor的类型,分为三种:SIMPLE, REUSE, BATCH,默认使用的是SIMPLE
* TransactionIsolationLevel 指定事务隔离级别,使用null,则表示使用数据库默认的事务隔离界别
* autoCommit 是否自动提交,传过来的参数为false,表示不自动提交
*/
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);
// 创建Executor,即执行器
// 它是真正用来Java和数据库交互操作的类,后面会展开说。
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象返回,其实现了SqlSession接口
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
大家应该还有印象,就是上面的DefaultSqlSession,那上一节的SqlSessionTemplate是什么鬼???我们来看看
// 实现SqlSession接口,单例、线程安全,使用spring的事务管理器的sqlSession,
// 具体的SqlSession的功能,则是通过内部包含的sqlSessionProxy来来实现,这也是静态代理的一种实现。
// 同时内部的sqlSessionProxy实现InvocationHandler接口,则是动态代理的一种实现,而线程安全也是在这里实现的。
// 注意mybatis默认的sqlSession不是线程安全的,需要每个线程有一个单例的对象实例。
// SqlSession的主要作用是提供SQL操作的API,执行指定的SQL语句,mapper需要依赖SqlSession来执行其方法对应的SQL。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
// 一个代理类,由于SqlSessionTemplate为单例的,被所有mapper,所有线程共享,
// 所以sqlSessionProxy要保证这些mapper中方法调用的线程安全特性:
// sqlSessionProxy的实现方式主要为实现了InvocationHandler接口实现了动态代理,
// 由动态代理的知识可知,InvocationHandler的invoke方法会拦截所有mapper的所有方法调用,
// 故这里的实现方式是在invoke方法内部创建一个sqlSession局部变量,从而实现了每个mapper的每个方法调用都使用
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
} public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
} public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
} public SqlSessionFactory getSqlSessionFactory() {
return this.sqlSessionFactory;
} public ExecutorType getExecutorType() {
return this.executorType;
} public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
return this.exceptionTranslator;
} public <T> T selectOne(String statement) {
//由真实的对象sqlSessionProxy执行查询
return this.sqlSessionProxy.selectOne(statement);
} public <T> T selectOne(String statement, Object parameter) {
//由真实的对象sqlSessionProxy执行查询
return this.sqlSessionProxy.selectOne(statement, parameter);
} public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
//由真实的对象sqlSessionProxy执行查询
return this.sqlSessionProxy.selectMap(statement, mapKey);
} public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
//由真实的对象sqlSessionProxy执行查询
return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);
} public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
//由真实的对象sqlSessionProxy执行查询
return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
} public <T> Cursor<T> selectCursor(String statement) {
return this.sqlSessionProxy.selectCursor(statement);
} public <T> Cursor<T> selectCursor(String statement, Object parameter) {
return this.sqlSessionProxy.selectCursor(statement, parameter);
} public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
} public <E> List<E> selectList(String statement) {
return this.sqlSessionProxy.selectList(statement);
} public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
} public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
} public void select(String statement, ResultHandler handler) {
this.sqlSessionProxy.select(statement, handler);
} public void select(String statement, Object parameter, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, handler);
} public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
} public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
} public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
} public int update(String statement) {
return this.sqlSessionProxy.update(statement);
} public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
} public int delete(String statement) {
return this.sqlSessionProxy.delete(statement);
} public int delete(String statement, Object parameter) {
return this.sqlSessionProxy.delete(statement, parameter);
} public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
} public void commit() {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
} public void commit(boolean force) {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
} public void rollback() {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
} public void rollback(boolean force) {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
} public void close() {
throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
} public void clearCache() {
this.sqlSessionProxy.clearCache();
} public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
} public Connection getConnection() {
return this.sqlSessionProxy.getConnection();
} public List<BatchResult> flushStatements() {
return this.sqlSessionProxy.flushStatements();
} public void destroy() throws Exception {
} private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
} unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
} throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
} } return unwrapped;
}
}
}
我们看到SqlSessionTemplate实现了SqlSession接口,那么Mapper代理类中执行所有的数据库操作,都是通过SqlSessionTemplate来执行,如上我们看到所有的数据库操作都由对象sqlSessionProxy执行查询
静态代理的使用
SqlSessionTemplate在内部访问数据库时,其实是委派给sqlSessionProxy来执行数据库操作的,SqlSessionTemplate不是自身重新实现了一套mybatis数据库访问的逻辑。
SqlSessionTemplate通过静态代理机制来提供SqlSession接口的行为,即实现SqlSession接口来获取SqlSession的所有方法;SqlSessionTemplate的定义如下:标准的静态代理实现模式,即实现SqlSession接口并在内部包含一个SqlSession接口实现类引用sqlSessionProxy。那我们就要看看sqlSessionProxy这个SqlSession,我们先来看看SqlSessionTemplate的构造方法
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
动态代理的使用
不是吧,又使用了动态代理??真够曲折的,那我们接着看 new SqlSessionTemplate.SqlSessionInterceptor() 这个InvocationHandler
private class SqlSessionInterceptor implements InvocationHandler {
//很奇怪,这里并没有真实目标对象?
private SqlSessionInterceptor() {
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取一个sqlSession来执行proxy的method对应的SQL,
// 每次调用都获取创建一个sqlSession线程局部变量,故不同线程相互不影响,在这里实现了SqlSessionTemplate的线程安全性
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped;
try {
//直接通过新创建的SqlSession反射调用method
//这也就解释了为什么不需要目标类属性了,这里每次都会创建一个
Object result = method.invoke(sqlSession, args);
// 如果当前操作没有在一个Spring事务中,则手动commit一下
// 如果当前业务没有使用@Transation,那么每次执行了Mapper接口的方法直接commit
// 还记得我们前面讲的Mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个Mapper的方法,sqlSession都提交了
// sqlSession提交,会清空一级缓存
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
} unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
} throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
} }
return unwrapped;
}
}
这里大概讲一下Mapper代理类调用方法执行逻辑:
1、SqlSessionTemplate生成Mapper代理类时,将本身传进去做为Mapper代理类的属性,调用Mapper代理类的方法时,最后会通过SqlSession类执行,也就是调用SqlSessionTemplate中的方法。
2、SqlSessionTemplate中操作数据库的方法中又交给了sqlSessionProxy这个代理类去执行,那么每次执行的方法都会回调其SqlSessionInterceptor这个InvocationHandler的invoke方法
3、在invoke方法中,为每个线程创建一个新的SqlSession,并通过反射调用SqlSession的method。这里sqlSession是一个线程局部变量,不同线程相互不影响,实现了SqlSessionTemplate的线程安全性
4、如果当前操作并没有在Spring事务中,那么每次执行一个方法,都会提交,相当于数据库的事务自动提交,Mysql的一级缓存也将不可用
接下来我们还要看一个地方,invoke中是如何创建SqlSession的?
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
//通过TransactionSynchronizationManager内部的ThreadLocal中获取
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if(session != null) {
return session;
} else {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
//这里我们知道实际上是创建了一个DefaultSqlSession
session = sessionFactory.openSession(executorType);
//将创建的SqlSession对象放入TransactionSynchronizationManager内部的ThreadLocal中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
通过sessionFactory.openSession(executorType)实际创建的SqlSession还是DefaultSqlSession。如果读过我前面Spring源码的朋友,肯定知道TransactionSynchronizationManager这个类,其内部维护了一个ThreadLocal的Map,这里同一线程创建了SqlSession后放入ThreadLocal中,同一线程中其他Mapper接口调用方法时,将会直接从ThreadLocal中获取。
Mybaits 源码解析 (十一)----- 设计模式精妙使用:静态代理和动态代理结合使用:@MapperScan将Mapper接口生成代理注入到Spring的更多相关文章
- Mybaits 源码解析 (三)----- Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
上一篇我们讲解到mapperElement方法用来解析mapper,我们这篇文章具体来看看mapper.xml的解析过程 mappers配置方式 mappers 标签下有许多 mapper 标签,每一 ...
- Mybaits 源码解析 (四)----- SqlSession的创建过程(看懂框架源码再也不用死记硬背面试题)
SqlSession是mybatis的核心接口之一,是myabtis接口层的主要组成部分,对外提供了mybatis常用的api.myabtis提供了两个SqlSesion接口的实现,常用的实现类是De ...
- Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问 上一篇文章我们获取到了DefaultSqlSe ...
- Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析
像Mybatis.Hibernate这样的ORM框架,封装了JDBC的大部分操作,极大的简化了我们对数据库的操作. 在实际项目中,我们发现在一个事务中查询同样的语句两次的时候,第二次没有进行数据库查询 ...
- Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...
- Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗 ...
- Mybaits 源码解析 (六)----- 全网最详细:Select 语句的执行过程分析(上篇)(Mapper方法是如何调用到XML中的SQL的?)
上一篇我们分析了Mapper接口代理类的生成,本篇接着分析是如何调用到XML中的SQL 我们回顾一下MapperMethod 的execute方法 public Object execute(SqlS ...
- Mybaits 源码解析 (七)----- Select 语句的执行过程分析(下篇)(Mapper方法是如何调用到XML中的SQL的?)全网最详细,没有之一
我们上篇文章讲到了查询方法里面的doQuery方法,这里面就是调用JDBC的API了,其中的逻辑比较复杂,我们这边文章来讲,先看看我们上篇文章分析的地方 SimpleExecutor public & ...
- Mybaits 源码解析 (八)----- 全网最详细,没有之一:结果集 ResultSet 自动映射成实体类对象(上篇)
上一篇文章我们已经将SQL发送到了数据库,并返回了ResultSet,接下来就是将结果集 ResultSet 自动映射成实体类对象.这样使用者就无需再手动操作结果集,并将数据填充到实体类对象中.这可大 ...
随机推荐
- Spring Boot 2.X(五):MyBatis 多数据源配置
前言 MyBatis 多数据源配置,最近在项目建设中,需要在原有系统上扩展一个新的业务模块,特意将数据库分库,以便减少复杂度.本文直接以简单的代码示例,如何对 MyBatis 多数据源配置. 准备 创 ...
- Android中内存泄露与如何有效避免OOM总结
一.关于OOM与内存泄露的概念 我们在Android开发过程中经常会遇到OOM的错误,这是因为我们在APP中没有考虑dalvik虚拟机内存消耗的问题. 1.什么是OOM OOM:即OutOfMemoe ...
- ElasticSearch Cardinality Aggregation聚合计算的误差
使用ES不久,今天发现生产环境数据异常,其使用的ES版本是2.1.2,其它版本也类似.通过使用ES的HTTP API进行查询,发现得到的数据跟javaClient API 查询得到的数据不一致,于是对 ...
- 设计模式 - 动态代理原理及模仿JDK Proxy 写一个属于自己的动态代理
本篇文章代码内容较多,讲的可能会有些粗糙,大家可以选择性阅读. 本篇文章的目的是简单的分析动态代理的原理及模仿JDK Proxy手写一个动态代理以及对几种代理做一个总结. 对于代理模式的介绍和讲解,网 ...
- web 前端优化-戈多编程
大家好,我是戈多,从事web开发工作接近三年了,今天来归纳下web前端优化的解决方案(码农搬砖工,来自各网络汇总) 1.减少Http请求 http请求越多,那么消耗的时间越多,如果在加上网络很糟糕,那 ...
- CS184.1X 计算机图形学导论L3V2和L3V3(部分)
组合变换 连接矩阵的优点是可以使用这些矩阵单独操作. 多个变换依然是一个矩阵. 连接矩阵不可交换,因为矩阵乘法不具有交换性. X3=RX2 X2=SX1 X3=R(SX1)=(RS)X1 X3≠SRX ...
- 分享:JS视频在线视频教程
作者说明 (1)JS说明 JS是非常重要的一门语言,但是,我们对JS的认识似乎仍然停留在“hello word”或者“alert”的观念上.其实,JS发展到现在已经非常的成熟,功能也非常的强大,因此, ...
- 计算机网络知识点总结2:IP协议(IPV4)
一.Internet网络是一种数据报网络(另一种是虚电路网络,用于ATM等),主要功能是路由和转发. 二.IP数据报(分组)格式(IPV4版本) 首部 描述 版本号(4bit) 描述IP协议的版本号, ...
- Cocos Creator 通用框架设计 —— 资源管理
如果你想使用Cocos Creator制作一些规模稍大的游戏,那么资源管理是必须解决的问题,随着游戏的进行,你可能会发现游戏的内存占用只升不降,哪怕你当前只用到了极少的资源,并且有使用cc.loade ...
- STM32串口IAP分享
什么是IAP? IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通 ...