一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。 一级缓存是默认开启的不用配置。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的开启(实体类必须序列化),然后在配置文件里面配置。
mybatis-plus是基于mybatis进行的增强,所以mybatis-plus缓存机制的了解,我们可以拿mybatis进行举例。
mybatis-plus 在springboot 中的核心配置如下
# mybatis配置
mybatis-plus:
# 全局配置
global-config:
db-config:
# 逻辑删除全局字段 (默认无 设置会自动扫描实体字段)
logic-delete-field: delFlag
# 逻辑删除全局值(默认 1、表示已删除)
logic-delete-value: 1
# 逻辑未删除全局值(默认 0、表示未删除)
logic-not-delete-value: 0
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
#设置当查询结果值为null时,同样映射该查询字段给map。
call-setters-on-nulls: true
cache-enabled: true #开启二级缓存
mapper-locations: classpath*:/mapper/*.xml
type-aliases-package: com.isoftstone.manage.entity
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
<version>2.2.2.RELEASEversion>
dependency>
package com.isoftstone.manage.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @author chaoyangzhang
* @version 1.0
* @date 2021/6/7 18:36
* mybatis-plus 代码生成器
*/
public class CodeGenerator {
private static String projectPath = System.getProperty("user.dir");
private static String DriverName ="com.mysql.cj.jdbc.Driver";
private static String Url = "jdbc:mysql://127.0.0.1:3306/transcribe?useUnicode=true&useSSL=false&characterEncoding=utf8";
private static String username = "root";
private static String password = "root";
private static String MICROSERVICE = "";
/**
*
* 读取控制台内容
*
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
/**
* 全局配置
* @return
*/
public static GlobalConfig getGlobalConfig(){
GlobalConfig gc = new GlobalConfig();
// MICROSERVICE = scanner("微服务名");
// gc.setOutputDir(projectPath + "/" + MICROSERVICE + "/src/main/java");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("Mr.Zhang");
gc.setOpen(false);
gc.setFileOverride(false); //如果存在文件是否覆盖
gc.setServiceName("%sService"); //去掉service前面的I前缀
gc.setIdType(IdType.AUTO); //设置主键策略为自增
gc.setDateType(DateType.ONLY_DATE); //设置日期格式
//gc.setSwagger2(true); // 实体属性 Swagger2 注解
return gc;
}
/**
* 数据源配置
* @return
*/
public static DataSourceConfig getDataSourceConfig(){
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDriverName(DriverName);
dsc.setUrl(Url);
dsc.setUsername(username);
dsc.setPassword(password);
dsc.setDbType(DbType.MYSQL);
return dsc;
}
/**
* 包配置
* @return
*/
public static PackageConfig getPackageConfig(){
PackageConfig pc = new PackageConfig();
//将来代码会生成于com.isoftStone.manage.user的目录下
//自定义输入模块名称
// pc.setModuleName(scanner("模块名称"));
pc.setParent("com.xxx.manage");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setController("controller");
pc.setService("service");
return pc;
}
/**
* 策略配置
* @return
*/
public static StrategyConfig getStrategyConfig(){
StrategyConfig strategy = new StrategyConfig();
//下划线转驼峰命名
strategy.setNaming(NamingStrategy.underline_to_camel);
//列名下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
//自动支持Lombok注解
strategy.setEntityLombokModel(true);
//设置实体类逻辑删除字段
strategy.setLogicDeleteFieldName("deleted");
//设置自动填充字段
TableFill createDate = new TableFill("oper_time", FieldFill.INSERT);
// TableFill modifyDate = new TableFill("modify_date", FieldFill.INSERT_UPDATE);
List<TableFill> list = new ArrayList<>();
list.add(createDate);
// list.add(modifyDate);
strategy.setTableFillList(list);
//设置乐观锁
strategy.setVersionFieldName("version");
//设置下划线命名 localhost:8080/hello_id_2
strategy.setControllerMappingHyphenStyle(true);
//设置restful的驼峰命名
strategy.setRestControllerStyle(true);
strategy.setSuperEntityClass(BaseDO.class);
// 公共父类
//strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
//strategy.setSuperEntityColumns("id");
//strategy.setInclude("user","product"); 设置只映射生成两张表:user表和product表
//配置生成的表由控制台输入
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
return strategy;
}
/***
* 入口
* @param args
*/
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 1.全局配置
GlobalConfig gc = CodeGenerator.getGlobalConfig();
mpg.setGlobalConfig(gc);
// 2.数据源配置
DataSourceConfig dsc = CodeGenerator.getDataSourceConfig();
mpg.setDataSource(dsc);
// 3.包配置
PackageConfig pc = CodeGenerator.getPackageConfig();
mpg.setPackageInfo(pc);
// 4.策略配置
StrategyConfig strategy = CodeGenerator.getStrategyConfig();
mpg.setStrategy(strategy);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置,自定义配置会被优先输出
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath +"/"+ MICROSERVICE + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板,不在mapper中新建xml目录并生成xml文件
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
一级缓存是默认打开的,但是在测试过程中,发现mybatis的一级缓存没有起作用,失效了。经过调研,发现是由于以下原因引起的:
通过以上步骤后发现,同一线程里面两次查询同一数据所使用的sqlsession是不相同的,所以,给人的印象就是结合spring后,mybatis的一级缓存失效了。
在SqlSessionTemplate中执行SQL的session都是通过sqlSessionProxy来,sqlSessionProxy的生成在构造函数中赋值,如下:
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
sqlSessionProxy通过JDK的动态代理方法生成的一个代理类,主要逻辑在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,commit,关闭
代码如下:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 每次执行前都创建一个新的sqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
因为每次都进行创建,所以就用不上sqlSession的缓存了.
对于开启了事务为什么可以用上呢, 跟入getSqlSession方法
如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 首先从SqlSessionHolder里取出session
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
在里面维护了个SqlSessionHolder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了。
yml配置可以参考上面。
@EnableCaching 注解
@EnableTransactionManagement
@EnableCaching
@MapperScan("com.isoftstone.manage.mapper")
@SpringBootApplication
public class TranscribeApplication {
public static void main(String[] args) {
SpringApplication.run(TranscribeApplication.class, args);
}
}
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @Author: zhangchaoyang
* @Date: 2021/3/23 18:01
* @Version 1.0
* @Description redis配置文件,重新编排序列化和兼容mybatis缓存
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
* 重写Redis序列化方式,使用Json方式:
* 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。
* Spring Data JPA为我们提供了下面的Serializer:
* GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
* 在此我们将自己配置RedisTemplate并定义Serializer。
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置值(value)的序列化采用FastJsonRedisSerializer。
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//缓存管理器
@Bean
public CacheManager cacheManager(RedisConnectionFactory lettuceConnectionFactory) {
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
// 设置缓存管理器管理的缓存的默认过期时间
defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofMinutes(60))
// 不缓存空值
.disableCachingNullValues();
Set<String> cacheNames = new HashSet<>();
cacheNames.add("my-redis-cache1");
// 对每个缓存空间应用不同的配置
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("my-redis-cache1", defaultCacheConfig.entryTtl(Duration.ofMinutes(50)));
RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
.cacheDefaults(defaultCacheConfig)
.initialCacheNames(cacheNames)
.withInitialCacheConfigurations(configMap)
.build();
return cacheManager;
}
}
关于redis序列化的说明,可以参考https://blog.csdn.net/CSDN_zcy_my/article/details/121820969
/**
*
* 服务类
*
*
* @author chaoyang
* @since 2021-10-27
*/
@CacheConfig(cacheNames = "user")
public interface TblUserService extends IService<TblUser> {
@CachePut(key = "#tblUser.id")
ActionResult<TblUser> register(TblUser tblUser);
@Cacheable(key = "#id")
ActionResult<TblUser> info(Long id);
TblUser userInfo(Long id);
@CacheEvict(key = "#userPasswordVO.id")
ActionResult<?> changePassword(UserPasswordVO userPasswordVO);
@CacheEvict(key = "#id")
ActionResult<?> resetPassword(Long id);
@CacheEvict(key = "#tblUser.id")
ActionResult<?> updateUser(TblUser tblUser);
@CacheEvict(key = "#id")
ActionResult<?> updateFrozen(Long id);
TblUser getUserInfo(HttpServletRequest request);
boolean updateUserCache(TblUser tblUser, String... type);
boolean updateUserListCache(TblUser tblUser);
ActionResult<?> relationPayonner(String type, String id, HttpServletRequest request);
}
二级缓存使用之后,初次会执行数据库,并缓存进redis,再次访问将不再执行sql,直接从redis缓存中读取。
https://www.cnblogs.com/gmhappy/p/11864104.html