关于Mybits三层架构的项目操作

概述

本篇文章用来记录如何使用mybits在三层架构中进行简单的增删改查操作,下面我们进入管理系统的场景下进行操作,这个管理系统中的增删改查的基本逻辑。

首先三层架构中

  • colltroller:用来接收和传递参数值
  • service:在这层调用mapper接口中的方法或者回调的函数数据
  • mapper:在这层用来定义SQL语句操作数据库,又或者在resources配置文件夹中,建立同名同包的文件,来映射接口的方法

注意在三层架构中,注重面向接口开发,所以在文件中,一般是面向接口开发,使用privete来定义接口文件对象

注意,特别注意,接口开发要根据 接口文档 开发,避免出现前后端不能联调的问题

注解

在项目开发中我们一般会采用各种依赖来进行简化代码操作,而这种依赖通过注释注解来进行标注

注意:如果想要使用注解,需要引入所对应的依赖

@Data

主要用来提高代码的简洁,这个注解可以自动创建get(),set(),toString()等方法

此注解需要引入Lombok依赖

另外

@AllArgsConstructor : 注在类上,提供类的全参构造
@NoArgsConstructor : 注在类上,提供类的无参构造

优点是可以自动生成各种构造器和方法,缺点是 不支持多种参数构造器的重载

另外Lombok中可以使用@Slf4j:注解在类上,提供对应的Logger对象,变量名为log

@RestController

这注解是专门用来处理Http请求处理的

RestController是Controller的一个衍生注解,@RestController 是 @Controller 和 @ResponseBody两个注解的结合体

我们通常在colltroller中顶层使用这个注释,表名该类中的所有方法都会以JSON/XML的格式返回响应

因为在colltroller层负责数据前端和后端的传递,可以通过其他注释来进行方法路由的标识

  • @GetMapping Get方法的路由
  • @PostMapping POST方法的路由
  • @DeleteMapping DELETE方法的路由
  • @PutMapping put方法的路由

@RequestMapping

当你的这个文件中的路由路径都是相同的时候就可以使用这个注释标注路径,用于映射HTTP请求的路径(post,put,delete,get)

或者在方法上使用注解分别标记具体请求路径

  • @GetMapping
  • @PostMapping
  • @DeleteMapping
  • @PutMapping

列如 在文件头部

@RequestMapping("/customers")

@Autowired

@Autowired 注释可以标注在属性上,方法上,构造器上来完成自动装配。默认是根据属性类型,spring自动将匹配到的属性值进行注入,然后就可以使用这属性

当标注的属性是接口时,其实注入的是这个接口的实现类,如果这个接口又多个实现类,只使用@Autowired就会进行报错,因为它默认是根据类型去找。

@RequestBody

主要用来接收前端传递给后端的数据(请求体)

将HTTP请求的请求体内容绑定到方法参数上,常用于接收JSON和XML格式的请求数据

注意: 需要确保请求的Content-Type和方法的参数类型匹配

@RequestBody和@RequestParam可以同时使用,@RequesBody最多只能有一个,而@RequestParam可以有多个

@ResponseBody

可以用于方法上,标记该方法的返回值作为HTTP响应的内容

注意返回值的数据类型,会根据不同的数据类型进行相应的转换处理,常用于返回JSON,XML等格式的数据

@DateTimeFormat

常用于方法参数上,指定日期时间格式

  @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate begin;//开始时间

@PathVariable

用于方法参数上,用于获取HTTP请求路径中的占位符变量

列如请求路径参数为/{id}

/clazzs/3

项目前期准备

数据库数据表dept准备

响应实体类

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 后端统一返回结果
 */
@Data
@NoArgsConstructor  //无参构造
@AllArgsConstructor  //有参构造
public class Result {

    private Integer code; //编码:1成功,0为失败
    private String msg; //错误信息
    private Object data; //数据

    public static Result success() {
        Result result = new Result();
        result.code = 1;
        return result;
    }

    public static Result success(Object object) {
        Result result = new Result();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static Result error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

Dept对象封装实体类

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor  //无参构造
@AllArgsConstructor  //有参构造
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;

}

接口请求类型实体类封装

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor  //无参构造
@AllArgsConstructor  //有参构造
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;

}

注意,层与层之间面向接口开发,实际注入的接口的实现类

    @Autowired
private DeptService deptService;//面向接口编程,多态的体现

查询所有部门操作

controller -> service -> mapper

查询操作的路径为Get,所以在controller层中写接收返回方法时,要在头部加上@GetMapping标识Get方法的路由

 /**
     * 查询部门
     *
     * @throws Exception
     */
    @GetMapping
    public Result list() throws Exception {
        List<Dept> deptList  = deptService.list();
      return Result.success(deptList);

    }

在service层下定义list()方法,在接口的实现类中接收处理的数据

 /**
     * 查看所有部门
     * @return
     */
    @Override
    public List<Dept> list() {
        return deptMapper.findAll();
    }

在mapping接口中,写SQL语句获取或者操作数据内容

    @Select("select *from dept")
    public List<Dept> findAll();

根据id删除操作

本操作需要前端向后端传入id值,所以需要使用参数方法,并且不需要向前端返回数据,所以success()中不需要返回数据

   @DeleteMapping
    public Result delete(Integer id){
        System.out.println("根据ID删除部门;"+id);
        deptService.delete(id);
        return Result.success();
    }

在service层中,定义delete方法,实现类实现,

  @Override
    public void delete(Integer id) {
        deptMapper.deleteById(id);
    }

并在mapper层实现SQL操作数据库

    @Delete("delete from dept where id = #{id} ")
    void deleteById(Integer id);

根据id增加数据

增加数据,在前端页面中像后端传递的是一个对象,增加操作也不需要返回数据

/**
     * 新增部门
     * @param dept
     * @return
     */
    @PostMapping
    public Result add(@RequestBody Dept dept){
        System.out.println("新增部门:"+dept);
        deptService.add(dept);
        return Result.success();
    }

在service层中定义add方法,但因为封装的类中,有时间参数,我们通常将修改时间的数值,使用now()函数获取当前时间传入

  @Override
    public void add(Dept dept) {
        //为基础属性进行赋值
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.insert(dept);
    }

在mapping层写SQL语句

   /**
     * 根据id增加部门数据
     * @param dept
     */
    @Insert("insert into dept(name,create_time,update_time) values(#{name},#{createTime},#{updateTime})")
    void insert(Dept dept);

修改数据

修改数据业务逻辑

前端页面修改页面首先点击数据,此时前端向后端传递id值,后端返回对象数据,前端页面回显数据,前端再进行修改,将修改的数据传入后端数据库,此时,修改完毕后不需要向前端返回数据

根据id获取对象数据

获取数据实际上是查询操作,所有需要返回数据

 @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id){
        System.out.println("根据id查询部门"+id);
        Dept dept = deptService.getById(id);
        return Result.success(dept);
    }

Service层

 @Override
    public Dept getById(Integer id) {
        return deptMapper.getById(id);
    }

Mapper层

根据接口手册操作返回数据

    @Select("select id,name,create_time,update_time from dept where id = #{id} ")
    Dept getById(Integer id);

修改数据

controller层

    /**
     * 修改部门
     * @param dept
     * @return
     */
    @PutMapping
    public Result update(@RequestBody Dept dept){
    System.out.println("修改部门:"+dept);
    deptService.update(dept);
    return Result.success();
}

Service层

  @Override
    public void update(Dept dept) {
        //补全基础属性
        dept.setUpdateTime(LocalDateTime.now());
        //更新数据
        deptMapper.update(dept);
    }

Mapper层

我们在修改数据时,如果采用@Update的形式,安全性不高,可能会发送SQL注入安全问题,所以我们采用映射文件XML的方式对传参数据安全校验

/**
     * 根据id修改部门数据
     * @param dept
     */
//    @Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id} ")
    //采用映射文件严谨,预防SQL注入
    void update(Dept dept);
}

在resources配置文件中创建XML映射文件

注意

  1. 接口名要和对应的映射文件名称相同,并且路径也相同 (同包同名
  2. 接口中的方法名要和mapper映射文件中唯一标识的id相同
  3. 接口中的全限定名要和mapper映射文件的namespace一致
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.DeptMapper">
<update id="update">
    update dept
    <set>
        <if test="name !=null and name!=''">
            name = #{name},
        </if>
        <if test="updateTime != null">
            update_time = #{updateTime}
        </if>
    </set>
    where id =#{id}
</update>
</mapper>

进阶操作

ok,你已经学会了1+1=2,下面我们来学习这道高数不定积分吧(不是

分页条件查询

本业务已采用mybits分页查询插件

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

业务分析

首先我们先对业务逻辑进行分析,前端页面将列表的数据分页展示,也就是说,前端需要返回一个分页查询的页码page和分页查询的每页记录数pageSize,而后端需要返回总记录数和列表数据。

再根据接口文档来看前端需要请求什么参数。我们以下面列表为例

参数名称 是否必须 示例 备注
name 姓名
gender 1 性别 , 1 男 , 2 女
begin 2010-01-01 范围匹配的开始时间(入职日期)
end 2020-01-01 范围匹配的结束时间(入职日期)
page 1 分页查询的页码,如果未指定,默认为1
pageSize 10 分页查询的每页记录数,如果未指定,默认为10

如果请求参数比较少的话,我们可以将请求参数直接写在方法参数中

代码如下

@GetMapping
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize,
                       String name, Integer gender,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
   log.info("分页查询: {},{},{},{},{}",page,pageSize,name,gender,begin,end);
   return Result.success();

而如果请求参数多的话,我们一般将参数封装到实体类中

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDate;

@Data
@NoArgsConstructor  //无参构造
@AllArgsConstructor  //有参构造
public class EmpQueryParam {

    private String name;
    private Integer gender;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate begin;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate end;

    private Integer page = 1;
    private Integer pageSize = 10;

}

后端响应请求,我们也单独封装到类中

@Data
@NoArgsConstructor  //无参构造
@AllArgsConstructor  //有参构造
public class PageBean {
    private Long total; //总记录数
    private List rows; //结果列表
}

业务实现

Controller层,我们来接收前端请求数据,使用pageBean来接收方法方法处理的数据并返回页面

 @GetMapping
    public Result page(EmpQueryParam queryParam) {


        log.info("分页查询:{}", queryParam);
        PageBean pageBean = empService.page(queryParam);
        return Result.success(pageBean);
    }

Service层

  • 无插件

无插件正常实现的情况下,我们需要方法处理得到需要返回pageBean中的数据,也就是总记录数和结果列表

//1.调用mapper接口获取总记录数 total
       Long total= empMapper.count();
        //2.调用mapper接口获取结果列表 rows
        Integer start = (page-1)*pageSize;
       List<Emp> rows= empMapper.list(start,pageSize);
        //3.封装结果
        return new PageBean(total,rows);
  • 有插件
//1.设置分页参数
        PageHelper.startPage(queryParam.getPage(),queryParam.getPageSize());
//2.调用Mapper接口方法
        List<Emp> empList=empMapper.list(queryParam);

        //3.解析并封装结果
        Page<Emp> p = (Page<Emp>) empList;
        return new PageBean(p.getTotal(),p.getResult());
    }

Mapper层

由于SQL语句比较复杂,并且还是条件查询,我们需要考虑,如果条件为空的时候,进行修改问题

   List<Emp> list(EmpQueryParam queryParam);

xml映射文件

<select id="list" resultType="com.itheima.pojo.Emp">
    select emp.*,dept.name deptName from emp left join dept on emp.dept_id = dept.id
    <where>
    <if test="name!=null and name != '' ">
        emp.name like concat('%',#{name},'%')
    </if>
        <if test="gender!=null">
            and emp.gender=#{gender}
        </if>

        <if test="begin!=null and end!=null">
      and emp.entry_date between #{begin} and #{end}
        </if>
</where>
order by emp.update_time desc
</select>

新增数据之同时添加多个

业务分析

当业务要求中,添加操作步骤比较复杂,列如添加员工信息中,有批量添加操作,我们通常会用集合来进行接收,返回后端,而在代码层面上我们会进行分步操作

接口手册前端请求参数

名称 类型 是否必须 备注
username string 必须 用户名
name string 必须 姓名
gender number 必须 性别, 说明: 1 男, 2 女
image string 非必须 图像
deptId number 非必须 部门id
entryDate string 非必须 入职日期
job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师
salary number 非必须 薪资
exprList object[] 非必须 工作经历列表
|- company string 非必须 所在公司
|- job string 非必须 职位
|- begin string 非必须 开始时间
|- end string 非必须 结束时间

在这个案例中,工作经历列表是批量添加操作,我们会用exprList来存储,并且在Service层分为基本信息和工作经历两部分进行操作,我们通常将他们看作一组 事务,成功执行提交事务,不成功则进行回滚,保证数据的一致性

业务实现

controller层

/**
     * 新增员工
     */
    @PostMapping
    public Result add(@RequestBody Emp emp) {
        log.info("新增员工:{}", emp);
        empService.add(emp);
        return Result.success();
    }

Service层

/**
 *新增员工
 */
@Transactional(rollbackFor = {Exception.class}) 
// 事务管理的注释-所有异常都会回滚
//将当前方法交给spring进行事务管理,方法执行前,开启事务,成功执行完毕提交事务,出现异常,回滚事务
@Override
public void add(Emp emp){
    //1.调用empMapper保存员工的基本信息
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    empMapper.insert(emp);
    //2.调用empEmpMapper保存员工的工作经历
    List<EmpExpr> exprList = emp.getExprList();
    if (!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> {
            empExpr.setEmpId(emp.getId());
        });
    }
    empExprMapper.insertBatch(exprList); //批量保存工作经历信息


}

mapping层

@Options(useGeneratedKeys = true,keyProperty = "id") //需要获取数据库自动增长的id
@Insert("insert into emp (username,name,gender,phone,job,salary,image,entry_date,dept_id,create_time,update_time) " +
        "values (#{username},#{name},#{gender},#{phone},#{job},#{salary},#{image},#{entryDate},#{deptId},#{createTime},#{updateTime});")
    void insert(Emp emp);

//批量保存工作经历信息xml映射
<insert id="insertBatch">
      insert into emp_expr(emp_id, begin, end, company, job) VALUES
      <foreach collection="exprList" item="expr" separator=",">
          (#{expr.empId},#{expr.begin},#{expr.end},#{expr.company},#{expr.job})
      </foreach>
  </insert>

批量删除数据+双表同时删除

业务分析

首先前端向后端传入需要删除的id值,有可能是一个也有可能是多个,所以单个删除和批量删除可以写一个接口功能中,其次前端向后端传入的是一个数组,删除不需要返回数据

业务实现

controller层接收数组,可以使用数组接收,也可以使用集合接收

 @DeleteMapping
    public Result delete(@RequestParam List<Integer> ids) {
        log.info("批量删除:ids={}", ids);
        empService.delete(ids);
        return Result.success();
    }

Service层

员工数据中分为两个表,一个表为员工基本信息,另一个是员工工作经历信息,所以我们可以分开删除

@Transactional
@Override
public void delete(List<Integer> ids) {
    //批量删除员工基本信息
    empMapper.deleteByIds(ids);
    //批量删除员工工作经历信息
    empExprMapper.deleteByEmpIds(ids);
}

deleteByIds方法的SQL语句

 <delete id="deleteByIds">
        delete
        from emp
        where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

deleteByEmpIds方法的SQL语句

<!--    根据员工id批量删除工作经历信息-->
<delete id="deleteByEmpIds">
    delete  from emp_expr where emp_id in
    <foreach collection="empIds" item="empId" open="(" separator="," close=")">
        #{empId}
    </foreach>
</delete>

由于需要批量删除数据,我们需要将传入的id值遍历,并且SQL语句中使用in关键字来进行批量删除

posted @ 2023-12-10 22:27  奕帆卷卷  阅读(253)  评论(0)    收藏  举报