✨✨个人主页:沫洺的主页
📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏
📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏
💖💖如果文章对你有所帮助请留下三连✨✨
🍸效果
在后端向前端返回统一格式data里一般按照需求是要包含分页信息的,比如总条数和按照每页的条数去返回对应的总页数等
🍹搭建多模块环境(父子项目)
项目创建参考SpringBoot专栏里:
父项目scm-root的pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.4</version> <relativePath/> </parent> <groupId>com.moming</groupId> <artifactId>scm-root</artifactId> <version>14-SNAPSHOT</version> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> </properties> <modules> <module>scm-authority</module> <module>scm-app</module> <module>scm-dao</module> <module>scm-dto</module> <module>scm-entity</module> <module>scm-service</module> <module>scm-api</module> <module>scm-core</module> </modules> <dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> <scope>compile</scope> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.5</version> </dependency> </dependencies> </dependencyManagement> <build> </build> </project>
数据库
首先实现简单的查询业务
scm-dto模块
pom.xml依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
创建UserDto实体类
package com.moming.dto; import lombok.Data; @Data public class UserDto { private Integer id; private String username; private String password; }
scm-entity模块
pom.xml依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
创建UserEntity实体类
package com.moming.entity; import lombok.Data; @Data public class UserEntity { private Integer id; private String username; private String password; }
scm-dao模块
pom.xml依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>com.moming</groupId> <artifactId>scm-entity</artifactId> <version>14-SNAPSHOT</version> <scope>compile</scope> </dependency>
创建UserDao接口
package com.moming.dao; import com.moming.entity.UserEntity; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface UserDao { @Select("select * from tb_user order by id") List<UserEntity> select(); }
scm-service模块
pom.xml依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <dependency> <groupId>com.moming</groupId> <artifactId>scm-dao</artifactId> <version>14-SNAPSHOT</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.moming</groupId> <artifactId>scm-dto</artifactId> <version>14-SNAPSHOT</version> <scope>compile</scope> </dependency>
创建UserService业务类
package com.moming.service; import cn.hutool.core.bean.BeanUtil; import com.moming.dao.UserDao; import com.moming.dto.UserDto; import com.moming.entity.UserEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { @Autowired private UserDao userDao; public List<UserDto> select(){ List<UserEntity> entityList = userDao.select(); List<UserDto> userDtos = BeanUtil.copyToList(entityList, UserDto.class); return userDtos; } }
scm-app模块
pom.xml依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.9</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>com.moming</groupId> <artifactId>scm-service</artifactId> <version>14-SNAPSHOT</version> <scope>compile</scope> </dependency>
创建controller/UserController
package com.moming.controller; import com.moming.dto.UserDto; import com.moming.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @GetMapping("/select") public List<UserDto> select(){ return userService.select(); } }
创建resources/application.yml
#数据库连接信息配置 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db5?useSSL=false&useServerPrepStmts=true username: root password: 123456 druid: initial-size: 10 # 初始化时建立物理连接的个数 min-idle: 10 # 最小连接池数量 maxActive: 200 # 最大连接池数量 maxWait: 60000 # 获取连接时最大等待时间,单位毫秒 #映射文件所在位置 mybatis: mapper-locations: classpath:mapper/*Mapper.xml #别名 type-aliases-package: com.moming.entity #配置日志级别 logging: level: com.moming: debug
启动类App运行后调用接口测试
🥂统一返回格式
scm-dto模块
创建ResponseDto实体类
package com.moming.dto; import lombok.Data; @Data public class ResponseDto { private int code; private String message; private Object data; }
scm-app模块
创建advice/MyResponseAdvice
package com.moming.advice; import cn.hutool.core.lang.Dict; import cn.hutool.json.JSONUtil; import com.moming.dto.PageInfo; import com.moming.dto.ResponseDto; import com.moming.local.PageInfoLocal; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @RestControllerAdvice(basePackages = MyResponseAdvice.BASEPACKAGES) public class MyResponseAdvice implements ResponseBodyAdvice<Object> { public static final String BASEPACKAGES="com.moming.controller"; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @ExceptionHandler public Object processException(Exception ex){ ResponseDto responseDto = new ResponseDto(); responseDto.setCode(1); responseDto.setMessage(ex.getMessage()); return responseDto; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { ResponseDto responseDto = new ResponseDto(); responseDto.setCode(0); responseDto.setMessage(""); responseDto.setData(body); //处理返回类型为字符串 if (selectedConverterType == StringHttpMessageConverter.class) { //hutool JSONUtil.toJsonStr字符串转换为json数据 return JSONUtil.toJsonStr(responseDto); } else { return responseDto; } } }
接口测试查看效果
🍻返回带分页信息格式
业务需求:
不改变原有的代码,在返回的data中呈现总页数(pages)和(total)
分析:
首先完整的查询业务在上面已经写好了,根据业务需求要在不改变原有的代码的基础之上进行业务增强,就要使用AOP技术来实现,另外要获取总页数,那么在url接口上就需要传递每页多少条的参数(pageSize),既然实现了每页条数,那么同样的就可以传递第几页(pageNum)去动态查询某页的数据,接下来就是怎么去获取url传递的参数,可以通过AOP中的环绕通知,在环绕前使用RequestContextHolder对象去获取ServletRequestAttributes对象,然后通过它去获取HttpServletRequest对象,最后调用getParameter获取参数,获取参数之后使用PageHelper调用startPage方法会将目标方法返回值的类型改为Page类型(底层实现了ArrayList),而Page底层提供了total,pages等属性的get方法,因为属性有很多所以这里需要处理以下,通过我们自定义的PageInfo的set方法获取total,pages的值,然后使用ThreadLocal(本地线程)的机制去实现线程安全问题,ThreadLocal中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的(也就是线程安全的),ThreadLocal为变量在每个线程中创建了一个副本,这样每个线程就可以访问自己内部的副本变量,通过这种机制我们给当前线程脑门上贴一个变量(pageInfo),然后再获取当前线程脑门上的变量(pageInfo),最后移除当前线程脑门上的变量(pageInfo)
scm-app模块
pom.xml添加依赖
AOP坐标依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
第三方pagehelper分页坐标依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.5</version> </dependency>
相关模块导入依赖
<dependency> <groupId>com.moming</groupId> <artifactId>scm-core</artifactId> <version>14-SNAPSHOT</version> <scope>compile</scope> </dependency>
创建local/PageInfoLocal
package com.moming.local; import com.moming.dto.PageInfo; public class PageInfoLocal { private static ThreadLocal<PageInfo> threadLocal = new InheritableThreadLocal<>(); //给当前线程头上贴一个变量 pageinfo public static void set(PageInfo pageInfo){ threadLocal.set(pageInfo); } //获取当前线程头上的变量 pageinfo public static PageInfo get(){ return threadLocal.get(); } //移除当前线程头上的变量 pageinfo public static void remove(){ threadLocal.remove(); } }
创建aop/DaoAop
package com.moming.aop; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.moming.dto.PageInfo; import com.moming.local.PageInfoLocal; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Component @Aspect public class DaoAop { //通过注解方式定义切入点 @Pointcut("@annotation(com.moming.annotation.PageCut)") public void point(){} //环绕通知 @Around("point()") public Object around(ProceedingJoinPoint pjp) throws Throwable { //获取url传递的参数 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String pageNum = request.getParameter("pageNum"); String pageSize = request.getParameter("pageSize"); //如果只传递pageSize,则默认pageNum为1 if( ObjectUtil.isNotEmpty(pageSize)){ int page_num =1; if(ObjectUtil.isNotEmpty(pageNum)){ page_num = Integer.valueOf(pageNum); } int page_size = Integer.valueOf(pageSize); PageHelper.startPage(page_num,page_size); } //目标方法List<UserEntity> select()--- Object result = pjp.proceed(); //如果目标方法的返回值类型为Page,则将pageInfo对象贴在当前线程脑门上 if(result instanceof Page){ Page page = (Page) result; //使用自定义的PageInfo对象获取需要的分页信息 PageInfo pageInfo = new PageInfo(); pageInfo.setPages(page.getPages()); pageInfo.setTotal(page.getTotal()); PageInfoLocal.set(pageInfo); } return result; } }
在通知类里对分页信息进行封装
//分页组件扩展开始 PageInfo pageInfo = PageInfoLocal.get(); try{ if(pageInfo!=null){ Dict dict = Dict.create() .set("pages",pageInfo.getPages()) .set("total", pageInfo.getTotal()) .set("items",body); responseDto.setData(dict); } else { responseDto.setData(body); } //分页组件扩展结束 }finally { //移除当前线程头上的变量标签 if(pageInfo!=null){ PageInfoLocal.remove(); } }
advice/MyResponseAdvice
package com.moming.advice; import cn.hutool.core.lang.Dict; import cn.hutool.json.JSONUtil; import com.moming.dto.PageInfo; import com.moming.dto.ResponseDto; import com.moming.local.PageInfoLocal; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @RestControllerAdvice(basePackages = MyResponseAdvice.BASEPACKAGES) public class MyResponseAdvice implements ResponseBodyAdvice<Object> { public static final String BASEPACKAGES="com.moming.controller"; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @ExceptionHandler public Object processException(Exception ex){ ResponseDto responseDto = new ResponseDto(); responseDto.setCode(1); responseDto.setMessage(ex.getMessage()); return responseDto; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { ResponseDto responseDto = new ResponseDto(); responseDto.setCode(0); responseDto.setMessage(""); //分页组件扩展开始 PageInfo pageInfo = PageInfoLocal.get(); try{ if(pageInfo!=null){ Dict dict = Dict.create() .set("pages",pageInfo.getPages()) .set("total", pageInfo.getTotal()) .set("items",body); responseDto.setData(dict); } else { responseDto.setData(body); } //分页组件扩展结束 }finally { //移除当前线程头上的变量标签 if(pageInfo!=null){ PageInfoLocal.remove(); } } //responseDto.setData(body); //处理返回类型为字符串 if (selectedConverterType == StringHttpMessageConverter.class) { //hutool JSONUtil.toJsonStr字符串转换为json数据 return JSONUtil.toJsonStr(responseDto); } else { return responseDto; } } }
scm-core模块
pom.xml依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
创建annotation/PageCut注解
package com.moming.annotation; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PageCut { }
创建dto/PageInfo实体类
package com.moming.dto; import lombok.Data; @Data public class PageInfo { private int pages; private long total; }
scm-dao模块
目标方法添加切入点注解
package com.moming.dao; import com.moming.annotation.PageCut; import com.moming.entity.UserEntity; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface UserDao { @PageCut @Select("select * from tb_user order by id") List<UserEntity> select(); }
测试