01、Mybatis源码分析
文章目录
01、目标
目标一:理解mybatis框架执行流程
目标二:理解mybatis框架整体架构设计
目标三:熟悉mybatis框架的核心组件
目标四:掌握通过debug跟踪学习源码方式
目标五:掌握mybatis框架相关的常见面试题
02、MyBatis应用案例
1.在mybatis框架课程学习中,有两种开发的方法:
-
1.1.传统mybatis提供的API方法 xml的方式和id
- 1.2.mapper接口代理开发方法
2.接下来,我们分别通过两种开发方法,实现数据库完整的CRUD操作案例,进行一个简单的回顾
03、Mybatis核心的两块如图
04、什么Mybatis
Mybatis是orm持久层框架,只要是对数据库JDBC进行封装而得一个框架。在早期它是ibatis。是apache组织开发的一个项目。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
帮助文档:https://mybatis.org/mybatis-3/zh/index.html
传统的方式JDBC如下:
public void findUser(){
// 1: 开链接
Connection connection = null;
// 2:开启一个执行SQL预处理对象
PreparedStatement statement = null;
// 3: 查询结果就集
ResultSet rs = null;
try{
connection = getConnection();
statement = connection.xxxxxx
rs = statement.executeQuery();
while(rs.hasNext()){
// 这里我可以通过xml的方式把执行sql结果,自动映射到pojo中。
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getString("age"));
}
}catch(ex){
}finally{
//rs.close()
//statement.close()
//connection.close();
}
}
05、编写一个mybatis的案例
1.完成账户表(account)的增删改查操作:
1.1.查询全部账户列表数据
1.2.添加账户数据
1.3.根据账户id修改账户数据
1.4.根据账户id删除账户数据
05-01、新建一个maven工程
05-02、引入mybatis的相关依赖
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- mysql驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<!-- slf4j依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
05-03、定义个db.properties来加载数据源的信息
db.properties放入到resources目录下
# mysql
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://127.0.0.1:3306/ksd-state-db?characterEncoding=utf-8
db.username=root
db.password=mkxiaoer
05-04、定义核心配置文件mybatis.config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--加载属性配置文件-->
<properties resource="db.properties"></properties>
<!-- 配置别名 -->
<typeAliases>
<!-- 包扫描的方式,配置自定义别名-->
<package name="com.itheima.po"/>
</typeAliases>
<!-- 数据源运行环境配置 Connnection?-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<!--加载mapper映射文件配置 -->
<mappers>
<!--加载Account.xml-->
<mapper resource="mappers/Account.xml"></mapper>
</mappers>
</configuration>
05-05、定义一个实体Account
package com.kuangstudy.po;
/**
* 账户实体类 mybatis ----ibatis
*/
public class Account implements java.io.Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
05-06、在resources目录新建一个mappers/AccountMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<!--1.查询全部账户列表数据 -->
<select id="findAllAccounts" resultType="com.kuangstudy.po.Account">
<bind name="keyword" value="'%'+name+'%'"/>
select id,name,money from account where name like #{keyword} limit 0,10
</select>
<!--2.添加账户-->
<insert id="insertAccount" parameterType="java.util.Map">
insert into account(name,money) values(#{name},#{money})
</insert>
<!--3.根据账户id修改账户-->
<update id="updateAccount" parameterType="account">
update account set name=#{name},money=#{money} where id=#{id}
</update>
<!--4.根据账户id删除账户-->
<delete id="deleteAccount" parameterType="account">
delete from account where id=#{id}
</delete>
</mapper>
05-07、定义一个测试用例
package com.kuangstudy.test;
import org.junit.Test;
public class MybatisAccountTest {
@Test
public void findAccountTest() throws Exception{
System.out.println("1111111");
}
}
06、 mybatis的初始化阶段
// 1.加载mybatis框架主配置文件sqlMapConfig.xml,
InputStream inputStream = Resources.getResourceAsStream("mybatis.config.xml");
加载类路径下面的配置文件,这个文件名可以随便明名。但是建议大家还按照规范来把。还没解析哦。值是找到这个文件。准备就绪给后面的解析作为一个参考。
// 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactoryBuilder是mybatis解析配置文件的建造者类,主要的作用是里面提供一个builder方法可以解析加载配置文件。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 获取解析xml配置文件的对象,并且初始化了Coinfiguration的对象,并且解析xml
// 并且把configuration对象的成员属性的构造函数全部初始化一遍,把一些默认配置
// 全部进行初始化
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse();解析xml文件的位置,把解析的内容一一放入到Configuration对象的属性中。
Configurtation configuration = parser.parse();
return build(configuration);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
-----开始解析xml文件,并且把解析的内容放入到Configration对象中
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
07、面试题:你对Mybatis的理解是什么。
我在开发xxxx项目的时候、使用Mybatis开发项目,我对Mybatis的认识是:它其实是一个orm持久层框架,其实就对jdbc一个封装而得的框架,使用好处其实就可以把jdbc从连接开辟,事务管理以及连接关闭,和sql执行,对数据的映射pojo整个过程进行一个封装而已。它的整个执行的过程:
- 首先会引入mybatis依赖,然后会定义个xml核心配置文件放入类路径resouces,这个文件里面就描述了数据源、mapper映射、别名的映射、数据类型转换、插件、属性配置等,定义好以后,那么接下就是创建一个SqlSessionFactory对象,但是在创建这个对象之前,我们会进行xml文件的解析,解析过程中会使用SqlSessionFacotoryBuilder里面提供了一个build方法。这个方法的做了一个非常核心的事情:初始化Configuration对象,并且把对应类的属性的对象全部初始化。并且解析核心xml文件,
- 把解析核心的xml配置文件的内容放入到Configuration对象中属性中。其中就包括别名的映射,在初始化阶段别名映射会自动注册一些常用的别名,如果我们自己也配置也会自动注册到Configuration对象的TypeAliasRegistry的map中。
- 并且把在配置文件中的数据源和事务解析以后放入到Environment,给后续的执行,提供数据链接和事务管理。
- 然后在解析xxxMapper.xml配置文件,根据配置文件解析的规则,会解析里面对应的节点,比如:<select <update <insert <delete <dql <cache <cache-ref <resultMap 等,然后把每个解析的节点放入到一个叫MapperStament对象,sql语句就放入到这个对象SqlSource中.
- 并且把解析的每一个节点对应的MapperStatment同时放入到Configuration全局的Map(mapperedStatments)中。以节点的id和命名空间+id 做为key。以MapperStatement对象做value。给后续执行提供一个参考和方向。
08、关于Mapper解析的话题
mybatis中有一级缓存和二级缓存。缓存的更新:只要在开发中触发insert、update、delete就更换一级缓存和二级缓存。
因为insert,update,delete默认情况下:flushCache = true select是的 flushCache =false
问题:select节点不会刷新缓存缓存。
刷新缓存是指:要不要每次执行把缓存更新一遍。很明显查询是没必要去刷新缓存,因为你查询无论多次次结果一样的,前提是:只要的查询条件一致的话。
答案:每次执行SQL语句是不胡刷新缓存
insert、update、delete刷新缓存默认值是:true
为啥要只要做呢:如果数据发生了变更如果缓存不同步的话,就产生脏读,数据库更新了缓存还是旧数据。
09、创建SqlSessionFactory的作用是干嘛?
- 非常核心的事情:就是将SqlSessionFactoryBuilder中通过build方法创建和装配好Configuration对象通过构造函数进行下传,传递到SqlSession中。
- 开辟SqlSession会话对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
}
10、SqlSession是如何拿到Configuration对象呢?
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();
}
}
return new DefaultSqlSession(configuration, executor, autoCommit);
11、执行SQL语句前述
在调用sqlsession执行的selectList、insert、update、delete的时候,其实就是根据执行的statemenet名字,到Configuration的mapperStatements对应的map中去找到有没有一个对应的 MapperStatement对象,如果找到就返回这个对象,然后给后续执行一个依据和参考。
02、执行阶段的整体架构和参考
- **执行器:**Executor, 处理流程的头部,主要负责缓存、事务、批处理。一个执行可用于执行多条SQL。它和SQL处理器是1对N的关系。
- **Sql处理器:**StatementHandler 用于和JDBC打道,比如基于SQL声明Statement、设置参数、然后就是调用Statement来执行。它只能用于一次SQL的执行
- **参数处理器:**ParameterHandler,用于解析SQL参数,并基于参数映射,填充至PrepareStatement。同样它只能用于一次SQL的执行
- **结果集处理器:**ResultSetHandler,用于读取ResultSet 结果集,并基于结果集映射,封装成JAVA对象。他也只用用于一次SQL的执行。
01、执行器Executor-具体执行SQL语句
mybatis提供很多执行器,其目的是为了插件机制、和缓存做一系列的执行器。每种执行它都好对应的业务和核心的逻辑。比如:
执行器的实现有三种:
1、SimpleExecutor简单执行器(默认)
2、ReuseExecutor 可重用执行器
3、BatchExecutor 批处理执行器
简单执行器
SimpleExecutor是执行器的默认实现,主要完成了“执行”功能,在利用StatementHandler 完成。每次调用执行方法 都会构建一个StatementHandler,并预行参数,然后执行。
// insert() update() delete() 全部通过执行器计入到doUpdate()
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
//selectList()、selectOne() 都进入doQuery()方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
02、我们是如何得知是SimpleExecutor执行器的呢?
默认情况是:executor是CachingExecutor。这个执行器是二级缓存的执行器,如果你在配置文件xxxxMapper.xml文件中申明了
<cache/>
节点的话,就是使用:CachingExecutor
否则没有话,就会委托SimpleExecutor执行器去执行你的SQL语句,
通过SimpleExecutor执行的结构会放入到一个localCache(PerpetualCache)的一级缓存中。
缓存是啥
/**
* Copyright 2009-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
03、整个执行器默认情况下执行SQL的整个过程
03、为什么要设置缓存中
是因为缓解数据库DB的压力。比如你执行一语句
select id,name,age from user where id =1
上面的sql如论你执行1次还是100次还是1000次,其实运行的结果都是一样,如果老是冲入db去获取数据,就给数据库造成很大的连接的压力。可能就出现max connection exception等。索引为了缓存数据库的压力。mybatis默认情况下,
-
一级缓存是默认开启的
-
二级缓存是默认关闭的,如果你要打开就必须在mapper.xml文件中去配置即可。
-
但是二级缓存是属于mapper/xml级别的,是一种跨域sqlsession会话机制,默认情况下是关闭的
也就是说:你只要执行的sql语句是一样的参数一致,无论你多少个会话执行其实都会直接走缓存。
04、一级缓存
@Service
public class UserService {
@AutoWired
private SqlSessionTemplate sqlSessionTemplate;
public User getUser(Integer userid){
User user = sqlSessionTemplate.selectOne("test.getUser",userid);
return user;
}
}
@Service
public class OrderService{
@Autowired
private UserService userService;
public void makeorder(Integer userid){
// 1: 查询用户信息
User user = userService.getUser(userid);
// 此处省略.......
// 2: 查询用户信息---直接走一级缓存
User user = userService.getUser(userid);
}
}
05、二级缓存
06、如何证明sql语句进入了一级缓存呢?
package com.kuangstudy.test;
import com.kuangstudy.po.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class MybatisAccountTest {
@Test
public void findAccountTest() throws Exception{
// 1.加载mybatis框架主配置文件sqlMapConfig.xml,
InputStream inputStream = Resources.getResourceAsStream("mybatis.config.xml");
// 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
// 3.获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.执行数据库操作
List<Account> list1 = sqlSession.selectList("test.findAllAccounts");// 放缓存 + 查数据库
List<Account> list2 = sqlSession.selectList("test.findAllAccounts");// 直接从缓存返回
for (Account ac:list1){
System.out.println(ac);
}
// 5.释放资源
sqlSession.close();
}
}
核心代码
// 4.执行数据库操作
List<Account> list1 = sqlSession.selectList("test.findAllAccounts");// 放缓存 + 查数据库
List<Account> list2 = sqlSession.selectList("test.findAllAccounts");// 直接从缓存返回
这里话,执行的时候:list1会进入db中去查询,list2直接从一级缓存中返回。因为list1到db查询的时候,同时放了一级缓存。一级缓存是在哪里放的呢?BaseExecutor的query方法中,找到一个localCache的对象PerpetualCache。缓存的就是一全局Map对象。
证明1:list1执行数据库,list2不执行,结果依然返回
证明2:通过源码代码方式,list1是不是查询数据库放入一级缓存、查看list2是不是进入了一级缓存,。
07、一级缓存的范围是什么?它的key是什么呢?
一级缓存和缓存的key的组装如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
核心代码
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
key = ms.id + rowBounds.getOffset()+rowBounds.getOffset()+sql+参数+上下文环境的id
key = hash(test.findaccounts):test.findaccounts+0+Integer.MAX_VALUE+hash(select id,name,money from account)+select id,name,money from account+参数+mysql
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
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;
}
结论
一级缓存的范围是:sqlsession范围 。如果是同一个sqlsession一级缓存是有效的,如果不是同一个会话的化是无效。
08、二级缓存的key是怎么命名的,一级缓存的key是什么?
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
范围:二级缓存是以一个Mapper为范围进行缓存。而且是缓存到一个额外的缓存对象中。不同的会话执行只要执行的sql语句和参数是一致的话,永远只会执行一次。后面全部在二级缓存中查询返回
09、一级缓存和二级缓存他们的key是一样?
答案是:一样的,在开发中,如果你二级缓存也开着,一级缓存默认就开着的,两个都会放。
因为执行的时候:都会经过BaseExecutor的query方法—SimpleExecutor-doQuery()—db查询
但是如果都开着,二级缓存优先级及要高于一级缓存。
10、缓存如何更新呢?
触发insert、update、delete 时候会更新。–flushcache = true
SimpleExecutor – doUpdate - 是如何更新缓存和清楚缓存的,这里交给大家?
11、缓存原理是什么?缓存很大了怎么办?
缓存的原理是:Map
缓存的原理是一个Map。如果这个全局map放入很多执行的SQL语句,可能会包内存撑满,会造成内存溢出。缓存的设计原则:新老更替,或者 做弱引用。
- 会有一个上限
- 会有个淘汰算法
- 或者弱引用
设置 cache 标签的属性
cache 标签有多个属性,一起来看一些这些属性分别代表什么意义
-
eviction
: 缓存回收策略,有这几种回收策略
- LRU - 最近最少回收,移除最长时间不被使用的对象
- FIFO - 先进先出,按照缓存进入的顺序来移除它们
- SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
默认是 LRU 最近最少回收策略
flushinterval
缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值readOnly
: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改size
: 缓存存放多少个元素type
: 指定自定义缓存的全类名(实现Cache 接口即可)blocking
: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
12、后面结果集映射
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
– PrepeareStatement – execute()-sql
-
ResultSet
-
Configuration —ResultMap----
-
MapperStastement —POJO – ResultMap
-
Configuration------typeHandlerRegistry–TYPE_HANDLER_MAP—
register(JdbcType.CHAR, new StringTypeHandler()); register(JdbcType.VARCHAR, new StringTypeHandler()); register(JdbcType.CLOB, new ClobTypeHandler()); register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler()); register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler()); register(float.class, new FloatTypeHandler()); register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler()); register(double.class, new DoubleTypeHandler()); register(JdbcType.DOUBLE, new DoubleTypeHandler());
13、二级缓存会淘汰一级缓存的key吗?
答案是不会:
但是注意:sqlsession.close();sqlSession.clearCache();这些方法只会清空一级缓存,不会清空二级缓存。
二级缓存的默认策略是:LRU 淘汰算法.
08、参考文献
https://www.jianshu.com/p/4e00de459949
https://blog.csdn.net/zongf0504/article/details/100104029
https://zhuanlan.zhihu.com/p/299020451