SHAN


  • Home

  • Archives

IoC容器初始化过程(一):Resource定位

Posted on 2018-05-27

BeanDefinition

application.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--XML方式配置Bean-->
<bean id="student" class="com.springleanring.Student">
</bean>
</beans>

Spring会把配置在XML中的bean信息转为IoC容器内部的数据结构,这个数据结构就是
BeanDefinition,也就是说在Spring的XML配置文件中存在的元素,在容器初始化时,会读取这些XML文件,从中解析,然后封装为BeanDefinition对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;

String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

int ROLE_APPLICATION = 0;

int ROLE_SUPPORT = 1;

int ROLE_INFRASTRUCTURE = 2;

void setParentName(@Nullable String parentName);

@Nullable
String getParentName();

void setBeanClassName(@Nullable String beanClassName);//设置bean的className

@Nullable
String getBeanClassName();

void setScope(@Nullable String scope);//设置bean的scope属性

@Nullable
String getScope();

void setLazyInit(boolean lazyInit);//设置lazyInit

boolean isLazyInit();//

void setDependsOn(@Nullable String... dependsOn);

//省略了其他方法
}

IoC容器初始化过程

(1)Resource定位过程

Resource定位指的是BeanDefinition的资源定位,这个过程类似于容器寻找数据的过程。

以在application.xml中配置的为例:

BeanDefinition是元素在IoC容器内部的存在形式,对BeanDefinition的定位也就是对元素所在资源文件application.xml的定位,定位到资源文件的位置 才能从application.xml中读取信息,因此IoC容器初始化时首先要完成对Resource的定位。

(2)BeanDefinition的载入

这个载入过程是把用户定义好的Bean表示成IoC容器的内部数据结构,这个容器内部数据结构就是BeanDefinition,也就是POJO对象在IoC容器中的抽象,通过BeanDefinition IoC容器可以方便的对Bean进行管理。

(3)向IoC容器注册这些BeanDefinition

BanDefinition注册指的是将第(2)步中得到的每一个BeanDefinition注册到一个HashMap中,IoC容器通过这个HashMap来持有这些BeanDefinition数据。

Spring IoC中的两个主要容器

IoC容器可以理解为spring管理Bean的地方。

(1)BeanFactory

是一个接口,定义了IoC容器最基本的功能规范,提供容器最基本的功能。如getBean方法,通过该方法可以从容器中获取Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

public interface BeanFactory {

/**
* 通过name获取容器中管理的bean
* @param name bean的name
* @return bean的一个实例
*/
Object getBean(String name) throws BeansException;

<T> T getBean(String name, @Nullable Class<T> requiredType)throws BeansException;

Object getBean(String name, Object... args) throws BeansException;

<T> T getBean(Class<T> requiredType) throws BeansException;

<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

boolean containsBean(String name);//是否包含某个bean

//是否是单 模式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

//是否是原型模式
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

//省略了其他方法
}

(2)ApplicationContext

如果说BeanFactory是IoC容器的基本形式,那么ApplicationContext就是IoC容器的高级表现形式,它除了包含BeanFactory中的基本方法,还提供了一些其他的方法。

ApplicationContext同样是一个接口,它继承了ListableBeanFactory接口,ListableBeanFactory继承了BeanFacotry接口,因此ApplicationContext包含了BeanFactory接口中的所有方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

String getId();

String getApplicationName();

String getDisplayName();

long getStartupDate();

ApplicationContext getParent();

AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

}

常用的实现ApplicationContext接口的三种容器:

FileSystemXmlApplicationContext:从文件系统载入Resource

ClassPathXmlApplicationContext:从class path载入Resource

XmlWebApplicationContext:在web容器中载入Resource

通过ApplicationContext从容器中获取一个bean:

1
2
3
4
5
6
7
//使用FileSystemXmlApplicationContext加载配置文件
ApplicationContext context=new FileSystemXmlApplicationContext("classpath*:/spring/applicationContext.xml");
//ClassPathXmlApplicationContext加载
//ApplicationContext context=new ClassPathXmlApplicationContext("classpath*:/spring/applicationContext.xml");
//从容器中获取bean
Student student= (Stuedent) context.getBean("student");//根据id从配置文件中获取bean
student.getName();

那么就以FileSystemXmlApplicationContext为例,分析ApplicationContext的实现过程。

1.FileSystemXmlApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public FileSystemXmlApplicationContext {
}

public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}

/**
* 构造函数:需要传入XML资源文件的路径
*/
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}

/**
* 构造函数:传入多个资源文件
*/
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}

/**
* 构造函数
* @param configLocations 资源文件数组
* @param parent 双亲IoC容器
*/
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}

/**
* 构造函数
* @param configLocations 资源文件路径
* @param refresh 是否调用refresh方法载入BeanDefinition
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, (ApplicationContext)null);
}

/**
* 构造函数,其他构造函数最终调用的都是该函数
* @param configLocations 资源文件路径
* @param refresh 是否调用refresh方法载入BeanDefinition
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if(refresh) {
this.refresh();//分析容器初始化的一个重要入口
}

}
/**
* 通过资源文件路径获取Resource对象,返回的是一个FileSystemResource对象,通过这个对象可以进行相关I/O操作,完成BeanDefinition定位
* @param path 资源文件路径
* @return Resource spring中的资源对象
*/
protected Resource getResourceByPath(String path) {
if(path.startsWith("/")) {
path = path.substring(1);
}

return new FileSystemResource(path);
}
  • 从第二个构造函数开始,实际上都是调用的 FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)这个构造函数。
  • 对BeanDefinition资源定位的过程是从refresh方法开始的,refresh方法是分析容器初始化的重要入口。

2.AbstractApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
// 调用obtainFreshBeanFactory()方法获取IoC容器,容器的创建和对Resource的定位就在该方法中
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);

try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if(this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}

this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}

}
}
}
/**
* 获取BeanFactory
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//refreshBeanFactory中会创建IoC容器
this.refreshBeanFactory();
//获取创建的IoC容器
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}

return beanFactory;
}

public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
  • refreshBeanFactory()方法,在AbstractApplicationContext中,该方法是一个抽象方法,具体的实现由AbstractApplicationContext的子类AbstractRefreshableApplicationContext来实现,该方法中创建了IoC容器BeanFacotry对象。

  • getBeanFactory()方法也是抽象方法,在AbstractRefreshableApplicationContext中实现,其实就是返回refreshBeanFactory方法中创建的BeanFacotry对象。

3.AbstractRefreshableApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

protected final void refreshBeanFactory() throws BeansException {
//如果已经建立了BeanFactory,销毁并关闭BeanFactory
if(this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}

try {
//构造了一个BeanFactory,这里使用DefaultListableBeanFactory实现
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
//载入BeanDefinition
this.loadBeanDefinitions(beanFactory);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}

/**
* 创建DefaultListableBeanFactory的地方
* getInternalParentBeanFactory()的具体实现可以参看AbstractApplicationContext的实现
*
* @return
*/
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}

/**
* 获取创建的IoC容器
*/
public final ConfigurableListableBeanFactory getBeanFactory() {
Object var1 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
if(this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
} else {
return this.beanFactory;
}
}
}

/**
* 抽象方法,载入BeanDefinition,由子类AbstractXmlApplicationContext实现
*
* @param beanFactory
* @throws BeansException
* @throws IOException
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
}
  • refreshBeanFactory()方法中调用了createBeanFactory()方法完成了容器的创建,使用的是DefaultListableBeanFactory类型的容器。
  • 创建容器之后,调用了loadBeanDefinitions()方法载入BeanDefinition,该方法由子类AbstractXmlApplicationContext实现。
  • loadBeanDefinitions()方法中包含了Resource资源定位的过程。

4.AbstractXmlApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建XmlBeanDefinitionReader对象,通过回调设置到BeanFactory中
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
//设置ResourceLoader
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//启动Bean定义信息载入的过程,委托给beanDefinitionReader完成
this.initBeanDefinitionReader(beanDefinitionReader);
//载入BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}

protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}

/**
* 载入BeanDefinition
* @param reader
* @throws BeansException
* @throws IOException
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取Reource类型的配置文件的地址
Resource[] configResources = getConfigResources();
if (configResources != null) {
//调用AbstractBeanDefinitionReader中的loadBeanDefinitions方法
reader.loadBeanDefinitions(configResources);
}
//获取字符串类型的配置文件的地址
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//调用AbstractBeanDefinitionReader中的loadBeanDefinitions方法
reader.loadBeanDefinitions(configLocations);
}
}

protected Resource[] getConfigResources() {
return null;
}
}
  • loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中首先创建了XmlBeanDefinitionReader对象,从名称上可以猜到,它可以读取XML中定义的bean信息。
  • loadBeanDefinitions(XmlBeanDefinitionReader reader)方法中又调用了XmlBeanDefinitionReader中的loadBeanDefinitions()方法,XmlBeanDefinitionReader是AbstractBeanDefinitionReader的子类,该方法在AbstractBeanDefinitionReader中实现。

5.AbstractBeanDefinitionReader

载入BeanDefinition时:

  • AbstractBeanDefinitionReader中实现的是参数类型是String的方法。
  • 如果传入的参数是Resource类型的,先进入到loadBeanDefinitions(Resource… resources)方法中,在该方法中又会调用XmlBeanDefinitionReader中的loadBeanDefinitions方法,也就是如果参数类型是Resource,是由XmlBeanDefinitionReader实现的。

查看上面FileSystemXmlApplicationContext用法的例子,传入的参数是资源文件的地址,因此调用的是AbstractBeanDefinitionReader的loadBeanDefinitions(String… locations)方法:

1
FileSystemXmlApplicationContext("classpath*:/spring/applicationContext.xml");

AbstractBeanDefinitionReader类中实现的方法为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

/**
* 载入参数类型是Resource对象的BeanDefinition,由子类XmlBeanDefinitionReader实现
*/
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
//对载入Bean的数量进行统计
int counter = 0;
//遍历整个Resource集合,从每个集合中载入所有的BeanDefinition
for (Resource resource : resources) {
//loadBeanDefinitions方法是一个接口方法,在XmlBeanDefinitionReader中有具体的实现
counter += loadBeanDefinitions(resource);
}
return counter;
}

/**
* 根据配置文件的地址,载入BeanDefinition
*/
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取ResourceLoader对象,实际使用的是DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//这里对Resource的路径模式进行解析,得到Resource集合,这些集合指向我们已经定义好的BeanDefinition信息
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//调用DefaultResourceLoader的getResource完成Resource定位,将传入的资源文件的地址,转为Resource对象
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}

/**
* 根据配置文件的地址,载入BeanDefinition
*/
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
}

  • 在loadBeanDefinitions(String location, @Nullable Set actualResources)方法中定义了ResourceLoader对象,由ResourceLoader完成资源的加载。ResourceLoader其实是使用了DefaultResourceLoader对象的getResource方法完成Resource对象的定位。
  • 在loadBeanDefinitions(String location, @Nullable Set actualResources)方法中根据location资源文件地址将资源文件转为了Resource对象,然后还是调用的XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)完成BeanDefinition的载入。

6.DefaultResourceLoader

进入DefaultResourceLoader看一下如何根据配置文件地址完成对Resource的定位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DefaultResourceLoader implements ResourceLoader {  
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.protocolResolvers.iterator();

Resource resource;
do {
if(!var2.hasNext()) {
//处理带有/反斜杠的resource
if(location.startsWith("/")) {
return this.getResourceByPath(location);
}
//处理带有calsspath标识的resource
if(location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}

try {
//处理URL标识的resource
URL url = new URL(location);
return (Resource)(ResourceUtils.isFileURL(url)?new FileUrlResource(url):new UrlResource(url));
} catch (MalformedURLException var5) {
return this.getResourceByPath(location);
}
}

ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);

return resource;
}

protected Resource getResourceByPath(String path) {
return new DefaultResourceLoader.ClassPathContextResource(path, this.getClassLoader());
}
}

到此,IoC容器对Resource的定位过程已经完成。

最后简单总结一下重点流程:

1.初始化时需要创建容器BeanFactory,默认使用的是DefaultListableBeanFactory类型的容器。
2.容器创建之后,会调用loadBeanDefinitions()方法载入BeanDefinition.
载入过程中的流程:
(1)创建XmlBeanDefinitionReader对象,为读取XML资源文件中定义的bean信息做准备。
(2)为XmlBeanDefinitionReader对象设置ResoucrLoader,用于资源文件的加载。
(3)调用XmlBeanDefinitionReader父类AbstractBeanDefinitionReader中实现的loadBeanDefinitions()方法,传入资源文件对象或文件地址,在这一步中,ResourceLoader对象实际使用的是DefaultResourceLoader。
(4)在DefaultResourceLoader中完成了对Resource的定位。

参考:

spring技术内幕:深入解析spring架构与设计原理

竹叶青1986:Spring源码阅读之IoC容 初始化1 – Resource定位

Spring版本:5.0.5

Mybatis二级缓存

Posted on 2018-05-19
  • 一级缓存是sqlsession级别的,不同sqlsession之间的缓存互不影响,而二级缓存实现了不同sqlsession之间的缓存共享。
  • 二级缓存是mapper级别的,同一个mapper的namespace相同,所以同一个mapper(或者namespace)下的查询共享一个二级缓存,不同mapper之间的二级缓存互不影响。
  • 如果配置了二级缓存,首先从二级缓存中查询,如果未找到再从一级缓存中查询。

二级缓存的配置方式

  1. mybatis的配置文件中开启二级缓存
1
<setting name="cacheEnabled" value="true"/>
  1. 在Mapper.xml中设置
1
2
3
4
5
6
7
<mapper namespace="mapper.StudentMapper">
<!-- 开启二级缓存 -->
<cache/>
<select id="getStudentById">
SELECT * FROM student WHERE id = #{id}
</select>
</mapper>
  1. 假如某个语句不想使用缓存,每次都需要从数据库查询最新的数据,那么通过useCache设置即可.
1
2
3
4
5
6
7
8
<mapper namespace="mapper.StudentMapper">
<cache/>
<!-- 配置useCache为false,禁用二级缓存 -->
<select id="getStudentById" useCache="false">
SELECT * FROM student WHERE id = #{id}
</select>

</mapper>
  1. 刷新缓存,同一个namespace下,insert、update、delete操作默认会刷新缓存,当然也可以通过设置flushCache=false不刷新缓存.
1
2
3
4
5
6
7
8
9
<mapper namespace="mapper.StudentMapper">
<cache/>

<!-- flushCache为false每次执行语句将不刷新缓存 -->
<update id="updateStudent" flushCache="false">
UPDATE student SET name = #{name} WHERE id = #{id}
</update>

</mapper>

Executor的生成

二级缓存使用CachingExecutor实现,看一下Executor的生成过程。

1.SqlSessionFactory

从SqlSessionFactoryBuilder说起,通过它的build方法构建SqlSessionFactory时返回一个DefaultSqlSessionFactory:

1
2
3
4
public SqlSessionFactory build(Configuration config) {
//创建一个DefaultSqlSessionFactory返回,它实现了SqlSessionFactory接口
return new DefaultSqlSessionFactory(config);
}

然后进入DefaultSqlSessionFactory,在它的openSeesion方法中又调用了openSessionFromDataSource方法,在此方法中,通过调用configuration的newExecutor方法创建了Executor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
final Executor executor = configuration.newExecutor(tx, execType);//生成执行器
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2. Configuration

Configuration的newExecutor方法中,根据Executor类型判断创建哪种执行器,默认是使用SimpleExecutor,如果开启了二级缓存,使用CachingExecutor对执行器进行装饰,完成Executor的创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根据类型判断创建哪种类型的执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {//默认的执行器
executor = new SimpleExecutor(this, transaction);
}
//如果开启了二级缓存,创建缓存执行器(装饰者模式)
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

缓存的使用过程

1. DefaultSqlSessionFactory

回到DefaultSqlSessionFactory的penSessionFromDataSource方法,该方法返回了一个DefaultSqlSession,那么进入DefaultSqlSession,以selectList方法为例,调用了Executor的query方法,由于开启二级缓存使用的是CachingExecutor,那么进入CachingExecutor查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//调用了执行器的query方法
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();
}
}
2. CachingExecutor

CachingExecutor包含了两个成员变量分别为Executor类型的delegate和TransactionalCacheManager类型的tcm。
从CachingExecutor的构造函数中也可以看到,CachingExecutor使用的是装饰者模式,对基本的Executor实现类进行装饰。

1
2
3
4
5
6
7
8
9
10
public class CachingExecutor implements Executor {

private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);//装饰者模式
}
}

CachingExecutor的query方法中,由于CachingExecutor使用了装饰者模式对Executor实现者(delegate变量)进行了装饰,如果未开启二级缓存,将调用被装饰者delegate的query方法进行查询,接下来流程就与一级缓存的流程一致。如果开启了二级缓存,首先从缓存中获取数据,如果未获取到,同样调用delegate的query方法走一级缓存的流程,然后将结果放入到二级缓存中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存key,一级缓存中提过如何创建CacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从MappedStatement获取缓存
Cache cache = ms.getCache();
//如果缓存不为空
if (cache != null) {
//是否需要刷新缓存
flushCacheIfRequired(ms);
//是否开启了useCache
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);//从缓存取数据
if (list == null) {
//如果为空,调用Executor中的query方法,走一级缓存的流程
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // 将数据放入缓存
}
return list;
}
}
//二级缓存为空,调用Executor中的query方法,走一级缓存的流程
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
3. Cache

首先,在query方法有一个从MappedStatement获取Cache对象的过程,MappedStatement是在MyBatis启动的时候存入Configuration中的,因此Mybatis的二级缓存的生命周期与应用的生命周期一致,应用不结束,二级缓存就会一直存在。

1
2
//从MappedStatement获取缓存
Cache cache = ms.getCache();

Cache只是一个接口,它有多个实现类,并且使用了装饰者模式进行层层包装,在构建Cache的过程中会提到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Cache {

String getId();

void putObject(Object key, Object value);

Object getObject(Object key);

Object removeObject(Object key);

void clear();

int getSize();

ReadWriteLock getReadWriteLock();

}
4. TransactionalCacheManager

从tcm缓存中获取数据的过程:

1
List<E> list = (List<E>) tcm.getObject(cache, key);//从缓存取数据

TransactionalCacheManager中使用了HashMap存储结构,key为Cache对象,value为TransactionalCache:

1
2
3
4
public class TransactionalCacheManager {

private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
}

key:Cache对象,是一个接口,它会根据Mapper文件中配置的创建对应的实例。

比如使用Mybatis内置的cache:

1
<cache eviction="FIFO" flushInterval="10000" readOnly="true" />

value:TransactionalCache事物缓存,它也是Cache的一个实现类。

调用TransactionalCacheManager的getObject或者putObject方法时都会首先调用getTransactionalCache方法获取当前的Cache对应的TransactionalCache对象,如果没有将会实例化一个TransactionalCache对象,因为TransactionalCache是Cache的实现类,因此可以使用TransactionalCache继续装饰当前的Cache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TransactionalCacheManager {

private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}

public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}

private TransactionalCache getTransactionalCache(Cache cache) {
//根据Cache获取TransactionalCache对象
TransactionalCache txCache = transactionalCaches.get(cache);
//如果获取为空
if (txCache == null) {
//创建一个TransactionalCache对象,并装饰当前的Cache对象的实现者
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
5. TransactionalCache

存取流程:

  • 当调用tcm.getObject时,实际上调用的就是TransactionalCache类的getObject()方法,由于TransactionalCache装饰了当前的Cache对象,索引调用getObject()职责会一层一层传递给PerpetualCache,然后获取缓存数据。TransactionalCache类还有一个clearOnCommit成员变量,表示使用事务提交时是否清空缓存,默认为false。

  • tcm.putObject()方法用于将数据放入缓存,实际上也是调用的TransactionalCache的putObject()方法,然后将数据放入到entriesToAddOnCommit待提交缓存数据中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class TransactionalCache implements Cache {

private static final Log log = LogFactory.getLog(TransactionalCache.class);

private final Cache delegate;
private boolean clearOnCommit;//事务提交时是否清空缓存
private final Map<Object, Object> entriesToAddOnCommit;//待提交的缓存数据
private final Set<Object> entriesMissedInCache;//统计命中率使用

public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false; //默认提交清除缓存为false
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}

/**
* 获取缓存
* @param key The key
* @return
*/
@Override
public Object getObject(Object key) {
// getObject方法中,会一路传递到PerpetualCache,然后获取数据
Object object = delegate.getObject(key);
//如果未获取到数据
if (object == null) {
//将数据放入集合,统计命中率使用
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}

/**
* 添加缓存数据
* @param key Can be any object but usually it is a {@link CacheKey}
* @param object
*/
@Override
public void putObject(Object key, Object object) {
//首先将数据放入到待提交缓存数据中
entriesToAddOnCommit.put(key, object);
}

//省去了其他方法

}

将数据放入缓存时首先将缓存放入到了待提交缓存中,所以如果不使用commit提交事务的时候,两次相同的查询,第二次并不会拿到第一次的缓存数据,因为第一次的数据被放在待提交的缓存数据中,相当于没有提交缓存数据,因此查询的时候也就取不到。

接下来就来看看DefaultSqlSession的commit方法,它调用了Executor实现类的commit方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void commit() {
commit(false);
}

@Override
public void commit(boolean force) {
try {
//调用了Executor实现类的commit方法
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

CachingExecutor的commit方法:

1
2
3
4
5
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();//调用了TransactionalCacheManager的commit方法
}

TransactionalCacheManager的commit方法:

1
2
3
4
5
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();//调用了TransactionalCache的commit方法
}
}

回到TransactionalCache类,查看它的commit方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();//清空待提交缓存数据
}

public void commit() {
//如果事务提交需要清空缓存
if (clearOnCommit) {
delegate.clear();//交给装饰的Cache对象清空缓存
}
//刷新缓存数据
flushPendingEntries();
//重置
reset();
}

private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();//清空待提交的缓存数据
entriesMissedInCache.clear();
}

private void flushPendingEntries() {
//遍历待提交的缓存数据
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
//委托给装饰的Cache类,将待提交的缓存数据加入缓存,会交给 PerpetualCache对象
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}

TransactionalCache的commit方法中,如果clearOnCommit为true,那么将会清空缓存数据,如果为false,本次需要提交的缓存数据将会委托给TransactionalCache装饰的Cache类,调用putObject方法,将数据加入到缓存中。


Cache的构建过程

1.XmlMapperBuilder

XmlMapperBuilder中的cacheElement方法首先或获取缓存的配置信息,然后根据配置信息调用MapperBuilderAssistant的useNewCache方法创建缓存对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取各种配置信息
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//创建缓存对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
2.MapperBuilderAssistant

MapperBuilderAssistant中的useNewCache方法中使用建造者模式创建了Cache对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();//使用建造者模式实例化对象
configuration.addCache(cache);
currentCache = cache;
return cache;
}
3.CacheBuilder

CacheBuilder的build方法中,首先调用了setDefaultImplementations()将PerpetualCache作为默认的实现类,完成Cache的实例化。接着会判断当前创建的实例是否是PerpetualCache的实例,如果是调用setStandardDecorators方法对PerpetualCache进行装饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Cache build() {
setDefaultImplementations();//设置默认的Cache实现类
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);//设置标准的装饰器
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}

private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;//使用PerpetualCache作为默认的实现类
if (decorators.isEmpty()) {
decorators.add(LruCache.class);//如果装饰器为空,添加LruCache装饰器,对Cache对象进行装饰
}
}
}

在setStandardDecorators中可以看到装饰链:

BlockingCache -> SynchronizedCache -> LoggingCache -> SerializedCache -> ScheduledCache-> LruCache -> PerpetualCache。

其中LruCache是在setDefaultImplementations()中设置的。

通过装饰链可以明白为什么在TransactionalCache添加或者获取缓存的时候最终会从PerpetualCache调用。
一级缓存也是使用PerpetualCache实现,二级缓存实际上就是通过装饰者模式对PerpetualCache进行层层包装,所以从二级缓存中未获取到数据时,还可以走一级缓存的流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
//如果clearInterval不为空,使用cheduledCache装饰
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
//使用SerializedCache装饰
cache = new SerializedCache(cache);
}
//使用LoggingCache装饰
cache = new LoggingCache(cache);
//使用SynchronizedCache装饰
cache = new SynchronizedCache(cache);
if (blocking) {
//使用BlockingCache装饰
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}

参考:

凯伦:聊聊MyBatis缓存机制

五月的仓颉:【MyBatis源码解析】MyBatis一二级缓存

Double-Eggs:【Mybatis框架】查询缓存(二级缓存)

零度anngle:MyBatis缓存策略之二级缓存

jtlgb:mybatis 缓存(cache)的使用

Mybatis一级缓存

Posted on 2018-05-17

Mybatis一级缓存的配置方式:

1
<setting name="localCacheScope" value="SESSION"/>

value有两个值可选:
session:缓存对一次会话中所有的执行语句有效,也就是SqlSession级别的。
statement:缓存只对当前执行的这一个Statement有效。

BaseExecutor

一级缓存中对缓存的查询和写入是在Executor中完成的,以BaseExecutor为例,查看query方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public abstract class BaseExecutor implements Executor {

private static final Log log = LogFactory.getLog(BaseExecutor.class);

protected Transaction transaction;
protected Executor wrapper;

protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache; //缓存
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;

protected int queryStack;
private boolean closed;

......

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//构建CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);//调用了下面的query方法
}

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果queryStack为0或者并且有必要刷新缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();//清空本地缓存
}
List<E> list;
try {
queryStack++;
//从缓存中获取数据,key的类型为CacheKey
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//处理本地缓存输出参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {//如果获取结果为空,从数据库中查找
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果是STATEMENT级别的缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// 清空缓存
clearLocalCache();
}
}
return list;
}
}

  1. 从BaseExecutor的成员变量中,可以看到有一个类型为PerpetualCache变量名为localCache的字段,缓存就是用它来实现的。PerpetualCache类的成员变量也很简单,包含一个id和一个HashMap,缓存数据就存储在HashMap中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class PerpetualCache implements Cache {  

    private final String id;

    private Map<Object, Object> cache = new HashMap<Object, Object>();//使用一个map做存储

    get set方法省略
    ......
    }
  2. 在BaseExecutor的quey方法中,有一个构建CacheKey的语句,既然缓存数据存储在HashMap中,那么数据格式一定是键值对的形式,这个CacheKey就是HashMap中的key,value是数据库返回的数据。

    1
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  3. 第二个query方法中,当执行查询时,首先通过localCache.getObject(key)从缓存中获取数据,如果获取的数据为空,再从数据库中查找。

1
2
//从缓存中获取数据,key的类型为CacheKey  
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
1
2
//如果获取结果为空,从数据库中查找  
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  1. 如果开启了flushcache,将会清空缓存
1
2
3
4
//如果queryStack为0或者并且有必要刷新缓存  
if(queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();//清空本地缓存
}

配置flushcache:

1
2
3
<select id="getStudent" parameterType="String" flushCache="true">    
SQL
</select>
  1. 如果一级缓存的级别为Statement,将会清空缓存,这也是如果设置一级缓存的级别为Statement时缓存只对当前执行的这一个Statement有效的原因:
1
2
3
4
5
//如果是STATEMENT级别的缓存  
if(configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//清空缓存
clearLocalCache();
}

配置方式:

1
<setting name="localCacheScope" value="STATEMENT"/>

CacheKey如何产生的

  1. 在query方法中,调用了createCacheKey方法生成CacheKey,然后多次调用了cachekey的update方法,将标签的ID、分页信息、SQL语句、参数等信息作为参数传入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
//设置ID,也就是标签所在的Mapper的namespace + 标签的id
cacheKey.update(ms.getId());
//偏移量,Mybatis自带分页类RowBounds中的属性
cacheKey.update(rowBounds.getOffset());
//每次查询大小,同样是Mybatis自带分页类RowBounds中的属性
cacheKey.update(rowBounds.getLimit());
//标签中定义的SQL语句
cacheKey.update(boundSql.getSql());
//获取参数
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 处理SQL中的参数
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
  1. 通过源码,看一下CacheKey的update方法,update方法中记录了调用update传入参数的次数、每个传入参数的hashcode之和checksum、以及计算CacheKey的成员变量hashcode的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class CacheKey implements Cloneable, Serializable {

private static final long serialVersionUID = 1146682552656046210L;

public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();

private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;

private final int multiplier;//一个乘数
private int hashcode;//hashcode
private long checksum;//update方法中传入参数的hashcode之和
private int count; //调用update方法向updatelist添加参数的的次数
private List<Object> updateList;//调用update传入的参数会被放到updateList

public CacheKey() {
//初始化
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<Object>();
}

public CacheKey(Object[] objects) {
this();
updateAll(objects);
}

public int getUpdateCount() {
return updateList.size();
}

public void update(Object object) {
//获取参数的hash值
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
//计数
count++;
//hash值累加
checksum += baseHashCode;
//更新hash,hash=hash*count
baseHashCode *= count;
//计算CacheKey的hashcode
hashcode = multiplier * hashcode + baseHashCode;
//将参数添加到updateList
updateList.add(object);
}
}
  1. CacheKey中的成员变量的作用是什么呢,接下来看一下它的equals方法,CacheKey中重写了equals方法,CacheKey中的成员变量其实就是为了判断两个CacheKey的实例是否相同:

如果满足以下条件,两个CacheKey将判为不相同:

  • 要比较的对象不是CacheKey的实例

  • CacheKey对象中的hashcode不相同、count不相同、checksum不相同(它们之间是或的关系)

  • CacheKey对象的updateList成员变量不相同

总结:

如果Statement Id + Offset + Limmit + Sql + Params 都相同将被认为是相同的SQL,第一次将CacheKey作为HashMap中的key,数据库返回的数据作为value放入到集合中,第二次查询时由于被认为是相同的SQL,HashMap中已经存在该SQL的CacheKey对象,可直接从localCache中获取数据来实现mybatis的一级缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public boolean equals(Object object) {
//如果对象为空
if (this == object) {
return true;
}
//如果不是CacheKey的实例
if (!(object instanceof CacheKey)) {
return false;
}

final CacheKey cacheKey = (CacheKey) object;
//如果hashcode值不相同
if (hashcode != cacheKey.hashcode) {
return false;
}
// 如果checksum不相同
if (checksum != cacheKey.checksum) {
return false;
}
//如果count不相同
if (count != cacheKey.count) {
return false;
}
//对比两个对象的updatelist中的值是否相同
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
//如果有不相同的,返回false
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}

总结:

(1)mybatis的一级缓存是SqlSession级别的,不同的SqlSession不共享缓存;

(2)mybatis一级缓存是通过HashMap实现的,在PerpetualCache中定义,没有容量控制;

(3)分布式环境下使用一级缓存,数据库写操作会引起脏数据问题;

参考

五月的仓颉:【MyBatis源码解析】MyBatis一二级缓存

凯伦:聊聊MyBatis缓存机制

1…56

shan

53 posts
12 tags
© 2022 shan
Powered by Hexo
|
Theme — NexT.Muse v5.1.4