Mybatis源码解析(一) —— mybatis与Spring是如何整合的?
Mybatis源码解析(一) —— mybatis与Spring是如何整合的?
从大学开始接触mybatis到现在差不多快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,比如:
它是如何加载各种mybatis相关的xml?
它是如何仅仅通过一个Mapper接口 + Mappe.xml实现数据库操作的(尽管很多人可能都清楚是通过代理实现,但面试时一旦深入询问:比如Mapper的代理类名是什么?是通过JDK还是cglib实现?)?
在同一个方法中,Mybatis多次请求数据库,是否要创建多个SqlSession会话?
它与Spring是如何适配(整合)的?
在Spring中是如何保障SqlSession的生命周期的?
等等一系列的问题。。。
如果以上问题你自认为无法回答,或者说了解一些,那么就从现在开始,我们来一一揭开这层面纱。
一、Mybatis:最简单测试Demo
相信只要用过Mybatis的同学看到下面的代码一定不会陌生,如果不清楚的可以看下官网文档
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 1、目前流行方式
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(101);
}
// 2、以前流行方式
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = sqlSession.selectOne("xxx.UserMapper.selectById", "101");;
}
示列代码演示了Mybatis进行一次数据库操作的过程,大致分为(针对目前流行方式,其实以前的使用和目前流行的使用方式实现原理一样):
1、 通过 SqlSessionFactoryBuilder 将读取到的配置资源 build 生成 SqlSessionFactory
2、 通过 SqlSessionFactory 的 openSession() 获取到 SqlSession
3、 通过 SqlSession 获取到 Mapper的代理对象(MapperProxy)
4、 通过 Mapper 进行 数据库请求操作
SqlSession、SqlSessionFactory、SqlSessionFactoryBuilder
我们可以轻易的发现每次去请求数据库操作都需要通过 SqlSessionFactory 去获取到 SqlSession,而 SqlSessionFactory 是通过 SqlSessionFactoryBuilder 构造出来的, 并且最后请求操作完成后都关闭了SqlSession。因此,不难得出:
- SqlSessionFactory 一个应用程序中最好只有1个,即单列。
- SqlSessionFactoryBuilder 只有一个作用: 创建 SqlSessionFactory对象。
- 一个SqlSession应该仅存活于一个业务请求中,也可以说一个SqlSession对应一次数据库会话,它不是永久存活的,每次访问数据库时都需要创建它,并且访问完成后都必须执行会话关闭
针对这3个类以及mapper的作用域(Scope)和生命周期的描述,个人觉得官方文档写得很清楚:
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。 换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。 这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架来使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。
映射器实例(Mapper实例)
映射器是一些由你创建的、绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的。因此从技术层面讲,任何映射器实例的最大作用域是和请求它们的 SqlSession 相同的。尽管如此,映射器实例的最佳作用域是方法作用域。 也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可丢弃。 并不需要显式地关闭映射器实例,尽管在整个请求作用域保持映射器实例也不会有什么问题,但是你很快会发现,像 SqlSession 一样,在这个作用域上管理太多的资源的话会难于控制。 为了避免这种复杂性,最好把映射器放在方法作用域内。就像示列代码一样。
如果SqlSession是注入的,那么映射器实例也可通过依赖注入,并且可忽略其生命周期。
二、Mybatis-Spring:将MyBatis代码无缝地整合到Spring
前面是学习mybatis常看到的一种代码,但缺点也很明显: 每次请求都得创建SqlSession,并且Mapper的代理类是通过SqlSession获取(说明耦合度很高),也就意味着每次请求都得创建一个新的Mapper代理类。为了整合Spring,并且解决前面问题,所以Mybatis-Spring 子项目来袭。
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean中。
上面是 Mybatis-Spring的官方介绍,其中 允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean中 是我们本次解析的关键点。那么开始分析吧!
SqlSessionFactoryBean 、 MapperScannerConfigurer
在Spring项目中应用了Mybatis都会有下面的2个bean配置,这2个配置就是实现xml加载、mapper和SqlSession注入的起始配置。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="xxx.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
一、 SqlSessionFactoryBean : 加载xml及build SqlSessionFactory对象
从配置中我们可以看的 SqlSessionFactoryBean 配置了数据源、mapper的xml路径、mybatis-config的xml路径。因此,不难想象,SqlSessionFactoryBean 内部实现了xml配置文件的加载及SqlSessionFactory对象的创建。我们来看下 SqlSessionFactoryBean继承关系图形:
在继承关系图中,我们发现了 InitializingBean、FactoryBean 的身影,可能清楚这个的同学,大概已经猜到了肯定有 afterPropertiesSet() 来创建 SqlSessionFactory 对象 和 getObject() 来获取 SqlSessionFactory 对象 。 话不多说,先看下getObject()实现:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
getObject()相对简单,我们都知道FactoryBean子类都是通过getObject()来获取到实际的Bean对象,这里也就是SqlSessionFactory。从源码中我们看到当 sqlSessionFactory为null会去调用 afterPropertiesSet(),所以 SqlSessionFactory 肯定是由 afterPropertiesSet() 来实现创建的。继续看afterPropertiesSet()实现:
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
afterPropertiesSet() 内部首先 验证了 dataSource 和 sqlSessionFactoryBuilder 部位null,最后调用 buildSqlSessionFactory()方法获取到 SqlSessionFactory 对象,并赋值到类字段属性 sqlSessionFactory 。 继续查看buildSqlSessionFactory()源码:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// 省略了 SqlSessionFactoryBean 的属性(比如:ObjectFactory )赋值到 Configuration 对象中的操作
// 1 Configuration : Mybatis的核心类之一,主要存放读取到的xml数据,包括mapper.xml
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
// 2 创建 xmlConfigBuilder 对象 : 用于解析 mybatis-config.xml 数据
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (xmlConfigBuilder != null) {
try {
// 3 XmlConfigBuilder 解析方法执行
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 4 创建 XMLMapperBuilder 对象 : 用于解析 mapper.xml 数据
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
}
// 5 通过 SqlSessionFactoryBuilder bulid SqlSessionFactory 对象
return this.sqlSessionFactoryBuilder.build(configuration);
}
整个 buildSqlSessionFactory() 源码主要有以下几个重要的点:
1、 XMLConfigBuilder ,通过调用其 parse() 方法来 解析 mybatis-config.xml 配置(如果 配置有 mapper.xml ,其会通过 XMLMapperBuilder 进行解析加载),并将解析的数据赋值到 Configuration(Mybatis的核心类之一,主要存放读取到的xml数据,包括mapper.xml,该类贯穿整个mybatis,足以见得其重要性)
2、 XMLMapperBuilder : 通过调用其 parse() 方法来 解析 mapper.xml 配置, 并将解析的数据赋值到 Configuration
3、 将存放有解析数据的 Configuration 作为 sqlSessionFactoryBuilder.build() 参数,创建 sqlSessionFactory 对象。
至此
二、 MapperScannerConfigurer :扫描Mapper接口路径,将 Mapper 偷梁换柱成 MapperFactoryBean
MapperScannerConfigurer 是 mybatis-spring 项目中为了实现方便加载Mapper接口,以及将 Mapper 偷梁换柱成 MapperFactoryBean。查看 MapperScannerConfigurer 源码,先看下其继承关系图:
从中我们其继承了 BeanDefinitionRegistryPostProcessor 接口,熟悉Spring 的同学应该 已经大致想到了 其如何将 Mapper 偷梁换柱成 MapperFactoryBean 了。话不多说,我们来看看 MapperScannerConfigurer 是如何实现 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
我们可以发现整个方法内部其实就是通过 ClassPathMapperScanner 的 scan() 方法,查看 scan() 实现,发现其内部调用了关键方法 doScan(),那么我们来看下 doScan() 方法实现:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 1、调用父类 ClassPathBeanDefinitionScanner的 doScan方法 加载路径下所有的mapper接口生成对应的 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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 2、 设置 被代理的 Bean(也就是Mapper) 的class信息
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
// 3、 偷梁换柱成 MapperFactoryBean
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 4、 设置 sqlSessionFactory
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;
}
// 5、 设置 sqlSessionTemplate
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
整个方法分为3个部分:
1、 调用父类 ClassPathBeanDefinitionScanner的 doScan()方法 加载路径下所有的mapper接口生成对应的 BeanDefinition
2、 通过definition.setBeanClass(MapperFactoryBean.class) 偷梁换柱成 MapperFactoryBean
3、 通过 definition.getPropertyValues().add() 添加 MapperFactoryBean 所需的 字段或者方法参数信息 : sqlSessionFactory 、 mapperInterface等
至此 MapperScannerConfigurer 的使命已经完成, 至于 MapperFactoryBean 的创建就完全交给Spring来完成了。
三、 MapperFactoryBean 、SqlSessionTemplate:Mapper与SqlSession解耦的利器
我们知道在mybatis中,Mapper是通过 SqlSession创建的,而SqlSession的生命周期仅仅在一次会话中,那么按照这种设计,每一次会话都要去创建SqlSession,然后再通过SqlSession去创建Mapper。我们知道Mapper其实没有必要每次都去创建,它更加适合作为一个单列对象。那么怎么将SqlSession和Mapper解耦呢? 在mybatis-spring项目中通过 MapperFactoryBean 、SqlSessionTemplate 来实现的。接下来我们就来解析它们。
MapperFactoryBean
正如前面我们所看到的一样,MapperFactoryBean 其实可以理解为 Mapper的代理工厂Bean,我们可以通过 MapperFactoryBean 的方法获取到 Mapper的代理对象。先来看下 MapperFactoryBean继承关系 :
我们可以看到 MapperFactoryBean 实现了 FactoryBean, 那么 肯定通过 实现 getObject() 获取到 Mapper的代理对象,查看源码如下:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
其内部就是我们熟悉的 getSqlSession().getMapper() 创建Mapper代理对象的方法。熟悉Spring 的同学都知道 在Bean加载的过程中如果发现当前Bean对象是 FactoryBean 会去 调用getObject() 获取真正的Bean对象。不熟悉的同学可以去看下 AbstractBeanFactory 的 getBean() 方法。
但是似乎还是没有吧SqlSession和Mapper解耦的迹象呢?不着急,我们继续看下 getSqlSession(), 发现其是 父类 SqlSessionDaoSupport 实现,我们看下SqlSessionDaoSupport源码:
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
// 创建 SqlSession子类 SqlSessionTemplate
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
....
}
我们发现我们获取到的SqlSession其实是其子类SqlSessionTemplate, 我们查看其构造方法源码:
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;
// 维护了一个 SqlSession的代理对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
我们可以清楚的发现,其内部维护了一个 SqlSession的字段 sqlSessionProxy ,其赋值的是代理对象 SqlSessionInterceptor。 我们再来看下 SqlSessionInterceptor 的源码:
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过getSqlSession() 获取一个 SqlSession
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)) {
// 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) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
我们发现其代理实现时,通过getSqlSession() 获取一个 全新的SqlSession。也就是说创建Mapper的SqlSession和会话请求的SqlSession不是同一个。这里就完美的解耦了Mapper和SqlSession,并且保障了每次会话SqlSession的生命周期范围。
这里超前提下: getSqlSession().getMapper() 其实 是通过 configuration.getMapper() 来获取的,那么就意味着 configuration内部必须添加了Mapper信息,那么configuration是何时添加的呢? 可以看下 MapperFactoryBean的checkDaoConfig()方法,源码如下:
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
由于父类实现了 InitializingBean 接口,并且其afterPropertiesSet() 调用了 checkDaoConfig() 方法 ,所以,至少在初始化创建MapperFactoryBean 时,就已经向 configuration内部必须添加了Mapper信息。
三、个人总结
本文解析了Mybatis与Spring是如何整合的,其中的关键对象包括:
SqlSessionFactoryBuilder: 用于创建 SqlSessionFactory
SqlSessionFactory: 用于创建 SqlSession
SqlSession: Mybatis工作的最顶层API会话接口,所有访问数据库的操作都是通过SqlSession来的
Configuration: 存放有所有的mybatis配置信息,包括mapper.xml、 mybatis-config.xml等
XMLConfigBuilder: 解析 mybatis-config.xml 配置并存放到Configuration中
XMLMapperBuilder: 解析 mapper.xml 配置并存放到Configuration中
SqlSessionFactoryBean: mybatis整合Spring时的 生成 SqlSessionFactory 的FactoryBean
MapperScannerConfigurer: mybatis整合Spring时的 实现方便加载Mapper接口,以及将 Mapper 偷梁换柱成 MapperFactoryBean
MapperFactoryBean: 生成 Mapper 代理对象的FactoryBean
SqlSessionTemplate: 内部维护有 SqlSession 的代理对象,解耦Mapper和SqlSession的关键对象。
如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!
本文由博客一文多发平台 OpenWrite 发布!
Mybatis源码解析(一) —— mybatis与Spring是如何整合的?的更多相关文章
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- 源码解析之 Mybatis 对 Integer 参数做了什么手脚?
title: 源码解析之 Mybatis 对 Integer 参数做了什么手脚? date: 2021-03-11 updated: 2021-03-11 categories: Mybatis 源码 ...
- Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别
XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...
- Mybatis源码解析,一步一步从浅入深(一):创建准备工程
Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...
- Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取
在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...
- Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?
Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? 如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...
- Mybatis源码解析(三) —— Mapper代理类的生成
Mybatis源码解析(三) -- Mapper代理类的生成 在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...
- mybatis源码-解析配置文件(四)之配置文件Mapper解析
在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...
随机推荐
- 两道DP,四年修一次路
第十一届:山区修路 题目描述 SNJ位于HB省西部一片群峰耸立的高大山地,横亘于A江.B水之间,方圆数千平方公里,相传上古的神医在此搭架上山采药而得名.景区山峰均在海拔3000米以上,堪称" ...
- 每天一套题打卡|河南省第七届ACM/ICPC
A 海岛争霸 题目:Q次询问,他想知道从岛屿A 到岛屿B 有没有行驶航线,若有的话,所经过的航线,危险程度最小可能是多少. 多源点最短路,用floyd 在松弛更新:g[i][k] < g[i][ ...
- nth-child,nth-of-type
首先,这两个选择器是用来干什么的? 举例子 p:nth-child(1);这个选择器选择的是p所有父辈元素中第一个子元素,且这个子元素为p,此时就生效. p:nth-of-type(1);这个选择器选 ...
- 201871010110-李华《面向对象程序设计(java)》第十四周学习总结
博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...
- 201871010136—赵艳强《面向对象程序设计(java)》第十三周学习总结
201871010136—赵艳强<面向对象程序设计(java)>第十三周学习总结 博文正文开头格式:(2分) 项目 内容 <面向对象程序设计(java)> https:// ...
- Win10 Mactype 字体优化
1.下载安装 Mactype :http://www.mactype.net/ 2. 打开MacType Tray.exe,右键其在任务栏图标就能选择配置文件. 分享一个配置文件: [General] ...
- hdu3068-最长回文-马拉车(Manacher)算法
http://acm.hdu.edu.cn/showproblem.php?pid=3068 脑子转个弯总算看懂马拉车算法了.记录一下思路和模板. 马拉车算法是在O(n)的时间内求出最大回文子串. 一 ...
- webapi跨域实现(CROS、JSONP)
CROS: /// <summary> /// 支持WebAPI服务器端跨域 /// </summary> public class ServerCrossDomainAttr ...
- Web协议详解与抓包实战:HTTP1协议-请求与响应的上下文(7)
一.请求的上下文: User-Agent 指明客户端的类型信息,服务器可以据此对资源的表述做抉择 二.请求的上下文: Referer 浏览器对来自某一页面的请求自动添加的头部 截图2 这对于我们的防盗 ...
- [LeetCode] 187. Repeated DNA Sequences 求重复的DNA序列
All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: "ACG ...