一、mybatis自身缓存
引入mybatis相关依赖
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
application.properties:
#mybatis
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/nono?characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
mybatis.mapper-locations=classpath:com/nono/mapper/*.xml
mybatis.type-aliases-package=com.nono.entity
logging.level.com.nono.dao=debug
application
package com.nono;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.nono.dao")
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
User实体类,记住要implements Serializable
package com.nono.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
@Data
@Accessors(chain = true)
public class User implements Serializable {
private String id;
private String name;
private Integer age;
private Date bir;
}
UserDao:
package com.nono.dao;
import com.nono.entity.User;
import java.util.List;
public interface UserDao {
List<User> findAll();
}
resources-com/nono/mapper/-UserDaoMapper.xml
开启二级缓存:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nono.dao.UserDao">
<!--开启mybatis二级缓存-->
<cache/>
<!--findAll-->
<select id="findAll" resultType="User">
select id,name,age,bir from t_user
</select>
</mapper>
UserServiceImpl:
package com.nono.service;
import com.nono.dao.UserDao;
import com.nono.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
public List<User> findAll() {
return userDao.findAll();
}
}
test-java-TestUserService:
package com.nono;
import com.nono.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = RedisApplication.class)
@RunWith(SpringRunner.class)
public class TestUserService {
@Autowired
private UserService userService;
@Test
public void testFindAll(){
userService.findAll().forEach(user -> System.out.println(user));
System.out.println("=============");
userService.findAll().forEach(user -> System.out.println(user));
}
}
二、redis缓存
1、由上面mybatis二级缓存可以看出来,mybatis的缓存主要是实现Cache接口,所以我们可以自定义Cache类,public class RedisCache implements Cache。
putObject和getObject是用来实现我们 往redis中存取数据的。这里需要用到RedisTemplate,但是我们自定义的RedisCache是mybatis实例化的,并不是springboot工厂实例化,所以这里不能直接注入RedisTemplate。
所以我们需要一个工具类来将RedisTemplate从spring的工厂中注入RedisCache。
package com.nono.util;
//用来获取springboot创建工厂
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
//保留下来工厂
private static ApplicationContext applicationContext;
//将创建好的工厂以参数形式传递给这个类
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
//提供在工厂中获取对象的方法 RedisTemplate redisTemplate
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
}
package com.nono.cache;
import com.nono.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
//自定义Redis缓存实现
public class RedisCache implements Cache {
//当前放入缓存的mapper的namespace
private final String id;
//必须存在构造方法
public RedisCache(String id){
System.out.println(id);
this.id =id;
}
//返回cache唯一标识
@Override
public String getId() {
return this.id;
}
//缓存中放入值
@Override
public void putObject(Object key, Object value) {
System.out.println("key:"+key.toString());
System.out.println("value:"+value);
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//使用redishash类型作为缓存存储模型 key hashkey value
redisTemplate.opsForHash().put(id.toString(),key.toString(),value);
}
//缓存中获取数据
@Override
public Object getObject(Object key) {
System.out.println("key:"+key.toString());
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//根据key 从redis的hash类型中获取数据
return redisTemplate.opsForHash().get(id.toString(),key.toString());
}
@Override
public Object removeObject(Object o) {
return null;
}
@Override
public void clear() {
}
@Override
public int getSize() {
return 0;
}
}
UserDaoMapper.xml
<!--开启mybatis二级缓存-->
<cache type="com.nono.cache.RedisCache"/>
UserServiceImpl
package com.nono.service;
import com.nono.dao.UserDao;
import com.nono.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public List<User> findAll() {
return userDao.findAll();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User findById(String id) {
return userDao.findById(id);
}
}
TestUserService
package com.nono;
import com.nono.entity.User;
import com.nono.service.UserService;
import org.apache.ibatis.cache.Cache;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = RedisApplication.class)
@RunWith(SpringRunner.class)
public class TestUserService {
@Autowired
private UserService userService;
@Test
public void testFindAll(){
userService.findAll().forEach(user -> System.out.println(user));
System.out.println("=============");
userService.findAll().forEach(user -> System.out.println(user));
}
@Test
public void testFindOne(){
User byId = userService.findById("1");
System.out.println(byId);
System.out.println("==================");
User byId1 = userService.findById("1");
System.out.println(byId1);
}
}
2、增删改走的都是clear
package com.nono.cache;
import com.nono.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
//自定义Redis缓存实现
public class RedisCache implements Cache {
//当前放入缓存的mapper的namespace
private final String id;
//必须存在构造方法
public RedisCache(String id){
System.out.println(id);
this.id =id;
}
//返回cache唯一标识
@Override
public String getId() {
return this.id;
}
//缓存中放入值
@Override
public void putObject(Object key, Object value) {
System.out.println("key:"+key.toString());
System.out.println("value:"+value);
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//使用redishash类型作为缓存存储模型 key hashkey value
redisTemplate.opsForHash().put(id.toString(),key.toString(),value);
}
//缓存中获取数据
@Override
public Object getObject(Object key) {
System.out.println("key:"+key.toString());
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//根据key 从redis的hash类型中获取数据
return redisTemplate.opsForHash().get(id.toString(),key.toString());
}
//注意:这个方法为mybatis保留方法 默认没有实现 后续改版可能实现
@Override
public Object removeObject(Object key) {
System.out.println("根据指定key删除缓存");
return null;
}
@Override
public void clear() {
System.out.println("清空缓存");
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//清空namespace
redisTemplate.delete(id.toString());//清空缓存
}
@Override
public int getSize() {
return 0;
}
}
执行一下TestUserService的delete方法:
package com.nono;
import com.nono.entity.User;
import com.nono.service.UserService;
import org.apache.ibatis.cache.Cache;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = RedisApplication.class)
@RunWith(SpringRunner.class)
public class TestUserService {
@Autowired
private UserService userService;
@Test
public void testFindAll(){
userService.findAll().forEach(user -> System.out.println(user));
System.out.println("=============");
userService.findAll().forEach(user -> System.out.println(user));
}
@Test
public void testFindOne(){
User byId = userService.findById("1");
System.out.println(byId);
System.out.println("==================");
User byId1 = userService.findById("1");
System.out.println(byId1);
}
@Test
public void testDelete(){
userService.delete("1");
}
}
com.nono.userDao这个key在redis中就没有了
3、计数和缓存量封装redisTemplate给自己用的类用
package com.nono.cache;
import com.nono.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
//自定义Redis缓存实现
public class RedisCache implements Cache {
//当前放入缓存的mapper的namespace
private final String id;
//必须存在构造方法
public RedisCache(String id){
System.out.println(id);
this.id =id;
}
//返回cache唯一标识
@Override
public String getId() {
return this.id;
}
//缓存中放入值
@Override
public void putObject(Object key, Object value) {
System.out.println("key:"+key.toString());
System.out.println("value:"+value);
//通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//使用redishash类型作为缓存存储模型 key hashkey value
getRedisTemplate().opsForHash().put(id.toString(),key.toString(),value);
}
//缓存中获取数据
@Override
public Object getObject(Object key) {
System.out.println("key:"+key.toString());
//通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//根据key 从redis的hash类型中获取数据
return getRedisTemplate().opsForHash().get(id.toString(),key.toString());
}
//注意:这个方法为mybatis保留方法 默认没有实现 后续改版可能实现
@Override
public Object removeObject(Object key) {
System.out.println("根据指定key删除缓存");
return null;
}
@Override
public void clear() {
System.out.println("清空缓存");
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//清空namespace
getRedisTemplate().delete(id.toString());//清空缓存
}
//用来计算缓存数量
@Override
public int getSize() {
//通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//获取hash中key value数量
return getRedisTemplate().opsForHash().size(id.toString()).intValue();
}
//封装redisTemplate
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
现在在写一个关于emp的redis缓存:
EmpDaoMapper.xml:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nono.dao.EmpDao">
<!--开启mybatis二级缓存-->
<cache type="com.nono.cache.RedisCache"/>
<!--findAll-->
<select id="findAll" resultType="Emp">
select id,name from t_emp
</select>
</mapper>
service:
package com.nono.service;
import com.nono.dao.EmpDao;
import com.nono.entity.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class EmpServiceImpl implements EmpService{
@Autowired
private EmpDao empDao;
@Override
public List<Emp> findAll() {
return empDao.findAll();
}
}
dao:
package com.nono.dao;
import com.nono.entity.Emp;
import java.util.List;
public interface EmpDao {
List<Emp> findAll();
}
如果emp和user表没关联,可以直接用过上面的方式。
但是一旦两个表有关联,就会出问题,比如我们更新了emp的信息,redis里user包含的emp不会跟着跟新,是旧的。
在mybatis的缓存中如何解决关联关系更新缓存信息的问题:<cache-ref />用来将多个具体关联关系查询缓存放在一起处理。
EmpDaoMapper.xml:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nono.dao.EmpDao">
<!--开启mybatis二级缓存
<cache type="com.nono.cache.RedisCache"/>-->
<cache-ref namespace="com.nono.dao.UserDao"/>
<!--findAll-->
<select id="findAll" resultType="Emp">
select id,name from t_emp
</select>
</mapper>
1、缓存优化策略
key要设计简洁一点:
算法:MD5 处理 加密
在putObject和getObject中调用此方法:
//封装一个对key进行md5处理方法
private String getKeyToMD5(String key){
return DigestUtils.md5DigestAsHex(key.getBytes());
}
package com.nono.cache;
import com.nono.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.DigestUtils;
//自定义Redis缓存实现
public class RedisCache implements Cache {
//当前放入缓存的mapper的namespace
private final String id;
//必须存在构造方法
public RedisCache(String id){
System.out.println(id);
this.id =id;
}
//返回cache唯一标识
@Override
public String getId() {
return this.id;
}
//缓存中放入值
@Override
public void putObject(Object key, Object value) {
System.out.println("key:"+key.toString());
System.out.println("value:"+value);
//通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//使用redishash类型作为缓存存储模型 key hashkey value
getRedisTemplate().opsForHash().put(id.toString(),getKeyToMD5(key.toString()),value);
}
//缓存中获取数据
@Override
public Object getObject(Object key) {
System.out.println("key:"+key.toString());
//通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//根据key 从redis的hash类型中获取数据
return getRedisTemplate().opsForHash().get(id.toString(),getKeyToMD5(key.toString()));
}
//注意:这个方法为mybatis保留方法 默认没有实现 后续改版可能实现
@Override
public Object removeObject(Object key) {
System.out.println("根据指定key删除缓存");
return null;
}
@Override
public void clear() {
System.out.println("清空缓存");
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//清空namespace
getRedisTemplate().delete(id.toString());//清空缓存
}
//用来计算缓存数量
@Override
public int getSize() {
//通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//获取hash中key value数量
return getRedisTemplate().opsForHash().size(id.toString()).intValue();
}
//封装redisTemplate
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
//封装一个对key进行md5处理方法
private String getKeyToMD5(String key){
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
key:
2、防止缓存穿透、击穿
mybatis中cache解决了缓存击穿:将数据库中没有查询到结果也进行缓存存为null。
3、防止缓存雪崩
1、永久存储【不推荐】
2、针对于不同业务数据一定要设置不同超时时间。
if(id.equals("com.nono.dao.UserDAO")){
//缓存超时 client 用户 client 员工
getRedisTemplate().expire(id.toString(),1, TimeUnit.HOURS);
}
if(id.equals("com.nono.dao.CityDAO")){
//缓存超时 client 用户 client 员工
getRedisTemplate().expire(id.toString(),30, TimeUnit.MINUTES);
}
//.....指定不同业务模块设置不同缓存超时时间