目录

需求描述

前端ajax请求调用查询接口示例

准备工作

引入相关依赖

实体类

controller层

service层接口

service层实现类

mapper层

mapper.xml中的selectAll复杂动态sql

控制层切面

工具类MyUtils

通用类DataVO

发送请求查看响应结果

ajax请求体

响应内容

 关键——切面增强

感想

致谢


需求描述

在我想要实现效果中,前端调用查询接口时,请求体中携带以下数据:所查询实体类查询条件字段(可能有多个条件,也可能没有查询条件)、分页查询变量page当前页码)、limit(每页限制条数),

后端需要接收请求体的内容可以根据调用接口不同判断查询哪个实体类创建实体类,将对应实体类页码条数传给servic层,service层传实体类mapper的查询语句mapper层中实现动态sql),使用mybatispagehelper插件实现分页将数据层层返回前端

以我自己项目为例

前端ajax请求调用查询接口示例

$.ajax({
    url: "http://127.0.0.1:8080/counter/select",
    method: "POST",
    headers: {
        "token": "myToken"
    },  //由于我的项目拦截器进行了token验证,所以请求头带一个token没有进行token验证可不用写请求data:JSON.stringify({
        "page":1,
        "limit":5,
        "id":"A0001"    //要查询的字段
    }),
    contentType: "application/json;charset=utf-8",
    success: function (response) {
        console.log(response.data)
    }
});

准备工作

引入相关依赖

maven项目在pom.xml添加

<!--spring-web依赖-->
<dependency&gt;
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-aop依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mybatis-plus依赖,由于我的项目中需要很多复杂sql,所以依旧是按照mybatis来写的,mp相对于mybatis只做增强不做改变,依旧可以mybatis用法用-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
<!--mybatis分页插件——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,处理json数据-->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.23</version>
</dependency>
<!--lombok注解-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

实体类

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)//删除返回前端时为null字段
public class Counter {
    private String id;
    private Integer coldstorageId;
    private String type;
    private String state;
    private String pos;
    private Integer level;
    private String goodsId;
    /*
    添加临时字段,级联查询返回前端,方便数据表获取关联数据
     */
    @TableField(exist=false)
    private String coldstorageName;
    @TableField(exist = false)
    private String video;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")//json日期格式转换
    @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

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接口

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实现

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

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);
}

mapper.xml中的selectAll复杂动态sql

我写这么复杂是因为前端这个数表格需要其他三个表中的部分数据,所以进行了三个连接,同时实体类中用 @TableField(exist=false)注解添加了相应的临时字段使用sql标签实现动态sql,如果携带的实体类中哪个字段为空说明其是查询条件。

<?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>

控制层切面

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 {
    //指定切点为controller目录中所有类的selectAll方法并且要求携带的参数是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){}
    //指定环绕增强切面的切入点和参数名
    @Around(value = "controllerPoint(param) && 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);  //获得param用于分页的page和limit后将其移除,剩余在param中的键值对即为需要查询的条件
        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());    //工具获取全限定类名
        Class<?> clazz = Class.forName(className);  //反射机制创建类
        Object obj = JSON.parseObject(JSON.toJSONString(param), clazz); //将Map中剩余的键值对转为对应类型json对象

        Map<String,Object> params=new HashMap<>();          //重新存放最后需要新返回的参数,procceed方法参数需要一个Object数组,
        params.put(SysConstant.DEFAULT_BEAN_NAME,obj);      //但是controller层中的selectAll方法又只有一个参数,
        params.put(SysConstant.DEFAULT_PAGE_NAME,page);     //如果直接将键值对放到Object数组中将会报参数个数异常,
        params.put(SysConstant.DEFAULT_LIMIT_NAME,limit);   //所以这里键值对放到Map中,再将Map放到Object数组return joinPoint.proceed(new Object[]{params}); //procceed方法参数需要一个Object数组,
    }
}

工具类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;
    }
}

通用类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"})//指定返回给前端的字段顺序
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;
    }

}

系统常量SysConstant

package com.cns.coldstoragesys.common;

public interface SysConstant {
    Integer CODE_SUCCESS=0;     //操作成功
    Integer CODE_ERROR=1;       //操作失败
    String DEFAULT_BEAN_PATH="com.cns.coldstoragesys.bean.";
    String DEFAULT_PAGE_NAME="page";    //默认传递指定页数的变量名称,因为前端传来的数据是放在请求体中的,controller层接口通过Map<String,Object>接收,需要通过key取值
    Integer DEFAULT_PAGE=1;     //默认页数
    String DEFAULT_LIMIT_NAME="limit";
    Integer DEFAULT_LIMIT=10;   //默认条数
    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="查询成功";
    String SELECT_ERROR="查询失败";
    String ADD_SUCCESS="添加成功";
    String ADD_ERROR="添加失败";
    String DELETE_SUCCESS="删除成功";
    String DELETE_ERROR="删除失败";
    String UPDATE_SUCCESS="修改成功";
    String UPDATE_ERROR="修改失败";
    String NULL_VALUE="主键存在";
    String REPEAT_VALUE="主键重复";
    String LOGIN_SUCCESS="登陆成功";
    String LOGIN_ERROR="登陆失败";
    String UNKNOW_ERROR="未知错误";
}

发送请求查看响应结果

ajax请求体

响应内容

 关键——切面增强

在这个切面中,使用@Around注解,其切点定义为controller包下所有不同实体类的controller层类中的selectAll方法,并且这个方法要带有一个Map<String,Object>类型参数param,即如切点表达式

@Pointcut(value = “execution(* com.cns.coldstoragesys.controller..selectAll(..)) && args(param)” ,argNames = “param”)

@Aspect
@Component
public class ControllerAspect {
    //指定切点为controller目录中所有类的selectAll方法并且要求携带的参数是Map<String,Object> param
    @Pointcut(value = "execution(* com.cns.coldstoragesys.controller..selectAll(..)) && args(param)" ,argNames = "param")
    public void controllerPoint(Map<String, Object> param){}
    //指定环绕增强切面的切入点和参数名
    @Around(value = "controllerPoint(param) && 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);  //获得param中用于分页的page和limit后将其移除,剩余在param中的键值对即为需要查询的条件
        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());    //工具类获取全限定类名
        Class<?> clazz = Class.forName(className);  //反射机制创建类
        Object obj = JSON.parseObject(JSON.toJSONString(param), clazz); //将Map中剩余的键值对转为对应类型json对象

        Map<String,Object> params=new HashMap<>();          //重新存放最后需要新返回的参数,procceed方法的参数需要一个Object数组,
        params.put(SysConstant.DEFAULT_BEAN_NAME,obj);      //但是controller层中的selectAll方法又只有一个参数,
        params.put(SysConstant.DEFAULT_PAGE_NAME,page);     //如果直接将键值对放到Object数组中将会报参数个数异常,
        params.put(SysConstant.DEFAULT_LIMIT_NAME,limit);   //所以这里键值对放到Map中,再将Map放到Object数组return joinPoint.proceed(new Object[]{params}); //procceed方法的参数需要一个Object数组,
    }
}

这里用到的MyUtil工具类获取全限定类名方法,用正则表达式取出xxxController对应实体类名,加上系统常量定义的Bean包位置,即就是全限定类名

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;
}

changeParam这个环绕增强方法中,将param中分页相关两个变量page和limit拿到,将其从map移除,那么map中就只剩下我们需要查询的字段了,

可以通过joinPoint获取当前加强目标方法的controller层类名比如当前对counter进行查询,那就是CounterController,如果是查询User,那就是UserController,因为我们结构规范,那么我们可以通过这个类名获取实体类的全限定名称比如com.cns.bean.

获取到全限定类名后,通过反射机制创建类,

通过com.alibaba.fastjson2.JSON的方法将map转为对应实体类,因为这里的具体类我们不能确定,所以用Object接收,由controller层再强制转换成所需实体类,就实现效果

因为是通用的切面,所以这个切面可以多个controller进行增强,我有很多实体类的controller需要这样的加强

假如接口需要查询其他实体类,只需要换个接口一样能实现增强。

感想

虽然可以直接在Controller层中对传来的requestBody进行处理实现动态sql分页查询,但是这样很多个controller都需要多一段这样的重复性质代码,所以我就想放到切面中减少代码重复,这样controller还是只需要写一行,优雅美观

致谢

感谢ChatGPT,我有实现这个效果的想法,但其实自己并不能很好进行具体实现,很多bug是由询问ChatGPT才恍然大悟的

原文地址:https://blog.csdn.net/m0_54250110/article/details/129749589

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_16985.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注