Spring的缓存抽象
- 为 Java 方法增加缓存,缓存执行结果
当第一次调用完该方法之后,Spring会把执行的结果缓存起来,当下一次再次调用的时候,直接取缓存里面的数据 - 支持ConcurrentMap、EhCache、Caffeine、JCache(JSR-107)、Redis
Ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便
JCACHE是一种即将公布的标准规范(JSR 107),说明了一种对Java对象临时在内存中进行缓存的方法,包括对象的创建、共享访问、假脱机(spooling)、失效、各JVM的一致性等。它可被用于缓存JSP内最经常读取的数据,如产品目录和价格列表。利用JCACHE,多数查询的反应时间会因为有缓存的数据而加快(内部测试表明反应时间大约快15倍)。、
本章使用Redis。 - 接口
org.springframework.cache.Cache
org.springframework.cache.CacheManager
需要缓存的数据
将长久不变的信息,一天之内不会变的,会在JVM内部做一个缓存,因为每次访问集中式的分布式缓存,也会用一定的网络上的开销,如果对于我们来说,这个数据,它很长时间不会变,而且它的变化我可以接收,它有一定的延时,那么这种我们可以放到JVM内部,设置一个过期的时间,让他自动过期之后,再去从后面的后端捞取数据。
如果我希望说在整个集群里面,都有一个缓存,这个缓存要求在整个集群内部,访问的时候都具备一定的一致性的,也就是说,一旦这个值发生了变化,所有机器上必须取到同一个缓存的值。不能说A机器取到一个新值,B机器取到一个旧值。这时候,就要使用Redis的分布式缓存,将缓存放到Redis中。
一般来说,对于读一次写一次的数据,就没有必要放到缓存里面了。
Spring中基于注解的缓存
@EnableCaching
开启整个缓存的支持
- @Cacheable
执行这个方法,如果这个方法的结果在缓存里面,直接从缓存里面取,没有的话,执行完方法之后,将结果放到缓存中 - @CacheEvict
做缓存的清理 - @CachePut
不管方法的执行情况,直接做一个缓存的设置 - @Caching
直接对 缓存清理、缓存设置 等方法做一个打包,我们可以放入多个操作。 - @CacheConfig
对缓存做一个设置,比如说 名字
通过 Spring Boot 配置 Redis 缓存
配置文件
spring.cache.type=redis
spring.cache.cache-names=coffee
spring.cache.redis.time-to-live=5000
#缓存的生命周期
spring.cache.redis.cache-null-values=false
pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@MappedSuperclass //标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中
//标注为@MappedSuperclass的类不能再标注 @Entity或 @Table注解,也无需实现序列化接口
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity implements Serializable {
@Id //主键我们通过Id的注解来定义
@GeneratedValue(strategy = GenerationType.IDENTITY)
//@GeneratedValue注解存在的意义主要就是为一个实体生成一个唯一标识的主键、@GeneratedValue提供了主键的生成策略。
// @GeneratedValue注解有两个属性,分别是strategy和generator
//AUTO主键由程序控制,是默认选项 ,不设置就是这个
//IDENTITY 主键由数据库生成, 采用数据库自增长, Oracle不支持这种方式
//SEQUENCE 通过数据库的序列产生主键, MYSQL 不支持
//Table 提供特定的数据库产生主键, 该方式更有利于数据库的移植
//generator
//generator属性的值是一个字符串,默认为"",其声明了主键生成器的名称
//(对应于同名的主键生成器@SequenceGenerator和@TableGenerator)
private Long id;
@Column(updatable = false) //用来标识实体类中属性与数据表中字段的对应关系 updatable=false 不包含 UPDATE
@CreationTimestamp //创建时间戳 表示该字段为创建时间时间字段
private Date createTime;
@UpdateTimestamp
private Date updateTime; //更新时间戳 表示该字段为修改时间时间字段
}
``
```java
@Entity //标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的数据库表。如声明一个实体类 Customer,它将映射到数据库中的 customer 表上
@Table(name = "T_COFFEE")
//当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行。
//@Table 标注的常用选项是 name,用于指明数据库的表名@Table标注还有一个两个选项 catalog 和 schema 用于设置表所属的数据库目录或模式,通常为数据库名。uniqueConstraints 选项用于设置约束条件,通常不须设置
@Builder //添加构造器 构造Builder模式的结构。通过内部类Builder()进行构建对象。
@Data //提供 set get 方法 =@Setter+@Getter+@EqualsAndHashCode+@NoArgsConstructor
@ToString(callSuper = true) //提供tostring方法 用自己的属性和从父类继承的属性
@NoArgsConstructor
@AllArgsConstructor
public class Coffee extends BaseEntity implements Serializable {
// Serializable接口实现序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
private String name;
@Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyMinorAmount",
parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")}) //记录一个对象的类型
private Money money;
}
@Entity
@Table(name = "T_ORDER")
@Data
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CoffeeOrder extends BaseEntity implements Serializable {
private String customer;
@ManyToMany //多对多关系
@JoinTable(name = "T_ORDER_COFFEE")//关联表T_ORDER_COFFEE
@OrderBy("id") //因为查询出来的数据是混乱的,所以需要根据id来排序
private List<Coffee> items;
@Enumerated //打开枚举 实体Entity中通过@Enumerated标注枚举类型,例如将CustomerEO实体中增加一个CustomerType类型的枚举型属性
@Column(nullable = false) //该字段不能为空
private OrderState state;
}
public enum OrderState {
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
}
public interface CoffeeOrderRepository extends JpaRepository<CoffeeOrder,Long> {
}
public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
}
@Slf4j
@Service //@Service用来表示一个业务层bean 如果一个类带了@Service注解,将自动注册到Spring容器,不需要再在applicationContext.xml文件定义bean了,类似的还包括@Component、@Repository、@Controller。
@Transactional //开启事务的注解
public class CoffeeOrderService {
@Autowired //取出容器中的bean
private CoffeeOrderRepository orderRepository;
public CoffeeOrder createOrder(String customer, Coffee... coffee) {
CoffeeOrder order = CoffeeOrder.builder()
.customer(customer)
.items(new ArrayList<>(Arrays.asList(coffee)))
.state(OrderState.INIT)
.build(); //新建一个coffeeorder
CoffeeOrder saved = orderRepository.save(order); //保存到数据库
log.info("New Order: {}", saved);
return saved;
}
public boolean updateState(CoffeeOrder order, OrderState state) { //更新订单状态
if (state.compareTo(order.getState()) <= 0) {
log.warn("Wrong State order: {}, {}", state, order.getState());
return false;
}
order.setState(state);
orderRepository.save(order);
log.info("Updated Order: {}", order);
return true;
}
}
@Slf4j
@Service
@CacheConfig(cacheNames = "coffee") //对缓存做一个设置,比如说 名字
public class CoffeeService {
@Autowired
private CoffeeRepository coffeeRepository;
@Cacheable //执行这个方法,如果这个方法的结果在缓存里面,直接从缓存里面取,没有的话,执行完方法之后,将结果放到缓存中
public List<Coffee> findAllCoffee() {
return coffeeRepository.findAll();
}
@CacheEvict //做缓存的清理
public void reloadCoffee() {
}
public Optional<Coffee> findOneCoffee(String name) {
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", exact().ignoreCase());
//ExampleMatcher实例查询三要素
//实体对象:在ORM框架中与Table对应的域对象,一个对象代表数据库表中的一条记录,如上例中User对象,对应user表。在构建查询条件时,一个实体对象代表的是查询条件中的“数值”部分。如:要查询姓“X”的客户,
//实体对象只需要存储条件值“X”。
//匹配器:ExampleMatcher对象,它是匹配“实体对象”的,表示了如何使用“实体对象”中的“值”进行查询,它代表的是“查询方式”,解释了如何去查的问题。如:要查询姓“X”的客户,即姓名以“X”开头的客户,该对象就表示了“
//以某某开头的”这个查询方式,如上例中:
//withMatcher(“userName”, GenericPropertyMatchers.startsWith())
//实例:即Example对象,代表的是完整的查询条件。由实体对象(查询条件值)和匹配器(查询方式)共同创建。最终根据实例来findAll即可。
Optional<Coffee> coffee = coffeeRepository.findOne(
Example.of(Coffee.builder().name(name).build(), matcher));
log.info("Coffee Found: {}", coffee);
return coffee;
}
}
@Slf4j
@EnableTransactionManagement //添加开启事务的注解
@SpringBootApplication //@SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。
//@Configuration:提到@Configuration就要提到他的搭档@Bean。使用这两个注解就可以创建一个简单的spring配置类,可以用来替代相应的xml配置文件。
//@Configuration的注解类标识这个类可以使用Spring IoC容器作为bean定义的来源。@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册为在Spring应用程序上下文中的bean。
//@EnableAutoConfiguration:能够自动配置spring的上下文,试图猜测和配置你想要的bean类,通常会自动根据你的类路径和你的bean定义自动配置。
//@ComponentScan:会自动扫描指定包下的全部标有@Component的类,并注册成bean,当然包括@Component下的子注解@Service,@Repository,@Controller。
@EnableJpaRepositories //@EnableJpaRepositories用来扫描和发现指定包及其子包中的Repository定义
@EnableCaching(proxyTargetClass = true) //拦截类的执行 即aop的拦截 用来判断方法是直接执行还是从缓存中取数据
public class SpringBucksApplication implements ApplicationRunner {
@Autowired
private CoffeeService coffeeService;
public static void main(String[] args) {
SpringApplication.run(SpringBucksApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("Count: {}", coffeeService.findAllCoffee().size());
for (int i = 0; i < 5; i++) {
log.info("Reading from cache.");
coffeeService.findAllCoffee();
}
Thread.sleep(5_000);
log.info("Reading after refresh.");
coffeeService.findAllCoffee().forEach(c -> log.info("Coffee {}", c.getName()));
}
}