Directory
Description of Requirement
Front-end ajax request call query interface example
Preparation
Introduce related dependencies
Entity class
controller layer
service layer interface
service layer implementation class
mapper layer
selectAll complex dynamic sql in mapper.xml
control plane
Tool class MyUtils
General class DataVO
Send a request to view the response result
ajax request body
response content
The key – facet enhancement
feelings
thank you
Description of requirements
In the effect I want to achieve, when the front end calls the query interface, the request body carries the following data: the query condition field of the entity class to be queried (may have multiple conditions, or may not have query conditions), the variable of the page query: page (current page number), limit (limit number of entries per page),
The backend needs to receive the content of the request body. It can judge which entity class to query according to the different calling interfaces, create the entity class, and pass the corresponding entity class, page number, and item number to the service layer, and the service layer passes the entity class to the mapper query. Statement (dynamic sql is implemented in the mapper layer), and the pagehelper plug-in of mybatis is used to implement paging and return the data layer by layer to the front end.
Take my own project as an example
Example of front-end ajax request call query interface
$.ajax({ url: "http://127.0.0.1:8080/counter/select", method: "POST", headers: { "token": "myToken" }, //Because my project interceptor has performed token verification, so the request header carries a token, and there is no need to write the request header without token verification data:JSON. stringify({ "page": 1, "limit": 5, "id":"A0001" //Field to query }), contentType: "application/json;charset=utf-8", success: function (response) { console. log(response. data) } });
Preparation
Introduce related dependencies
The maven project is added in pom.xml
<!--spring-web dependency--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-aop dependency--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--mybatis-plus dependency, because my project needs a lot of complex sql, so it is still written according to mybatis, mp is only enhanced and not changed compared to mybatis, and can still be used according to mybatis --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!--mybatis paging plugin--pagehelper--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency> <!--mysql--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!--fastjson2, process json data--> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.23</version> </dependency> <!--lombok annotations--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
entity class
package com.cns.coldstoragesys.bean; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; @Data //@JsonInclude(JsonInclude.Include.NON_NULL)//Delete fields that are null when returning to the front end public class Counter { private String id; private Integer coldstorageId; private String type; private String state; private String pos; private Integer level; private String goodsId; /* Add temporary fields, cascade queries return to the front end, and facilitate data tables to obtain associated data */ @TableField(exist=false) private String coldstorageName; @TableField(exist = false) private String video; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT + 8")//json date format conversion @TableField(exist = false) private Date startTime; @TableField(exist = false) private String description; @TableField(exist = false) private Integer length; @TableField(exist = false) private Integer width; @TableField(exist = false) private Integer height; }
controller layer
package com.cns.coldstoragesys.controller; import com.cns.coldstoragesys.bean.Counter; import com.cns.coldstoragesys.common.DataVO; import com.cns.coldstoragesys.common.SysConstant; import com.cns.coldstoragesys.service.CounterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/counter") public class CounterController { @Autowired private CounterService counterService; @PostMapping("/select") public DataVO selectAll(@RequestBody Map<String,Object> param){ return counterService. selectAll( (Counter) param. get(SysConstant. DEFAULT_BEAN_NAME), (Integer) param. get(SysConstant. DEFAULT_PAGE_NAME), (Integer) param. get(SysConstant. DEFAULT_LIMIT_NAME)); } }
service layer interface
package com.cns.coldstoragesys.service; import com.cns.coldstoragesys.bean.Counter; import com.cns.coldstoragesys.common.DataVO; public interface CounterService { DataVO selectAll(Counter counter, Integer page, Integer limit); }
service layer implementation class
package com.cns.coldstoragesys.service.impl; import com.cns.coldstoragesys.bean.Counter; import com.cns.coldstoragesys.common.DataVO; import com.cns.coldstoragesys.common.SysConstant; import com.cns.coldstoragesys.mapper.CounterMapper; import com.cns.coldstoragesys.service.CounterService; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.List; @Slf4j @Service @Transactional public class CounterServiceImpl implements CounterService { @Autowired private CounterMapper counterMapper; @Override public DataVO selectAll(Counter counter, Integer page, Integer limit) { Page<Object> p = PageHelper.startPage(page,limit); try { List<Counter> counters = counterMapper. selectAll(counter); return new DataVO(SysConstant.CODE_SUCCESS,SysConstant.SELECT_SUCCESS,p.getTotal(),counters); } catch (Exception e) { log. error(e. toString()); return new DataVO(SysConstant.CODE_ERROR,SysConstant.SELECT_ERROR); } } }
mapper layer
package com.cns.coldstoragesys.mapper; import com.cns.coldstoragesys.bean.Counter; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface CounterMapper { List<Counter> selectAll(Counter counter); }
selectAll complex dynamic sql in mapper.xml
I wrote it so complicated because the front-end data table also needs part of the data in the other three tables, so three connections are made, and the corresponding temporary fields are added with @TableField(exist=false) annotation in the entity class, using sql The tag implements dynamic sql. If any field in the carried entity class is not empty, it means it is a query condition.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cns.coldstoragesys.mapper.CounterMapper"> <resultMap id="BaseResultMap" type="com.cns.coldstoragesys.bean.Counter"> <id column="id" jdbcType="CHAR" property="id" /> <result column="coldstorage_id" jdbcType="INTEGER" property="coldstorageId" /> <result column="coldstorage_name" jdbcType="VARCHAR" property="coldstorageName" /> <result column="type" jdbcType="CHAR" property="type" /> <result column="state" jdbcType="VARCHAR" property="state" /> <result column="pos" jdbcType="VARCHAR" property="pos" /> <result column="level" jdbcType="INTEGER" property="level" /> <result column="goods_id" jdbcType="CHAR" property="goodsId" /> <result column="description" jdbcType="VARCHAR" property="description" /> <result column="length" jdbcType="INTEGER" property="length" /> <result column="width" jdbcType="INTEGER" property="width" /> <result column="height" jdbcType="INTEGER" property="height" /> <result column="video" jdbcType="VARCHAR" property="video" /> <result column="start_time" jdbcType="TIMESTAMP" property="startTime"/> </resultMap> <select id="selectAll" parameterType="com.cns.coldstoragesys.bean.Counter" resultMap="BaseResultMap"> select counter.id,counter.coldstorage_id,counter.type,counter.state,counter.pos,counter.`level`,counter.goods_id, type.description, type.length, type.width, type.height, record.video,record.start_time, cold.name as coldstorage_name from counter left join coldstorage as cold on cold.id=counter.coldstorage_id left join counter_type as type on type.id = counter.type left join record_access as record on record.start_time=( select MAX(record. start_time) from record_access as record where record.counter_id=counter.id group by record.counter_id ) <where> <if test="null != coldstorageId and '' != coldstorageId"> and counter.coldstorage_id=#{coldstorageId} </if> <if test="null != coldstorageName and '' != coldstorageName"> and counter.coldstorage_id=(select id from coldstorage where name like "%${coldstorageName}%") </if> <if test="null != id and '' != id"> and counter.`id`= #{id} </if> <if test="null != type and '' != type"> and counter. `type` = #{type} </if> <if test="null != level and '' != level"> and counter. `level` = #{level} </if> <if test="null != state and '' != state"> and counter. `state` = #{state} </if> <if test="null != description and '' != description"> and counter.type=(select id from counter_type where `description` like "%${description}%") </if> </where> order by counter.id asc </select> </mapper>
Control Layer
package com.cns.coldstoragesys.aspect; import com.alibaba.fastjson2.JSON; import com.cns.coldstoragesys.common.SysConstant; import com.cns.coldstoragesys.util.MyUtils; 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.bind.annotation.RequestBody; import java.util.HashMap; import java.util.Map; @Aspect @Component public class ControllerAspect { //Specify the cut point as the selectAll method of all classes in the controller directory and the required parameter is Map<String, Object> param @Pointcut(value = "execution(* com.cns.coldstoragesys.controller..selectAll(..)) & amp; & amp; args(param)" ,argNames = "param") public void controllerPoint(Map<String, Object> param){} //Specify the entry point and parameter name of the surrounding enhanced aspect @Around(value = "controllerPoint(param) & amp; & amp; args(..)",argNames= "joinPoint,param") public Object changeParam(ProceedingJoinPoint joinPoint,@RequestBody Map<String, Object> param) throws Throwable { Integer page = (Integer) param.get(SysConstant.DEFAULT_PAGE_NAME); //Get the page and limit used for paging in param and remove them, and the remaining key-value pairs in param are the conditions that need to be queried param.remove(SysConstant.DEFAULT_PAGE_NAME); Integer limit = (Integer) param.get(SysConstant.DEFAULT_LIMIT_NAME); param.remove(SysConstant.DEFAULT_LIMIT_NAME); String className = MyUtils.getClassName(joinPoint.getTarget().getClass().getName()); //Get the fully qualified class name of the tool class Class<?> clazz = Class.forName(className); //Reflection mechanism creates class Object obj = JSON.parseObject(JSON.toJSONString(param), clazz); //Convert the remaining key-value pairs in the Map into corresponding types of json objects Map<String,Object> params=new HashMap<>(); //restore the last parameters that need to be returned, the parameters of the procceed method need an Object array, params.put(SysConstant.DEFAULT_BEAN_NAME, obj); //But the selectAll method in the controller layer has only one parameter, params.put(SysConstant.DEFAULT_PAGE_NAME,page); //If you directly put the key-value pair into the Object array, an abnormal number of parameters will be reported, params.put(SysConstant.DEFAULT_LIMIT_NAME,limit); //So here put the key-value pair into the Map, and then put the Map into the Object array return joinPoint.proceed(new Object[]{params}); //The parameter of the procceed method requires an Object array, } }
tool class MyUtils
package com.cns.coldstoragesys.util; import com.cns.coldstoragesys.common.SysConstant; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class MyUtils { public static String getClassName(String fullClassName) { Pattern pattern = Pattern.compile("\.(\w + )Controller$"); Matcher matcher = pattern. matcher(fullClassName); if (matcher. find()) { return SysConstant.DEFAULT_BEAN_PATH + matcher.group(1); } return null; } }
General DataVO
package com.cns.coldstoragesys.common; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.Data; import java.util.List; @Data @JsonPropertyOrder({"code","msg","count","data"})//Specify the field order returned to the front end public class DataVO<T> { private Integer code; private String msg; private Long count; private List<T> data; public DataVO() { } public DataVO(Integer code, String msg) { this.code = code; this.msg = msg; } public DataVO(Integer code, String msg, Long count, List<T> data) { this.code = code; this.msg = msg; this.count = count; this.data = data; } }
System constant SysConstant
package com.cns.coldstoragesys.common; public interface SysConstant { Integer CODE_SUCCESS=0; //The operation is successful Integer CODE_ERROR=1; //operation failed String DEFAULT_BEAN_PATH="com.cns.coldstoragesys.bean."; String DEFAULT_PAGE_NAME="page"; //By default, the variable name of the specified page number is passed, because the data from the front end is placed in the request body, and the controller layer interface is received through Map<String, Object>, which needs to be retrieved through the key value Integer DEFAULT_PAGE=1; //Default page number String DEFAULT_LIMIT_NAME="limit"; Integer DEFAULT_LIMIT=10; //The default number String DEFAULT_BEAN_NAME="bean"; Long REDIS_OVERDUE_TIME=30*24*60*60L; String DEFAULT_TOKEN_ISSUER="Yan"; String DEFAULT_TOKEN_AUDIENCE="Huang"; String SELECT_SUCCESS="Query succeeded"; String SELECT_ERROR="Query failed"; String ADD_SUCCESS="Added successfully"; String ADD_ERROR="Add failed"; String DELETE_SUCCESS="Delete successfully"; String DELETE_ERROR="Delete failed"; String UPDATE_SUCCESS="Modification succeeded"; String UPDATE_ERROR="Modification failed"; String NULL_VALUE="Primary key does not exist"; String REPEAT_VALUE="Primary key repeats"; String LOGIN_SUCCESS="Login successful"; String LOGIN_ERROR="Login failed"; String UNKNOW_ERROR="Unknown error"; }
Send a request to view the response result
ajax request body
Response content
The key – aspect enhancement
In this aspect, the @Around annotation is used, and its cut point is defined as the selectAll method in the controller layer class of all different entity classes under the controller package, and this method must have a parameter param of type Map
@Pointcut(value = “execution(* com.cns.coldstoragesys.controller..selectAll(..)) & amp; & amp; args(param)” ,argNames = “param”)
@Aspect @Component public class ControllerAspect { //Specify the cut point as the selectAll method of all classes in the controller directory and the required parameter is Map<String, Object> param @Pointcut(value = "execution(* com.cns.coldstoragesys.controller..selectAll(..)) & amp; & amp; args(param)" ,argNames = "param") public void controllerPoint(Map<String, Object> param){} //Specify the entry point and parameter name of the surrounding enhanced aspect @Around(value = "controllerPoint(param) & amp; & amp; args(..)",argNames= "joinPoint,param") public Object changeParam(ProceedingJoinPoint joinPoint,@RequestBody Map<String, Object> param) throws Throwable { Integer page = (Integer) param.get(SysConstant.DEFAULT_PAGE_NAME); //Get the page and limit used for paging in param and remove them, and the remaining key-value pairs in param are the conditions that need to be queried param.remove(SysConstant.DEFAULT_PAGE_NAME); Integer limit = (Integer) param.get(SysConstant.DEFAULT_LIMIT_NAME); param.remove(SysConstant.DEFAULT_LIMIT_NAME); String className = MyUtils.getClassName(joinPoint.getTarget().getClass().getName()); //Get the fully qualified class name of the tool class Class<?> clazz = Class.forName(className); //Reflection mechanism creates class Object obj = JSON.parseObject(JSON.toJSONString(param), clazz); //Convert the remaining key-value pairs in the Map into corresponding types of json objects Map<String,Object> params=new HashMap<>(); //restore the last parameters that need to be returned, the parameters of the procceed method need an Object array, params.put(SysConstant.DEFAULT_BEAN_NAME, obj); //But the selectAll method in the controller layer has only one parameter, params.put(SysConstant.DEFAULT_PAGE_NAME,page); //If you directly put the key-value pair into the Object array, an abnormal number of parameters will be reported, params.put(SysConstant.DEFAULT_LIMIT_NAME,limit); //So here put the key-value pair into the Map, and then put the Map into the Object array return joinPoint.proceed(new Object[]{params}); //The parameter of the procceed method requires an Object array, } }
The MyUtil tool class used here obtains the fully qualified class name method, and uses regular expressions to extract the entity class name corresponding to xxxController, plus the location of the Bean package defined in the system constant, that is, the fully qualified class name
public static String getClassName(String fullClassName) { Pattern pattern = Pattern.compile("\.(\w + )Controller$"); Matcher matcher = pattern. matcher(fullClassName); if (matcher. find()) { return SysConstant.DEFAULT_BEAN_PATH + matcher.group(1); } return null; }
In the surround enhancement method of changeParam, the two variables page and limit related to paging in param are obtained and removed from the map, then only the fields we need to query are left in the map.
The class name of the controller layer of the current enhanced target method can be obtained through joinPoint. For example, if the counter is currently queried, it is CounterController. If the user is queried, it is UserController. Because of our standardized structure, we can use this class name to obtain the entity class. Fully qualified name, such as com.cns.bean.
After obtaining the fully qualified class name, create the class through the reflection mechanism,
Convert the map to the corresponding entity class through the method of com.alibaba.fastjson2.JSON, because we cannot determine the specific class here, so we use Object to receive it, and then the controller layer is forced to convert it into the required entity class, and the effect is achieved.
Because it is a general aspect, this aspect can enhance multiple controllers. I have many controllers of entity classes that need such enhancements
If the interface needs to query other entity classes, it only needs to change the interface to achieve enhancement.
Feelings
Although the incoming requestBody can be processed directly in the Controller layer to implement dynamic sql paging query, but such a lot of controllers need an extra section of such repetitive code, so I want to put it in the section to reduce code duplication, so that the controller is still Only need to write one line, elegant and beautiful
Thanks
Thanks to ChatGPT, I have the idea to achieve this effect, but in fact I can’t implement it very well. Many bugs are realized by asking ChatGPT