RESTful的实现等级
- 0级:传统的RPC,基于SOAP的WS,调用的服务名,参数放在HTTP协议的body里面,同时必须以POST方式提交,问题在于你必须清楚的知道所有服务,子服务,及其参数的信息,并且需要知道各种服务的不同点
- 1级:利用resource概念,把所有服务都抽取成resource概念,从body中提取到header里,这样做的好处就是如果你知道一个服务地址,你可能无需知道具体服务是什么,依照资源的惯例就访问到服务,比如查询id=1的书籍信息时使用路径/books/1
- 2级:利用HTTP动词,HTTP定义了4种动词,GET获取服务器资源,POST在服务器上创建新资源,PUT更改服务器上资源,DELETE删除服务器上资源,任何操作都可以看成增删改查,所以利用标准的http verb加上resource(/book/1)就能准确地操作资源,当你不知道服务具体是什么的时候也可以轻易按照惯例访问到服务,然而服务供应商更改服务也需要遵循惯例,不会像RPC那样轻易更改服务接口
- 3级:最高级别,HATEOS超媒体既应用状态引擎。这个意思是说,对于任何服务都存在很多子服务,你只需要知道第一个服务的入口,便可以依据服务返回结构的自描述性得到下一个服务的入口,这样在服务供应商修改服务的时候,不会影响到客户端的调用
在RESTful应用中需要和前端充分沟通,建议通信的数据规范
@Data
public class JsonResult implements Serializable{
private int code;//自定义的响应状态码,不是http规范中的响应码,一般用于给前端更详细的信息
private String message;//服务器端生成的响应提示信息
private Boolean success;//可有可无,给前端一个简单的响应状态提示
private Object data;//响应数据
public static JsonResult success(String message, Object data){
JsonResult result = new JsonResult();
result.setMessage(message);
result.setData(data);
result.setCode(2000);
result.setSuccess(true);
return result;
}
}
控制器类的定义
@RestController //@Controller + @ResponseBody
@RequestMapping("/catalogs")
public class CatalogController{
@Autowired //@Resource
private CatalogService catalogService;
@GetMapping
public JsonResult getAllCatalogs(){
List<Catalog> catalogList = catalogService.list();
return JsonResult.success("所有类目列表",catalogList);
}
}
7、使用Postman进行测试
在mysql数据库中插入测试数据
insert into tbl_catalogs values
(1,"计算机图书","计算机图书"),
(2,"烹饪图书","做菜图谱"),
(3,"音乐图书","教你音乐入门");
postman的用法
常见问题1:应用启动报错UnsatisfiedDependencyException,查看报错信息的详细提示
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'com.yan.mapper.CatalogMapper' available: expected at
least 1 bean which qualifies as autowire candidate. Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatc
hingBeanFound(DefaultListableBeanFactory.java:1801) ~[spring-beans-
5.3.23.jar:5.3.23]
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDe
pendency(DefaultListableBeanFactory.java:1357) ~[spring-beans-5.3.23.jar:5.3.23]
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDepe
ndency(DefaultListableBeanFactory.java:1311) ~[spring-beans-5.3.23.jar:5.3.23]
at
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcesso
r$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.j
ava:656) ~[spring-beans-5.3.23.jar:5.3.23]
... 39 common frames omitted
可以看到报错为com.yan.mapper.CatalogMapper没有注册,所以需要添加MyBatisPlus配置类或者在主类上添加配置
@MapperScan(basePackages = "com.ma.mapper") //注册自动扫描mapper接口,完成mapper接口的注册
@SpringBootConfiguration //用于声明当前类的一个配置类
public class MyBatisPlusConfig{
}
8、针对类目进行分页显示
分页操作可以利用MP中提供的分页插件实现,修改MP的配置类引入分页插件
@MapperScan(basePackages = "com.ma.mapper") //注册自动扫描mapper接口,完成mapper接口的注册
@SpringBootConfiguration //用于声明当前类的一个配置类
public class MyBatisPlusConfig{
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
mybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
intercetpor.addInterceptor(new PaginationInnerInterceptor(ObType.MYSQL);
return interceptor;
}
}
控制器类定义,这里需要传递分页参数。传递分页参数的值bean有多种写法
@Data //主要用于供前端实现分页导航
public class PageBean implements Serializable{
private long pageNum; //当前页码值
private long maxPage; //最大页面值
private long rowsNum; // 总行数
private long rowsPerPage = 15; //每页行数
//另外写法有:添加属性Object data存储查询结果集
}
控制器类
注意:这里新增业务方法listByPage目的在于后前使用redis缓存
@RestController //@Controller + @Responsebody
@RequestMapping("/catalogs")
public class CatalogController{
@Autowird //@Resource
private CatalogService catalogService;
@GetMapping("show")
public JsonResult getByPage(@RrquestParam(defaultValue = "1")Integer
page,@RequestParam(defaultValue="3") Integer size){
PageBean pages = new PageBean();
pages.setPageNum(page);
pages.setRowsPerPage(size);
List<Catalog> catalogList = catalogService.listByPage(pages);
Map<String,Object> map = new HashMap<>();
map.put("pages",pages);
map.put("data",catalogList);
return JsonResult.success("加载成功",map);
}
}
修改业务方法的实现
实际上具体的分页实现很简单,就是在调用查询之前构建IPage类型的对象,然后调用Mapper接口中所提供的支持Page参数的方法即可
@Service
public class CatalogServiceImpl extends ServiceImpl<CatalogMapper, Catalog>
implements CatalogService{
@Override
public List<Catalog> listByPage(PageBean pages){
List<Catalog> res = new ArrayList<>();
if(pages == null || pages.getRowsPerPage() < 1){
//查询所有
res = this.getBaseMapper().selectList(null);
} else{
//分页查询
if(pages.getPageNum() < 1)
pages.setPageNum(1);
Page<Catalog> pageInfo = new Page<>(pages.getPageNum(),pages.getRowsPerPage());
pageInfo = this.getBaseMapper().selectPage(pageInfo,null);
res = pageInfo.getRecords();
pages.setPageNum(pageInfo.getCurrent());
pages.setRowsNum(pageInfo.getTotal());
pages.setMaxPage(pageInfo.getPages());
}
return res;
}
}
使用postman测试验证
生成的响应数据为JSON格式的字符串
{
"code": 2000,
"success": true,
"message": null,
"data": {
"pages": {
"pageNum": 1,
"maxPage": 2,
"rowsNum": 3,
"rowsPerPage": 2
},
"data": [
{
"id": 1,
"name": "计算机图书",
"memo": "计算机图书"
},
{
"id": 2,
"name": "烹饪图书",
"memo": "做菜的书"
}
]
}
}
9、针对类目信息发现一般很少修改,但是经常需要执行查询,例如添加商品等操作。比较适合使用redis缓存数据,通过浪费内存以减少数据库的查询此时,从而提供执行性能,应对更高的并发性需求redis缓存开发一般有2种方式,使用注解【推荐】和使用自定义编程实现。缓存可以添加在不同的层面上,一般针对controller缓存,多使用本地缓存Ehcache;如果针对业务层缓存,一般使用支持分布式的Redis;也可以在持久层添加缓存,例如开启的MyBatis的缓存
9.1、首先添加依赖
<!--业务层缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</actifactId>
</dependency>
9.2、添加Redis序列化器和反序列化器的配置
@SpringBootConfiguration
public class MyRedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();//创建模板类对象
RedisSerializer<String> redisSerializer = new StringRedisSerializer();//创建String序列化类
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //指定使用jackson工具负责具体的序列化操作
ObjectMapper om = new ObjectMapper(); //创建jackson的核心api的 ObjectMapper ,将bean,list,map,数组等等转成字符串
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 使用objectmapper设置bean的属性,修饰符
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //设置默认类型
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); //设置将本地时间转成字符串
jackson2JsonRedisSerializer.setObjectMapper(om); //将核心api objectmapper设置给jackson
template.setConnectionFactory(factory); // 通过工厂得到连接对象
template.setKeySerializer(redisSerializer); //设置key序列化方式: 转成字符串
template.setValueSerializer(jackson2JsonRedisSerializer); // 设置value序列化: 字符串
template.setHashValueSerializer(jackson2JsonRedisSerializer); // value hashmap序列化
return template;
}
}
9.3、添加Redis服务器相关配置
spring:
redis:
host: localhost
port: 6379
lettuce:
pool:
max-active: 8
min-idle: 3
9.4、通过spring框架提供的模板类RedisTemplate调用API操作Redis数据库
在使用redis缓存之前应该充分沟通,定义一个key的标准,或者查询公司旧有标准,注意不能随机起名
@Service
public class CatalogServiceImpl extends ServiceImpl<CatalogMapper, Catalog>
implements CatalogService{
@Autowired
private RedisTemplate redisTemplate
private ThreadLocalRandom random = ThreadLocalRandom.current();
@Override
public List<Catalog> listByPage(PageBean pages){
List<Catalog> res = new ArrayList<>();
if(pages == null || pages.getRowsPerPage() < 1){
//查询所有
if(redisTemplate.haskey("catalog::all")){
//如果在具体开发中比较倾向与使用常量的方式
Object obj = redisTemplate.opsForValue().get("catalog::all");
if(obj != null && obj instanceof List)
res = (List<Catalog>) obj;
} else {
res = this.getBaseMapper().selectList(null);
if(res != null && res.size() > 0){
//为了避免雪崩问题,所以生命周期引入随机数
int kk = 100 + random.nextInt(100);
redisTemplate.opsForValue().set("catalog:: all",res,
Duration.ofSeconds(kk));
}
}
} else {
//分页查询,实际上具体的应用中不一定针对分页数据进行缓存 catalog::page::size
if(pages.getPageNum() < 1)
pages.setPageNum(1);
String key = "catalog::" + pages.getPageNum() + "::" +
pages.getRowsPerPage();
if(this.redisTemplate.hasKey(key)){
Object obj = redisTemplate.opsForValue().get(key);
if(obj!=null && obj intanceof List)
res = (List < Catalog>) obj;
if(this.redisTemplate.hasKey(key+"::page")){
obj = redisTemplate.opsForValue.get(key + "::page");
if(obj != null && obj instance PageBean){
PageBean tmp = (PageBean) obj;
BeanUtils.copyProPerties(tmp,pages);
}
}
}
if(res == null || res.size() < 1){
Page<Catalog> pageInfo = new Page<>(pages.getPageNUm(),
pages.getRowsPerPage());
pageInfo = this.getBaseMapper().selectPage(pageInfo,null);
res = pageInfo.getRecords();
pages.setPageNum(pageInfo.getCurrent());
pages.setRowsNum(pageInfo.getTotal());
pages.setMaxPage(pageInfo.getPages());
int kk = 100 + random.nextInt(100);
redisTemplate.opsForValue().set(key,res,Duration.ofSeconds(kk));
}
}
return res;
}
}
9.5、使用postman测试,可以在控制台上查看是否有对应的SQL语句输出以判断缓存是否生效