SpringBoot之JPA+Redis

1、Redis是什么?

No(Not Only)SQL数据库,内存数据库,提高数据访问的效率.

可用于缓存,事件发布或订阅,高速队列等场景。该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。

什么场景用到了redis? 验证码。 有效期.缓存.

长期不变的数据:省市区级联[MySQL] ->[Redis]->[直接存储在前端]

2、Redis 的一些优点

  • 异常快 - Redis 非常快,每秒可执行大约 110000 次的设置(SET)操作,每秒大约可执行 81000 次的读取/获取(GET)操作。
  • 支持丰富的数据类型 - Redis 支持开发人员常用的大多数数据类型,例如列表,集合,排序集和散列等等。这使得 Redis 很容易被用来解决各种问题,因为我们知道哪些问题可以更好使用地哪些数据类型来处理解决。
  • 操作具有原子性 - 所有 Redis 操作都是原子操作,这确保如果两个客户端并发访问,Redis 服务器能接收更新的值。
  • 多实用工具 - Redis 是一个多实用工具,可用于多种用例,如:缓存,消息队列(Redis 本地支持发布/订阅),应用程序中的任何短期数据,例如,web应用程序中的会话,网页命中计数等。

3、Redis 5种数据结构类型

结构类型结构存储的值结构的读写能力
String可以是字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作;对象和浮点数执行自增(increment)或者自减(decrement)
List一个链表,链表上的每个节点都包含了一个字符串从链表的两端推入或者弹出元素;根据偏移量对链表进行修剪(trim);读取单个或者多个元素;根据值来查找或者移除元素
Set包含字符串的无序收集器(unorderedcollection),并且被包含的每个字符串都是独一无二的、各不相同添加、获取、移除单个元素;检查一个元素是否存在于某个集合中;计算交集、并集、差集;从集合里卖弄随机获取元素
Hash包含键值对的无序散列表添加、获取、移除单个键值对;获取所有键值对
Zset字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素

4、Redis的基本使用

步骤一: 新建springboot项目

此时pom.xml中导入的主要的包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
复制代码

步骤二:配置yml文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/joint_force?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 795200
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

  redis:
    host: 42.193.104.128
    port: 6379
    username:
    password: 795200
复制代码

步骤三:新建实体类添加JPA注解

@Data
@AllArgsConstructor
@NoArgsConstructor

@Entity
@Table(name = "article")
public class Article implements Serializable {

    @Id
    @GeneratedValue
    @Column(name = "a_id")
    private int AId;
    @Column(name = "article_title")
    private String articleTitle;
    @Column(name = "article_content")
    private String articleContent;
    @Column(name = "head_image")
    private String headImage;
    @Column(name = "article_author")
    private String articleAuthor;
    @Column(name = "type_number")
    private int typeNumber;

    private int pageviews;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "is_state")
    private int isState;
}
复制代码

步骤四:新建Dao接口

public interface ArticleDao extends JpaRepository<Article, Integer>, JpaSpecificationExecutor<Article>, Serializable {

    List<Article> findByArticleTitleContaining(String keywords);


    @Query("select art from Article art where art.articleTitle like %?1% or art.articleContent like %?1%")
    Page<Article> findByLike(String keywords, Pageable pageable);



    List<Article> findByAId(Integer Aid);

}
复制代码

步骤五:1.1 编写服务类接口和实现

public interface ArticleService {

    List<Article> findByAId(Integer Aid);
}
复制代码

接口的实现类中配置注解 @Cacheable(cacheNames = "com.singerw.serviceImpl.findByAId")表示启用缓存,并命名(命名可自定义,按要求命名即可);

@Service
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private ArticleDao articleDao;

    @Override
    @Cacheable(cacheNames = "findByAId",key = "#Aid")
    public List<Article> findByAId(Integer Aid) {
        return articleDao.findByAId(Aid);
    }
}
复制代码

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。

@Cacheable可以指定三个属性:

  • value
  • key
  • condition

@Cacheable(value=”accountCache”):当调用这个方法的时候,会从一个名叫 accountCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。这里的缓存中的key就是参数 Aidvalue 就是 Article对象。

@Override
@Cacheable(cacheNames = "findByAId",key = "#Aid")
public List<Article> findByAId(Integer Aid) {
    return articleDao.findByAId(Aid);
}
复制代码

步骤六:启动类添加@EnableCaching注解

@SpringBootApplication
@EnableCaching
public class RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }
}
复制代码

步骤七:启动测试

第一次访问该接口,会查询我们的mysql来获取记录,查询成功后会将数据存储再redis中;则:

第二次访问该接口,没有查询我们的mysql,而是查询缓存数据库redis中的记录。

5、Redis常用注解小结

@CacheConfig

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
复制代码

默认key的生成按照以下规则:

  • 如果没有参数,则使用0作为key

  • 如果只有一个参数,使用该参数作为key

  • 如果又多个参数,使用包含所有参数的hashCode作为key

自定义key的生成:

当目标方法参数有多个时,有些参数并不适合缓存逻辑

比如:

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {...}
复制代码

其中checkWarehouse,includeUsed并不适合当做缓存的key.针对这种情况,Cacheable 允许指定生成key的关键属性,并且支持支持SpringEL表达式。(推荐方法示例)

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {...}

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {...}

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {...}

@Cacheable(cacheNames="books", key="#map['bookid'].toString()")
public Book findBook(Map<String, Object> map) {...}
复制代码

@Cacheable

主要的参数:

  • value

value是缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:

@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}
复制代码

key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:

@Cacheable(value=”testcache”,key=”#userName”)
复制代码

condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:

@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
复制代码

@CachePut 的作用 主要针对方法配置,如果缓存需要更新,且不干扰方法的执行,可以使用注解@CachePut。@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CachePut @Cacheable 使用,注意返回值需要一致;

@CacheEvict @Cacheable 不用管返回值

注意:如果使用是@CachePut那么需要和@Cacheable的方法的返回值是一致的;

@CachePut

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
复制代码

注意:应该避免@CachePut 和 @Cacheable同时使用的情况。

6、编写压力测试类

步骤一:引入依赖

<!-- https://mvnrepository.com/artifact/org.databene/contiperf -->
<dependency>
    <groupId>org.databene</groupId>
    <artifactId>contiperf</artifactId>
    <version>2.3.4</version>
    <scope>test</scope>
</dependency> 
复制代码

步骤二:编写测试类

单元测试类里面使用 @Rule 注解激活 ContiPerf

@Rule
public ContiPerfRule i = new ContiPerfRule();
复制代码

在具体测试方法上使用 @PerfTest 指定调用次数/线程数,使用 @Required 指定每次执行的最长时间/平均时间/总时间等

@Test
@PerfTest(invocations = 30000, threads = 20)
@Required(max = 1200, average = 250, totalTime = 60000)
public void test1() throws Exception {
    snowflakeSequence.nextId();
}
复制代码

注意我们使用的是junt4,在pom.xml配置文件内已经添加了性能测试的依赖contiperf(康迪泼妇),测试从 Redis内读取数据与 数据库内读取输出的性能差异。

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootJpaRedisApplicationTests_Contiperf {

    @Rule
    public ContiPerfRule  cr = new ContiPerfRule();

    @Autowired
    private ArticleService articleService;

    /**
    * invocations 1w次查询,200个线程同时操作getArticle方法
    */
    @Test
    @PerfTest(invocations = 10000,threads = 200)
    public void contextLoads() {

        List<Article> list= articleService.getArticle();

        list.forEach(System.out::println);
    }

}
复制代码

​ 测试结论:测试有加入@Cacheable缓存时所用的时间和没有加入缓存时所用的时间进行对比,在数据库很多或并发性很高的情况下,加入缓存的测试结果要快得多。大致得到的一个效果是,数据量大,并发比较多,加上redis效率比较高的.

注:PerfTest参数

  • @PerfTest(invocations = 300):执行300次,和线程数量无关,默认值为1,表示执行1次;
  • @PerfTest(threads=30):并发执行30个线程,默认值为1个线程;
  • @PerfTest(duration = 20000):重复地执行测试至少执行20s。

2)Required参数

  • @Required(throughput = 20):要求每秒至少执行20个测试;
  • @Required(average = 50):要求平均执行时间不超过50ms;
  • @Required(median = 45):要求所有执行的50%不超过45ms;
  • @Required(max = 2000):要求没有测试超过2s;
  • @Required(totalTime = 5000):要求总的执行时间不超过5s;
  • @Required(percentile90 = 3000):要求90%的测试不超过3s;
  • @Required(percentile95 = 5000):要求95%的测试不超过5s;
  • @Required(percentile99 = 10000):要求99%的测试不超过10s;
  • @Required(percentiles = "66:200,96:500"):要求66%的测试不超过200ms,96%的测试不超过500ms。
分类:
后端
标签: