Java学习笔记-Day85 Spring Boot框架(五)


一、Spring Boot整合Redis数据库(JPA)

1、操作步骤


(1)添加Redis的坐标到pom.xml文件中。

<!--增加了redis的支持-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)添加Redis的配置到application.properties文件中。

## 配置devtools的自动启动
spring.devtools.restart.enabled= true
## 设置自动重启的目录为 src/main/java
spring.devtools.restart.additional-paths= src/main/java
## 设置不重启的目录
#spring.devtools.restart.exclude= WEB-INF/*

(3)添加@Cacheable注解(表示启动缓存,并对键命名)在Service实现类的方法前,添加@EnableCaching(表示允许缓存)在启动类前。

  • Service实现类的方法
@Service
public class GoodsServiceImpl implements GoodsService {
    
    

    @Autowired
    GoodsDao goodsDao;

    /**
     * 分页查询全部商品
     */
    @Override
    @Cacheable(cacheNames = "com.etc.ssm.service.impl.showGoods")
    public Page<Goods> showGoods(int page, int pageSize, String keywords) {
    
    
        if (page < 1) {
    
    
            page = 1;
        }
        Pageable pageable = PageRequest.of(page - 1, pageSize);
        Page<Goods> pd = goodsDao.selectByLike(keywords, pageable);
        return pd;
    }
}

@Cacheable(value=”com.etc.ssm.service.impl.showGoods”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 com.etc.ssm.service.impl.showGoods的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。

  • 启动类
@SpringBootApplication
@EnableCaching
public class SsmApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SsmApplication.class, args);
    }
}

(4)创建Service实现类的测试类(将鼠标点击Service实现类的编辑窗口,点击菜单栏中的Navigate的Test,再点击要生成的文件即可),运行测试的方法(在该方法中调用被@Cacheable注解的方法)。注意:Redis数据库要启动。

package com.etc.ssm;

import com.etc.ssm.entity.Goods;
import com.etc.ssm.service.GoodsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;

@SpringBootTest
class GoodsServiceImplTest {
    
    
    @Autowired
    GoodsService goodsService;

    @Test
    void geGoodsbyLike() {
    
    
        Page<Goods> pageinfo = goodsService.showGoods(0,5,"");
        System.out.println(pageinfo.getContent());
    }
}

(5)运行成功后,在Redis数据库中看到存储的键值对数据。

在这里插入图片描述

2、更新数据库,清空对应缓存


当数据库的数据修改时,Redis缓存中对应的数据会随着被清空,而不会返回修改之前的数据。当再次执行查询数据库的方法时,会返回修改后的数据,同时也会将修改后的数据存储到缓存中。

扫描二维码关注公众号,回复: 13011161 查看本文章
  • Service实现类
package com.etc.ssm.service.impl;

import com.etc.ssm.dao.UserDao;
import com.etc.ssm.entity.User;
import com.etc.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    
    
    @Autowired
    UserDao userDao;
    @CacheEvict(key = "#user.userid",cacheNames = "com.etc.ssm.service.impl.getUserByid")
    @Override
    public User updateUser(User user){
    
    
        User usertemp= userDao.save(user);
        return usertemp;
    }
    @Cacheable(key = "#userid",cacheNames = "com.etc.ssm.service.impl.getUserByid")
    @Override
    public User getUserByid(int userid){
    
    
        User user= userDao.findByUserId(userid);
        return user;
    }
}

先调用updateUser方法对数据库中的数据进行修改,修改成功后,会发现缓存中的数据也随着被清空。再调用getUserByid方法,此时会从数据库中获取数据。

  • Service实现类的测试类
package com.etc.ssm;

import com.etc.ssm.entity.User;
import com.etc.ssm.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class UserServiceImplTest {
    
    
    @Autowired
    UserService userService;
    @Test
    void updateUser() {
    
    
        User user = userService.getUserByid(1);
        user.setUsername("小明");
        userService.updateUser(user);
    }
    @Test
    void getUserByid() {
    
    
        User user = userService.getUserByid(1);
        System.out.println(user);
    }
}

3、相关注解

3.1、 @Cacheable


@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。

@Cacheable 主要的参数
(1)value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个。例如:@Cacheable(value="cache1")或者 @Cacheable(value={"cache1","cache2"}

(2)key:缓存的值,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。例如:@Cacheable(value="testcache",key="#userName")

(3)condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。例如:@Cacheable(value="testcache",condition="#userName.length()>2")

3.2、@CacheEvict


@CachEvict 的作用主要针对方法配置,能够根据一定的条件对缓存进行清空。

@CacheEvict 主要的参数:

(1)value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个。例如:@CachEvict(value="cache1") 或者 @CachEvict(value={"cache1","cache2"})

(2)key:缓存的值,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。例如:@CachEvict(value="testcache",key="#userName")

(3)condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存。例如:@CachEvict(value="testcache",condition="#userName.length()>2")

(4)allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。例如:@CachEvict(value="testcache",allEntries=true)

Spring Cache不仅支持将数据缓存,还支持将缓存数据删除。此过程经常用于从缓存中清除过期或未使用的数据。 @CacheEvict要求指定一个或多个缓存,使之都受影响。此外,还提供了一个额外的参数allEntries 。表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的键,清除缓存中所有的元素。

(5)beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。例如:@CachEvict(value="testcache",beforeInvocation=true)

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

3.3、@CachePut


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

@CachePut 主要的参数:

(1)value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:@Cacheable(value=”cache1”) 或者 @Cacheable(value={”cache1”,”cache2”})

(2)key:缓存的值,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。例如:@Cacheable(value="testcache",key="#userName")

(3)condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:@Cacheable(value="testcache",condition=”#userName.length()>2")

二、压力测试


ContiPerf 是一个轻量级的测试工具,基于JUnit 4 开发,可用于性能测试等。可以指定在线程数量和执行次数,通过限制最大时间和平均执行时间来进行性能测试。

使用ContiPerf 测试工具模拟并发,进行压力测试。ContiPerf 测试工具需要使用Junit4。

实现步骤:
(1)在pom.xml中加入Contiperf的坐标。

<!--加入轻量级压力测试工具-->
<dependency>
    <groupId>org.databene</groupId>
    <artifactId>contiperf</artifactId>
    <version>2.3.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

(2)添加@Rule注解在单元测试类中ContiPerfRule对象前,使用@Rule注解激活 ContiPerf。添加@PerfTest注解在测试方法前,在具体测试方法上使用@PerfTest指定调用次数/线程数,使用@Required指定每次执行的最长时间/平均时间/总时间等。@PerfTest(invocations=30000,threads=20) 表示执行30000次,并发执行20个线程。

package com.etc.ssm;

import com.etc.ssm.entity.User;
import com.etc.ssm.service.UserService;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.junit.ContiPerfRule;
import org.junit.Rule;
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;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest_contiperf {
    
    
    @Autowired
    private UserService userService;
    @Rule
    public ContiPerfRule contiperfrule = new ContiPerfRule();
    @Test
    @PerfTest(invocations=30000,threads=20)//执行30000次,并发执行20个线程
    public void getUserByid() {
    
    
        User user = userService.getUserByid(1);
        System.out.println(user);
    }
}

注意:在相同条件下,使用Redis进行的压力测试比不使用Redis进行压力测试花的时间少。

三、Spring Boot整合Swagger2


(1)在pom.xml文件中加入Swagger2的坐标。

 <!--swagger2 start-->
	<dependency>
	    <groupId>io.springfox</groupId>
	    <artifactId>springfox-swagger2</artifactId>
	    <version>2.9.1</version>
	</dependency>
	<dependency>
	    <groupId>io.springfox</groupId>
	    <artifactId>springfox-swagger-ui</artifactId>
	    <version>2.9.2</version>
	</dependency>
	<dependency>
	    <groupId>org.webjars</groupId>
	    <artifactId>bootstrap</artifactId>
	    <version>3.3.5</version>
	</dependency>
<!--swagger2 end-->

(2)创建com.etc.ssm.config包,并在该包中创建Swagger2的配置类。

package com.etc.ssm.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2 {
    
    
    //swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等
    @Bean
    //创建一个bean
    public Docket createRestApi() {
    
    
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //为当前包路径
                .apis(RequestHandlerSelectors.basePackage("com.etc.ssm.controller"))
                .paths(PathSelectors.any())
                .build();
    }
    //构建 api文档的详细信息函数,注意这里的注解引用的是哪个
    private ApiInfo apiInfo() {
    
    
        return new ApiInfoBuilder()
                //页面标题
                .title("Spring Boot 测试使用 Swagger2 构建RESTful API")
                //创建人
                .contact(new Contact("Tom", "", ""))
                //版本号
                .version("1.0")
                //描述
                .description("API 描述")
                .build();
    }
}

(3)在类、方法、参数上添加对应的Swagger2注解。

package com.etc.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.etc.entity.Book;
import com.etc.service.BookService;
import com.etc.util.AjaxResponse;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;

@Api(value = "图书接口", description = "图书控制器,实现了图书的检索,增加,修改等操作")
@Controller
public class TempController {
    
    
	@Autowired
	private BookService bookservice;

	@GetMapping("book/getlistbyname.do")
	@ApiOperation(value = "图书检索", notes = "实现图书名、简介、作者名、出版社名的模糊分页查询", httpMethod = "GET", response = AjaxResponse.class)
	@ResponseBody
	public AjaxResponse<Book> getBookList(
			@ApiParam(name = "booknamekeywords", required = false, value = "图书名") @RequestParam(value = "booknamekeywords", required = false, defaultValue = "") String booknamekeywords,
			@ApiParam(name = "introducekeywords", required = false, value = "简介") @RequestParam(value = "introducekeywords", required = false, defaultValue = "") String introducekeywords,
			@ApiParam(name = "authorkeywords", required = false, value = "作者名") @RequestParam(value = "authorkeywords", required = false, defaultValue = "") String authorkeywords,
			@ApiParam(name = "presskeywords", required = false, value = "出版社名") @RequestParam(value = "presskeywords", required = false, defaultValue = "") String presskeywords,
			@ApiParam(name = "bookbusinessid", required = false, value = "商家编号") @RequestParam(value = "bookbusinessid", required = false, defaultValue = "0") int bookbusinessid,
			@ApiParam(name = "page", required = false, value = "当前页数") @RequestParam(value = "page", required = false, defaultValue = "1") int page,
			@ApiParam(name = "limit", required = false, value = "每页数量") @RequestParam(value = "limit", required = false, defaultValue = "10") int limit) {
    
    
		//调用业务层方法实现分页查询
		PageInfo<Book> pageinfo = bookservice.showBookByPage(page, limit, booknamekeywords,introducekeywords,authorkeywords,presskeywords,bookbusinessid);
		//将返回结果转换为视图层能接受的数据格式,AjaxResponse=>Layui所以自己封装了一个对象
		AjaxResponse<Book> ar = new AjaxResponse<Book>(0, "success", (int) pageinfo.getTotal(), pageinfo.getList());
		return ar;
	}	
}

(4)访问地址:http://localhost:8080/swagger-ui.html。页面文档和测试效果如下。

在这里插入图片描述

四、热部署


spring为开发者提供了一个名为spring-boot-devtools的模块来使Spring Boot应用支持热部署,提高开发者的开发效率,无需手动重启Spring Boot应用。

实现步骤:
(1)在pom.xml文件中加入Spring Boot devTools的坐标。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <version>2.4.4</version>
</dependency>

(2)在配置文件application.properties中增加以下配置。

## 配置devtools的自动启动
spring.devtools.restart.enabled= true
## 设置自动重启的目录为 src/main/java
spring.devtools.restart.additional-paths= src/main/java
## 设置不重启的目录
#spring.devtools.restart.exclude= WEB-INF/*

(3)设置自动编译,点击settings -> 点击Build, Execution, Deployment -> 点击compiler -> 将Build Project automatically打钩。

在这里插入图片描述
(4)通过快捷键(Shift+Ctrl+Alt+/)进入Registry,把compiler.automake.allow.when.app.running打钩。

在这里插入图片描述
(5)此时只要修改代码的话,就会自动重启Spring Boot应用。

猜你喜欢

转载自blog.csdn.net/qq_42141141/article/details/115216850