【Mybatis】-01

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、执行阶段的整体架构和参考

在这里插入图片描述

  1. **执行器:**Executor, 处理流程的头部,主要负责缓存、事务、批处理。一个执行可用于执行多条SQL。它和SQL处理器是1对N的关系。
  2. **Sql处理器:**StatementHandler 用于和JDBC打道,比如基于SQL声明Statement、设置参数、然后就是调用Statement来执行。它只能用于一次SQL的执行
  3. **参数处理器:**ParameterHandler,用于解析SQL参数,并基于参数映射,填充至PrepareStatement。同样它只能用于一次SQL的执行
  4. **结果集处理器:**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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值