一起来学SpringBoot(十)缓存的使用
Spring Framework支持透明地向应用程序添加缓存。从本质上讲,抽象将缓存应用于方法,从而根据缓存中可用的信息减少执行次数。缓存逻辑应用透明,不会对调用者造成任何干扰。只要通过@EnableCaching
注释启用了缓存支持,Spring Boot就会自动配置缓存基础结构。下面我就介绍两个我比较常用的缓存。
JSR-107
为了统一缓存的开发规范,以及我们系统的扩展性。java发布了JSR-107缓存规范。Java Caching定义了5个核心接口,分别是CachingProvider、CacheManager、Cache和Expiry。
- CachingProvider 定义了创建,配置,获取,管理和控制多个CacheManager。一个应用可以在运行期间访问多个CachingProvider。
- CacheManager定义了创建,配置,获取,管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager 的上下文中,一个CacheManager仅被一个CachingProvider所拥有。
- Cache是一个类似Map的数据结构,并临时存储Key为索引的值。一个Cache仅被一个CacheManager 所拥有。
- Entry是一个存储在Cache中的key-value对
- Expirt每一个存储在Cache中的条目有一个定义的有效期,一旦超过这个有效期,条目就为过期状态,一旦过期,条目不可访问,更新,和删除。缓存有效期可以通过ExpiryPolicy设置。
但是呢实现JSR107对于我们快速开发项目,遇到没有实现JSR-107接口的功能时,此时集成难度较大,也并不是所有框架都集成JSR-107。
Spring缓存抽象
所以呢我们更多使用的是Spring的缓存抽象,Spring的缓存抽象的概念,基本和JSR-107是通用的。Spring从3.1开始定义了Cache和CacheManager接口来同意不同的缓存技术;并且支持使用JSR-107注解来简化我们的开发。
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合。
- Cache接口下Spring提供了各种缓存的实现,比如RedisCache,EhCacheCache,
- ConcurrentMapCache等。
- 每次调用需要缓存功能的方法的时候,Spring会检查制定参数的制定目标方法,是否被调用过,如果有,就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结构后返回给用户,下次在调用的时候直接从缓存中获取。
- 使用Spring缓存抽象的时候我们需要注意,确定方法需要缓存以及他们的缓存策略,从缓存中读取之前缓存存储的数据。
缓存注解
这里列出常用的几个概念和注解
名称 | 概念 |
---|---|
Cache | 缓存接口,定义缓存操作,实现有RedisCache,EhCacheCache,ConcurrentMapCache等等 |
CacheManager | 缓存管理器,管理各种缓存组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其返回的结果尽心缓存 |
@CacheEvict | 情况缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 |
@EnableCaching | 开启基于缓存的注解 |
serialize | 缓存数据时value序列化策略 |
keyGenerator | 缓存数据时key的生成策略 |
@CacheConfig | 统一配置本类的缓存注解的属性 |
这里列出其中几个注解的主要参数
参数名 | 主要作用 | 栗子 |
---|---|---|
value | 缓存的名称,在spring配置文件中定义,必须制定至少一个 | @Cacheable(value=“mycache”) @Cacheable(value={“cache1”,“cache2”}) |
key | 缓存的key,可以为空,如果制定要按照SpEL表达式编写,如果不制定,则按照方法的所有参数进行组合 | @Cacheable(value=“mycache”,key="#userName") |
condition | 缓存的条件,可以为空,使用SpEL编写,返回true或者false,只有为true才能进行缓存/清除操作,在调用方法之前之后都能进行判断 | @Cacheable(value=“mycache”,condition="#userName.length()>2") |
allEntries (@CacheEvict) | 是否清空所有缓存内容,缺省为fasle,如果指定为true,则方法调用后将立即清空所有缓存 | @CacheEvict(value=“mycache”,allEntries=true) |
beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为fasle,如果制定为true,则在方法还没有执行的时候就会清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CacheEvict(value=“mycache”,beforeInvocation=true) |
unless (@CachePut)(@Cacheable) | 用于否决缓存的,不等同于condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断,条件为true不会缓存,fasle才缓存 | @Cacheable(value=“mycache”,unless="#result==null") |
SpEL
其中提到了SpEL,SpEL表达式可基于上下文并通过使用缓存抽象,提供与root独享相关联的缓存特定的内置参数
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 |
#root.methodname
|
method | root对象 | 当前被调用的方法 |
#root.method.name
|
target | root对象 | 当前被调用的目标对象实例 |
#root.target
|
targetClass | root对象 | 当前被调用的目标对象的类 |
#root.targetClass
|
args | root对象 | 当前被调用的方法的参数列表 |
#root.args[0]
|
caches | root对象 | 当前方法调用使用的缓存列表 |
#root.caches[0].name
|
Argument Name | 执行上下文 | 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 |
#artsian.id
|
result | 执行上下文 | 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) |
#result
|
1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如
@Cacheable(key = "targetClass + methodName +#p0")
2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")
SpEL提供了多种运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&,||,!,and,or,not,between,instanceof |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],1,$[…] |
开始使用
首先呢加入添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
然后在启动或者配置类上加入 @EnableCaching
注解来开启缓存注解。
@EnableCaching
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application .class, args);}
}
创建一个Service来模拟对数据库的操作
package com.maoxs.service;import com.maoxs.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
@Slf4j
public class UserService {public static final Map<Integer, User> users = new HashMap<>();static {users.put(1, new User("我是快乐鱼"));users.put(2, new User( "我是忧郁猫"));users.put(3, new User( "我是昴先生"));}
}
然后是操作的实体类
@Data
public class User implements Serializable {private Integer id;private String name;public User() {}public User(String name) {this.name = name;}public User(Integer id, String name) {this.id = id;this.name = name;}
}
@Cacheable
在调用方法之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,就会返回缓存的值。否则,这个方法就会被调用,返回值会放到缓存之中。
@Cacheable(cacheNames = "user",key = "targetClass + methodName +#p0")
public User getUser(int id) {log.info("缓存中没有,从map中获取");User user = users.get(id);return user;
}
此处的value
是必需的,它指定了你的缓存存放在哪块命名空间。
此处的key
是使用的spEL表达式,参考上章。这里有一个小坑,如果你把methodName
换成method
运行会报错,观察它们的返回类型,原因在于methodName
是String
而methoh
是Method
。
此处的User
实体类一定要实现序列化public class User implements Serializable
,否则会报java.io.NotSerializableException
异常。
到这里,你已经可以运行程序检验缓存功能是否实现。
试着写一个controller 来调用此方法
@RequestMapping("/user/{id}")
public User getUser(@PathVariable int id) {return userService.getUser(id);
}
此时注意控制台,第一次访问的时候日志打印 缓存中没有,从map中获取
第二次则什么也没有显示,说明此时缓存已经生效了,结果是从缓存中取的。默认呢是使用SimpleCacheConfiguration,它在容器中注册了一个ConcurrentMapCacheManager,将缓存数据存储在了ConcurrentMap中。
深入源码,查看它的其它属性
我们打开@Cacheable
注解的源码,可以看到该注解提供的其他属性,如:
String[] cacheNames() default {}; //和value注解差不多,二选一
String keyGenerator() default ""; //key的生成器。key/keyGenerator二选一使用
String cacheManager() default ""; //指定缓存管理器
String cacheResolver() default ""; //或者指定获取解析器
String condition() default ""; //条件符合则缓存
String unless() default ""; //条件符合则不缓存
boolean sync() default false; //是否使用异步模式
这里key中提到了keyGenerator,默认是使用SimplekeyGenerator 来生成的,他的默认策略为
如果没有参数:key=new SimpleKey();
如果有一个参数: key=参数的值
如果有多个参数的方法: key=new SimpleKey(params);
当然你也可以按照自己的规则去生成key,这里我自己提供了一个自定义的使用起来呢只需要在注解中加入@Cacheable(keyGenerator = "wiselyKeyGenerator")
即可。
/*** 设置统一的生成key的方式** @return*/@Beanpublic KeyGenerator wiselyKeyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append("-");sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}
@CachePut
@CachePut
注解的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable
不同的是,它每次都会触发真实方法的调用 。简单来说就是用户更新缓存数据。但需要注意的是该注解的value
和 key
必须与要更新的缓存相同,也就是与@Cacheable
相同。
@Cacheable(cacheNames = "user", key = "#id")
public User getUser(int id) {log.info("缓存中没有,从map中获取");User user = users.get(id);return user;
}
@CachePut(cacheNames = "user", key = "#user.id")
public User updateUser(User user) {users.put(user.getId(), user);return user;
}
弄个controller测试下
@RequestMapping("/user/{id}")
public User getUser(@PathVariable int id) {return userService.getUser(id);
}
@RequestMapping("/user/{id}/{name}")
public User updateUser(@PathVariable int id, @PathVariable String name) {User user = new User(id, name);return userService.updateUser(user);
}
首先呢按id查询一个user 然后通过url更新这个用户,在根据id访问下这个用户,这是注意日志是不是没有打印
缓存中没有,从map中获取
没有打印则缓存更新成功
查看它的其它属性
String[] cacheNames() default {}; //与value二选一
String keyGenerator() default ""; //key的生成器。key/keyGenerator二选一使用
String cacheManager() default ""; //指定缓存管理器
String cacheResolver() default ""; //或者指定获取解析器
String condition() default ""; //条件符合则缓存
String unless() default ""; //条件符合则不缓存
@CacheEvict
@CachEvict
的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。
这里需要注意两个属性
属性 | 解释 | 示例 |
---|---|---|
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CachEvict(value=”testcache”,beforeInvocation=true) |
给个栗子
@Cacheable(cacheNames = "user", key = "#id")public User getUser(int id) {log.info("缓存中没有,从map中获取");User user = users.get(id);return user;}//清除一条缓存,key为要清空的数据@CacheEvict(value = "user", key = "#id")public void delect(int id) {users.remove(id);}//方法调用后清空所有缓存@CacheEvict(value = "accountCache", allEntries = true)public void delectAll() {users.clear();}//方法调用前清空所有缓存@CacheEvict(value = "accountCache", beforeInvocation = true)public void delectAllBefore() {users.clear();}
其他属性
String[] cacheNames() default {}; //与value二选一
String keyGenerator() default ""; //key的生成器。key/keyGenerator二选一使用
String cacheManager() default ""; //指定缓存管理器
String cacheResolver() default ""; //或者指定获取解析器
String condition() default ""; //条件符合则清空
@CacheConfig
当我们需要缓存的地方越来越多,你可以使用@CacheConfig(cacheNames = {"myCache"})
注解来统一指定value
的值,这时可省略value
,如果你在你的方法依旧写上了value
,那么依然以方法的value
值为准。
@Service
@Slf4j
@CacheConfig(cacheNames = {"user"})
public class UserService {// @Cacheable(cacheNames = "user", key = "#id")@Cacheable(key = "#id")public User getUser(int id) {log.info("缓存中没有,从map中获取");User user = users.get(id);return user;}
}
查看它的其它属性
String keyGenerator() default ""; //key的生成器。key/keyGenerator二选一使用
String cacheManager() default ""; //指定缓存管理器
String cacheResolver() default ""; //或者指定获取解析器
@Caching
有时候我们可能组合多个Cache注解使用,此时就需要@Caching组合多个注解标签了。
@Caching(cacheable = {@Cacheable(value = "emp",key = "#p0"),...},put = {@CachePut(value = "emp",key = "#p0"),...},evict = {@CacheEvict(value = "emp",key = "#p0"),....})public User save(User user) {....}
整合EHCACHE3.x
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。ehcache3.x与2.x的差距还是非常大的,主要区别在于3.x后使用了java的缓存规范JSR107!!!
依赖
引入jar包
<!-- JSR107 API -->
<dependency><groupId>javax.cache</groupId><artifactId>cache-api</artifactId>
</dependency>
<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
yml配置
需要说明的是默认路径为config: classpath:/ehcache.xml
入过在这个目录下这个配置可以不用写,但ehcache.xml
必须有。
spring:cache:type: jcachejcache:config: classpath:/cache/ehcache.xml
配置文件
在resources的cache目录下新建ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<configxmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xmlns='http://www.ehcache.org/v3'xmlns:jsr107='http://www.ehcache.org/v3/jsr107'xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsdhttp://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd"><cache-template name="heap-cache"><resources><heap unit="entries">2000</heap><offheap unit="MB">100</offheap></resources></cache-template><cache alias="myuser" uses-template="heap-cache"><expiry><ttl unit="seconds">40</ttl></expiry></cache></config>
然后呢使用的时候@CacheConfig(cacheNames = {"myuser"})
中的cacheNames 的名字,xml中的alias必须也有,不然会报找不到缓存名。
整合EHCACHE2.x
整合原理跟ehcache3.x一样,需要稍微改动下
依赖
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
yml配置
spring:cache:type: ehcacheehcache:config: classpath:/cache/ehcache.xml
配置文件
<ehcache><!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存path:指定在硬盘上存储对象的路径path可以配置的目录有:user.home(用户的家目录)user.dir(用户当前的工作目录)java.io.tmpdir(默认的临时目录)ehcache.disk.store.dir(ehcache的配置目录)绝对路径(如:d:\\ehcache)查看路径方法:String tmpDir = System.getProperty("java.io.tmpdir");--><diskStore path="java.io.tmpdir" /><!--defaultCache:默认的缓存配置信息,如果不加特殊说明,则所有对象按照此配置项处理maxElementsInMemory:设置了缓存的上限,最多存储多少个记录对象eternal:代表对象是否永不过期 (指定true则下面两项配置需为0无限期)timeToIdleSeconds:最大的发呆时间 /秒timeToLiveSeconds:最大的存活时间 /秒overflowToDisk:是否允许对象被写入到磁盘说明:下列配置自缓存建立起600秒(10分钟)有效 。在有效的600秒(10分钟)内,如果连续120秒(2分钟)未访问缓存,则缓存失效。就算有访问,也只会存活600秒。--><defaultCache maxElementsInMemory="10000" eternal="false"timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="true" /><cache name="myCache" maxElementsInMemory="10000" eternal="false"timeToIdleSeconds="120" timeToLiveSeconds="600" overflowToDisk="true" />
</ehcache>
同样呢也是这样使用@CacheConfig(cacheNames = {"myCache"})
中的cacheNames 的名字,xml中的alias必须也有,不然会报找不到缓存名。
整合Redis
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性
依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
当你导入这一个依赖时,SpringBoot的CacheManager就会使用RedisCache。
存入redis呢默认的缓存序列化策略为jdk序列化如果想更改怎么办呢,这里呢我们注入了一个RedisTemplate 设置了里面的序列化,然后呢把他注入到redisCacheManger里就可以了。
@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);serializer.setObjectMapper(mapper);template.setValueSerializer(serializer);//使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.afterPropertiesSet();return template;}@Beanpublic RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);}
当然也可以这样
@Beanpublic RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)) // 设置缓存有效期一小时.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(redisCacheConfiguration).build();}
代码形势
除了注解之外呢,想自己用代码的形势来使用缓存,其实是可以的,只用注入响应的cacheManager就可以啦,然后调用。举个栗子
public class RedisCacheTest extends SpringbootCacheApplicationTests {@Autowiredprivate RedisCacheManager redisCacheManager;@Testpublic void managerTest() {Cache cache = redisCacheManager.getCache("fulin");cache.put("1", "我看不清楚");Cache.ValueWrapper valueWrapper = cache.get("1");System.out.println(valueWrapper.get());}
}
这样可以的,自己用代码控制缓存。
这里顺便说一下那个啥 jcache和ehcache的cacaheManager 的个性化注入
@Beanpublic JCacheCacheManager jCacheCacheManager() throws URISyntaxException {CachingProvider provider = Caching.getCachingProvider();JCacheCacheManager jCacheCacheManager = new JCacheCacheManager();javax.cache.CacheManager eh107CacheManager = provider.getCacheManager(getClass().getResource("/cache/ehcache.xml").toURI(), getClass().getClassLoader());jCacheCacheManager.setCacheManager(eh107CacheManager);return jCacheCacheManager;}
/*** ehcache 主要的管理器* @param bean* @return*/
@Bean
public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean){return new EhCacheCacheManager(bean.getObject());
}
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean(){EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();factoryBean.setConfigLocation(new ClassPathResource("/cache/ehcache.xml"));factoryBean.setShared(true);return factoryBean;
}
好了代码和注解看你取舍了,这边我给个工具类,能省略不少操作
package com.maoxs;import org.springframework.cache.Cache;/*** fulin缓存抽象类*/
public abstract class AbstractCacheSupport {/*** 获取缓存内容** @param cache* @param key* @return*/protected Object getFromCache(Cache cache, String key) {final Cache.ValueWrapper valueWrapper = cache.get(key);return null == valueWrapper ? null : valueWrapper.get();}/*** 设置缓存数据** @param cache* @param key* @param value* @return*/protected boolean putCache(Cache cache, String key, Object value) {if (null == value) {return false;}cache.put(key, value);return true;}/*** 删除缓存数据** @param cache* @param key* @return*/protected boolean evictFromCache(Cache cache, Object key) {if (null == key) {return false;}cache.evict(key);return true;}
}
本博文是基于springboot2.x 如果有什么不对的请在下方留言。
… ↩︎
一起来学SpringBoot(十)缓存的使用相关推荐
- 跟我学Springboot开发后端管理系统6:缓存框架Caffeine
Caffeine是一个基于Java8的高性能缓存框架,号称趋于完美.Caffeine受启发于Guava Cache的API,使用API和Guava是一致的.它借鉴了Guava Cache和Concur ...
- 跟我学Springboot开发后端管理系统9:AOP+logback+MDC日志输出
MDC介绍 在比较复杂的应用中,一个请求需要走很多个方法的处理,怎么样才能快速查找一个请求的全部日志呢.在分布式系统中,我们可以用链路追踪,比如zipkin.skywalking去快速查找日志,从而定 ...
- 跟我学Springboot开发后端管理系统8:Matrxi-Web权限设计实现
上篇文章讲述了Matrix-web整体实现的权限控制的思路.现在来回顾一下: 首先,用户需要登录,填用户名.密码,后端接收到登录请求,进行用户.密码的校验,校验成功后则根据用户名生成Token,并返回 ...
- 跟我学Springboot开发后端管理系统7:Matrxi-Web权限设计
Matrxi-Web权限设计 对于一个后端系统来说,权限是基础设施,是安全保障.没有权限,系统可能随时面临各种风险,所以权限设计对后端系统来说至关重要.在Javaweb开发中,有很多权限开发的框架,比 ...
- 跟着大宇学SpringBoot目录贴
与君共勉 故不积跬步,无以至千里.不积小流,无以成江海.骐骥一跃不能十步,驽马十驾功在不舍. 谁都是从HelloWorld开始学习的,即使是架构师,也是一样. 纯干货,重实战,贵在积累. 从头开始学S ...
- 跟我学Springboot开发后端管理系统5:数据库读写分离
在Matrix-web后台管理系统中,使用到了数据库的读写分离技术.采用的开源的Sharding-JDBC作为数据库读写分离的框架.Matrix-Web后台数据库这一块采用的技术栈如下: 使用Myba ...
- 跟我学Springboot开发后端管理系统4:数据库连接池Druid和HikariCP
上一篇文章主要讲解了如何再Matrix-Web中使用Mybatis-Plus,Mybatis-Plus作为Orm框架,连接数据库需要连接数据库的依赖.WEB 系统高并发环境下,频繁的进行数据库连接操作 ...
- 跟我学Springboot开发后端管理系统3:Mybatis-Plus实战2
在上一篇文章讲述了如何使用Mybatis-plus自动生成代码,生成的代码具有单表操作数据库的能力,节约了开发时间.然后讲述了如何在Spring Boot中整合Mybatis-Plus.这篇文章讲述如 ...
- 跟我学Springboot开发后端管理系统2:Mybatis-Plus实战
在Matrix-Web项目中使用Mybatis-Plus作为操作数据库的ORM框架.在市面上常用的ORM框架有hibernetes.mybatis.JPA等,那么为什么选择Mybatis-Plus呢? ...
- jpa mysql乐观锁_【快学springboot】8.JPA乐观锁OptimisticLocking
介绍 当涉及到企业应用程序时,正确地管理对数据库的并发访问是至关重要的.为此,我们可以使用Java Persistence API提供的乐观锁定机制.它导致在同一时间对同一数据进行多次更新不会相互干扰 ...
最新文章
- 【C++】多线程与原子操作和无锁编程【五】
- 中科院DeepMind联手,用深度学习揭示大脑如何识别人脸|Nature子刊
- 【Java】全站编码过滤器GenericEncodingFilter代码与配置
- 非常详细的Django使用Token(转)
- python高级-异常(13)
- 【转】VC++计算当前时间点间隔N天的时间(不使用CTimeSpan类)
- opencv4版本和3版本_Spring Boot 太狠了,一口气发布了 3 个版本!
- C++封装Detours库挂钩函数
- 3U VPX高性能数据处理板(XC7K325T FMC载板)
- 决策树与随机森林Adaboost算法
- python提取pdf内容_别再问如何用Python提取PDF内容了!
- 酷客多小程序百城宣讲会-郑州站圆满成功
- linux实验二文件与文件夹操作
- ae使用计算机不支持的文字,AE软件使用字体出现错误83 ::2如何解决?
- 【强化学习】Actor-Critic(演员-评论家)算法详解
- 《前端开发者的进阶之路》
- Java中Map.Entry详解
- 国足晋级12强 | 爬取《NBA30支球队》“现役球员信息”,再来看看篮球吧!
- struts2框架的总结
- 硬币组合问题python_动态规划之硬币组合问题
热门文章
- Dell H300/6i/6iR/H700/H800阵列卡配置(转)
- 区块链与大数据结合分析
- DesignWare 加密文件 综合成GTECH 以便FPGA使用
- html5显示文件后缀,如何显示文件后缀名称
- 最优秀的6410开发板全球震撼首发!
- LoadLibrary failed with error 1114:动态链接库(DLL)初始化例程失败 解决方法
- 爱丁堡 ANLP-Lecture 1(NLP Structure Morphology, Ambiguity, Part of Speech)
- 2023南宁师范大学计算机考研信息汇总
- 开始接触tinyOS
- shell批处理 FFmpeg 批量转换格式 webm转MP4