SqlSessionFactory 生成
SqlSessionFactory 的生成是通过 SqlSessionFactoryBean 生成的。在讲源码之前,我们先看看 Mybatis 在 Springboot 中的配置:
@Bean(name = "dataSource")
@ConfigurationProperties(prefix = "spring.datasource.cashier")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(Boolean.TRUE);
configuration.setUseGeneratedKeys(Boolean.TRUE);
sessionFactoryBean.setConfiguration(configuration);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:yongda.mapper/*.xml"));
return sessionFactoryBean.getObject();
}
上面配置中,将数据源DataSource
,配置信息Configuration
等属性赋值给SqlSessionFactoryBean
,然后调用sessionFactoryBean.getObject()
方法,接下来我们看看这个方法做了什么。
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
直接返回SqlSessionFactory
对象,其初始化操作是在afterPropertiesSet()
方法中,里面会调用buildSqlSessionFactory()
给SqlSessionFactory
赋值:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
......
return this.sqlSessionFactoryBuilder.build(configuration);
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我们可以看到,生成了DefaultSqlSessionFactory
作为SqlSessionFactory
。
MapperScannerRegistrar
MapperScannerRegistrar,Mappper 接口的扫描配置类。
我们程序一般会使用@MapperScan(basePackages = "xxx")
来指定需要扫描的 Mapper 接口路径。
我们可以看其结构:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise
* annotation declarations e.g.:
* {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
* @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
*/
String[] value() default {};
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces
* with at least one method will be registered; concrete classes will be
* ignored.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* <p>Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* The {@link BeanNameGenerator} class to be used for naming detected components
* within the Spring container.
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have
* the specified annotation.
* <p>
* Note this can be combined with markerInterface.
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have
* the specified interface class as a parent.
* <p>
* Note this can be combined with annotationClass.
*/
Class<?> markerInterface() default Class.class;
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
*/
String sqlSessionTemplateRef() default "";
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
*/
String sqlSessionFactoryRef() default "";
/**
* Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
*
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
这里使用了@Import
注解来引入配置类,相当于 Spring 中的 <import/>标签。@Import
注解的参数可以是一个 @Configuration 配置类,也可以是一个ImportSelector
接口,也可以是ImportBeanDefinitionRegistrar
接口。如果是一个 @Configuration 配置类,会将类中 @Bean 修饰的 bean 注册到 IOC 容器;如果是ImportSelector
接口的实现类,那就会根据实现的逻辑对 @Configuration 配置类进行筛选;如果是一个ImportBeanDefinitionRegistrar
接口实现类,那么也会根据该实现类的逻辑来创建 Bean。
我们看引入的MapperScannerRegistrar
结构:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
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"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
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));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
我们发现,MapperScannerRegistrar
是 ImportBeanDefinitionRegistrar
的实现类,而ImportBeanDefinitionRegistrar
接口的作用是,它允许我们直接通过BeanDefinitionRegistry 对象注册 bean。其提供的 API 为 ImportBeanDefinitionRegistrar#registerBeanDefinitions
,而此 API 被调用的时机是在解析导入的@Configuration
配置类的核心方法ConfigurationClassPostProcessor#processConfigBeanDefinitions
中,具体源码就不深入分析,感兴趣的同学自行了解。
接下来我们看MapperScannerRegistrar
注册 Mapper 接口的流程。首先根据标注的@MapperScan
获取 basePackage,之后通过ClassPathMapperScanner
去扫描包,获取所有 Mapper 接口类的BeanDefinition
,之后具体配置,设置 beanClass 为MapperFactoryBean
,设置 sqlSessionFactory 属性为上面生成的DefaultSqlSessionFactory
,通过ClassPathBeanDefinitionScanner
父类进行 bean 注册,自动注入的时候,就会调用MapperFactoryBean
的getObject
方法获取实际类型的实例。
如果是与 Spring 集成的话,配置文件如下:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="xxx" />
</bean>
这里的MapperScannerConfigurer
实现的功能和MapperScannerRegistrar
是一致的,我就不累述了。
MapperFactoryBean 父类 SqlSessionDaoSupport-持久层创建
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
//包装成 SqlSessionTemplate 对象
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
上面我们已经介绍了,MapperScannerRegistrar
会给扫描到的 Mapper 替换为 MapperFactoryBean,并且会给其设置 sqlSessionFactory 属性。mybatis 持久层的操作都会被包装成SqlSessionTemplate
对象。
SqlSessionTemplate 构造函数
基本都是由SqlSessionFactory
作为入参:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
//默认的 ExecutorType 为 ExecutorType.SIMPLE
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
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;
//对数据库的操作也会包装成代理的形式,所有的 CRUD 操作则都由 sqlSessionProxy 对象来完成
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
这里代理了SqlSession
,后续所有的 CRUD 操作都是基于SqlSession
的。
MapperFactoryBean
它实现了FactoryBean
,故是一个工厂 Bean,在初始化阶段,返回的实例类型就是getObject()
返回值类型:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession 得到的是之前创建的SqlSessionTemplate
,于是我们直接看SqlSessionTemplte.getMapper(type)
方法:
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
SqlSessionTemplate
什么都没做,把包袱甩给了Configuration
, 接下来就看看Configuration
。源码如下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
Configuration
不要这烫手的山芋,接着甩给了MapperRegistry
, 那咱看看MapperRegistry
。 源码如下:
/**
* 烂活净让我来做了,没法了,下面没人了,我不做谁来做
* @param type
* @param sqlSession
* @return
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//能偷懒的就偷懒,俺把粗活交给 MapperProxyFactory 去做
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
是个苦B的人,粗活最终交给它去做了。咱们看看源码:
/**
* 别人虐我千百遍,我待别人如初恋
* @param mapperProxy
* @return
*/
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//动态代理我们写的 dao 接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
通过以上的动态代理,为每个 dao 接口,生成一个MapperProxy
代理,咱们就可以方便地使用 dao 接口啦。至此,程序启动阶段关于 Mybatis 做的事情就完事了,接下来,我们看看执行一条 SQL 时的处理流程。
MapperProxy
当调用 dao 层的方法时,会走其代理类 MapperProxy
的invoke
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//二话不说,主要交给 MapperMethod 自己去管
return mapperMethod.execute(sqlSession, args);
}
MapperMethod:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
看着代码不少,不过其实就是先判断 CRUD 类型,然后根据类型去选择到底执行sqlSession
中的哪个方法,绕了一圈,又转回sqlSession
了,这里的sqlSession
就是程序在启动阶段创建的sqlSessionTemplate
。当sqlSession
调用具体的 CURD 方法时,会调用其代理类SqlSessionInterceptor.invoke()
方法:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过 SqlSessionUtils.getSqlSession() 获得真实处理 CRUD 的持久层,默认为 DefaultSqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//执行 CRUD 操作
Object result = method.invoke(sqlSession, args);
//非事务处理的数据操作需要强制 commit
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
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) {
//关闭 sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
创建sqlSession
的步骤是通过SqlSessionUtils.getSqlSession()
来完成的,默认生成的是DefaultSqlSession
,下面我们拿 SELECT 方式为例,直接进入DefaultSqlSession.selectList()
:
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 将 sql 语句转换为 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//CRUD 实际上是交给 Excetor 去处理
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
至此,sql 执行流程已经完毕,下面我们通过流程图来看看整个流程:
彩蛋
Mybatis 的二级缓存机制
一级缓存:基于 HashMap 的本地缓存,它的生命周期是和 SqlSession
一致的,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。
二级缓存:也是基于 HashMap 的本地缓存,不同在于其存储作用域为Mapper
级别的,如果多个SqlSession
之间需要共享缓存,则需要使用到二级缓存,默认不打开二级缓存。
开启二级缓存的方式:
MyBatis 配置文件中通过:
<settings>
<setting name = "cacheEnabled" value = "true" />
</settings>
还需要在 Mapper 的 xml 配置文件中加入<cache>
标签。
开启二级缓存之后的数据查询流程:二级缓存 -> 一级缓存 -> 数据库。
缓存更新机制:当某一个作用域(一级缓存SqlSession
/二级缓存Mapper
)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
由上面我们知道,SqlSession
是由SqlSessionUtils.getSqlSession()
来创建的:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
首先从SqlSessionHolder
中获取SqlSession
,如果SqlSessionHolder
中没有,则新建一个。所以,我们看sessionHolder(executorType, holder)
方法是如何获取SqlSession
的:
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
// 验证当前线程是否存在事务
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException(
"Cannot change the ExecutorType when there is an existing transaction");
}
holder.requested();
LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
session = holder.getSqlSession();
}
return session;
}
由上面可知,只有当前线程存在事务时,SqlSession
就会被复用,否则将新建一个。
网友评论