二级缓存
什么是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.设置热点数据永远不过期。