二级缓存
什么是mybatis二级缓存?

二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace。

即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存。

第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存。

新增依赖
<!--集成Redis二级缓存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
application.yml中新增配置
#配置Redis链接
  redis:
    host: train
    port: 6379
    timeout: 5s
    lettuce:
      shutdown-timeout: 100ms
      pool:
        max-active: 10
    jedis:
      pool:
        max-idle: 8
        max-wait: 5ms
        min-idle: 1
UserDefineRedisCache(Cache实现类)
import com.baizhi.ApplicationContextHolder;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class UserDefineRedisCache implements Cache {

    private Logger logger = LoggerFactory.getLogger(UserDefineRedisCache.class);

    private String id; //存储namespace
    private long timeout = 300;
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //普通类获取工厂内容
    private RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");


    public UserDefineRedisCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        logger.debug("将查询结果存储到cache.key:"+key+",value:"+value);
        ValueOperations opsForValue = redisTemplate.opsForValue();
        opsForValue.set(key,value,timeout, TimeUnit.SECONDS);
    }

    @Override
    public Object getObject(Object key) {
        logger.debug("从缓存中读取结果.key:"+key);
        ValueOperations opsForValue = redisTemplate.opsForValue();
        return opsForValue.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        logger.debug("从缓存中清除.key:"+key);
        ValueOperations opsForValue = redisTemplate.opsForValue();
        Object value = opsForValue.get(key);
        redisTemplate.delete(key);
        return value;
    }

    @Override
    public void clear() {
        logger.debug("从缓存中清除缓存区所有数据");
        redisTemplate.execute((RedisConnection connection) ->{
            connection.flushAll();
            return null;
        });
    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }
}

由于redisTemplate 不能够获取非spring的bean,而所属类为普通类,不能够自动注入,所以需要用到“ApplicationContextAware”接口。

ApplicationContextHolder
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }
}

实现此接口,即可获取工厂管理的全部内容。

  • @Component
    把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
    泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
SpringBootApplication新增方法
@MapperScans(value ={
        @MapperScan(basePackages = "com.baizhi.dao")
})
@org.springframework.boot.autoconfigure.SpringBootApplication
public class SpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class,args);
    }
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    public RedisTemplate<Object,Object> redisTemplate(LettuceConnectionFactory connectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        //设置Key、value的序列化
        redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
}
  • @Bean
    Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
Mapper.xml中需要引入Cache实现类
<cache type="com.baizhi.cache.UserDefineRedisCache">
        <property name="timeout" value="60"/>
    </cache>
缓存击穿、缓存穿透、缓存雪崩
缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决:
1.设置热点数据永远不过期。
2.加互斥锁。

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决:
1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。

缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
3.设置热点数据永远不过期。