初识 spring springboot mybatis

手把手带你做一个简单的 mvc 应用。

一、代码结构

 代码结果如上图,主要包括:

  1. controller: 控制层,作用是对外接收用户请求,对内调度对应 service。也就是常说的 api url。
  2. service: 服务层,作用是实现业务逻辑。比如用户商品购买需要生成订单然后扣库存,也就是至少两个数据层操作,先调订单 dao 增加订单,再调库存 dao 减库存。
  3. entity: 实体,作用是对内映射数据库对象,对外映射接口请求对象和接口返回对象。例如:一个用户信息他在数据库中的基本信息只有姓名、密码两个字段。另外用户还对应一个爱好表,用户跟爱好的关系是一个1对多关系。这样我们可以定义一个User的实体,有两个成员参数 name、password ,用这个实体可以映射 user 表的数据。再定一个 UserRequest 实体对象,除了 name、password 外还有一个 List 保存爱好,这样可以用 UserRequest 接收接口传过来的数据。接口返回对象雷同,当需要一个新的接口返回数据时,可以定义一个接口返回对象。
  4. dao: 数据层 ,作用是操作数据库,也就是常说的 CRUD。

 二、controller

当完成需求分析后,一般设计都是从低往上的(entity -> dao -> service -> controller )。但这么写你可能感觉没啥意思,写了半天也看不到任何效果。所以这次咱们反过来先从 controller 下手。

当然如果公司没有接口平台,不能定义接口和 mock 接口的时候,也会先实现 controller,这样可以快速完成 api 文档和前后端分离开发。

业务背景:

  做了一个服务器检查的功能,其中每一台服务器需要检查多个内容,并且每台服务器要检查的内容不同。这次要增加的功能就是添加要检查的条目。

1、创建 StepController ,如下图

package com.bjy.qa.controller.servercheck;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/serverCheck/step")
public class StepController {
    private final Log logger = LogFactory.getLog(getClass());

    @RequestMapping("/add")
    @ResponseBody
    public Object add(@RequestBody String step) throws Exception {
        logger.info("add step:" + step);

        return "response";
    }
}

 

2、点击 运行

3、在 postman 中测试,配置如下图

 三、service

下面开始实现业务层,service 分为两部分 接口、实现。

1、创建接口,添加类选择 Interface 接口名称是 IStepService

注意:大写 I 开头,说明这是一个接口

 添加后如下图,由于没有定义实体对象,所以暂时当 String 传入

package com.bjy.qa.service.servercheck;

public interface IStepService {

    /**
     * 新增一条数据
     * @param step
     * @return
     */
    Object add(String step);

}

2、此时只有接口还没有实现,所以需要添加一个实现

package com.bjy.qa.service.servercheck.impl;

import com.bjy.qa.service.servercheck.IStepService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

@Service
public class StepService implements IStepService {
    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public Object add(String step) {
        logger.info("add step:" + step);

        return "成功执行 service";
    }
}

3、controller 中调用此 service

package com.bjy.qa.controller.servercheck;

import com.bjy.qa.service.servercheck.IStepService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

@Controller
@RequestMapping("/serverCheck/step")
public class StepController {
    private final Log logger = LogFactory.getLog(getClass());

    @Resource
    IStepService iStepService;

    @RequestMapping("/add")
    @ResponseBody
    public Object add(@RequestBody String step) throws Exception {
        logger.info("add step:" + step);

        return iStepService.add(step);
    }
}

4、重新运行后再次调用,你能看到这次返回的是 service 中的内容。

 四、entity

1、下面就要定义实体了,在 entity 中增加 step 实体,实体继承自 BaseEntity,BaseEntity 中包括了基本字段(id:自增id、createdAt:创建时间、updatedAt:更新时间、isDelete:逻辑删除标记)

package com.bjy.qa.entity.servercheck;

import com.bjy.qa.entity.BaseEntity;

public class Step extends BaseEntity<Step> {
    @Override
    public String toString() {
        
        return "Step{} " + super.toString();
    }
}

2、Controller、Service 中参数传递改为实体

  

 3、再次运行,你能看到 id 和 createdAt 两个参数被 BaseEntity 收到了

 4、定义业务字段,数据库中 step 表如下图

除了 id、createdAt、updatedAt、isDelete 外还有 server_check_id(bigint)、desc(varchar)、key(varchar)、expected(text)、runTag(varchr)。其中 bigint 对应 java 的 Long、varchar 和 text 对应 java 的 String。

 在 实体(step.java)中定义字段并 生成 set、get、toString 方法,如下图

package com.bjy.qa.entity.servercheck;

import com.bjy.qa.entity.BaseEntity;

public class Step extends BaseEntity<Step> {
    private Long serverCheckId; // 外健,对应 serverCheck id
    private String desc; // 描述
    private String key; // 需要检查的 key
    private String expected; // 预期结果
    private String runTag; // 运行 tag

    public Long getServerCheckId() {
        return serverCheckId;
    }

    public void setServerCheckId(Long serverCheckId) {
        this.serverCheckId = serverCheckId;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getExpected() {
        return expected;
    }

    public void setExpected(String expected) {
        this.expected = expected;
    }

    public String getRunTag() {
        return runTag;
    }

    public void setRunTag(String runTag) {
        this.runTag = runTag;
    }

    @Override
    public String toString() {
        return "Step{" +
                "serverCheckId=" + serverCheckId +
                ", desc='" + desc + '\'' +
                ", key='" + key + '\'' +
                ", expected='" + expected + '\'' +
                ", runTag='" + runTag + '\'' +
                "} " + super.toString();
    }
} 

5、修改提交 json 后重新运行

{
    "desc": "描述",
    "key": "key"
}

五、dao

最后就剩下操作数据库了。

1、添加 StepDao,如下图。因为集成了 mybatis、mybatisplus 添加完 dao 后就完成了基本的 CURD

package com.bjy.qa.dao.servercheck;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bjy.qa.entity.servercheck.Step;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface StepDao extends BaseMapper<Step> {

}

 2、修改 StepService。例子中写的是 add,那就完成数据库插入。如下图

 

package com.bjy.qa.service.servercheck.impl;

import com.bjy.qa.dao.servercheck.StepDao;
import com.bjy.qa.entity.servercheck.Step;
import com.bjy.qa.service.servercheck.IStepService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class StepService implements IStepService {
    private final Log logger = LogFactory.getLog(getClass());

    @Resource
    StepDao stepDao;

    @Override
    public Object add(Step step) {
        logger.info("add step:" + step);

        stepDao.insert(step);

        return "成功执行 service";
    }
}

 3、重新运行,报错了,原因是 desc、key 这两个字段是 mysql 关键字,错误如下图

 4、修改 Setp Entity,加入 TableField 指定字段名

    @TableField("`desc`")
    private String desc; // 描述
    @TableField("`key`")
    private String key; // 需要检查的 key

5、重新运行,成功

 

 

 六、返回从字符串改成包装对象

上边 add 接口只是返回一个字符串,每次成功和失败都要包装 code、msg 等信息。所以可以改成直接返回对象

1、修改 StepService 将返回字符串改为返回 dao 结果对象。

 

 

 2、修改 StepController 将 service 返回的内容用 Response.success 包装一下

 

 

 3、再次运行,会自动添加 code、msg

 

4、除了成功还有失败等,参看 

 

 :)-- 以上就是基本的 mvc 模型  -- (:

下面还会讲一些其他技巧

七、字段校验

1、字段校验

一般情况下,接口请求参数需要有一些必填、长度、类型等校验,该如何实现呢?

1、修改 Setp Entity,加入 NotEmpty 非空校验

    @NotEmpty(message = "desc: 描述字段必填")

2、修改 StepController 

 

 

 3、去掉 desc 参数后,重新运行

 4、除了 NotEmpty 还有如下,参见 https://blog.csdn.net/leaf__yang/article/details/126037361

  • @Null 元素必须为null
  • @NotNull 元素不能null
  • @AssertTrue 元素必须为true
  • @AssertFalse 元素必须是false
  • @Min(value) 元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value) 元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value) 元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max,min) 元素的大小必须在指定的范围内
  • @Digits(integer,fraction) 元素必须是一个数字,其值必须在可接受的范围内
  • @Past 元素必须是一个过去的日期
  • @Future 元素必须是一个将来的日期
  • @Pattern(value) 元素必须符合指定的正则表达式
  • @Email 元素必须是电子邮箱地址
  • @Length 字符串的大小必须在指定的范围内
  • @NotEmpty 字符串必须非空
  • @Range 元素必须在合理的范围内

2、分组校验 groups

当两个接口,一个接口要校验 desc 不能为空,另一个接口要校验 key 不能为空,那怎么办呢?可以按照分组校验

1、增加后如下图

 

 

package com.bjy.qa.entity.servercheck;

import com.baomidou.mybatisplus.annotation.TableField;
import com.bjy.qa.entity.BaseEntity;

import javax.validation.constraints.NotEmpty;

public class Step extends BaseEntity<Step> {
    private Long serverCheckId; // 外健,对应 serverCheck id
    @NotEmpty(groups = Step.Step1Group.class, message = "desc: 描述字段必填")
    @TableField("`desc`")
    private String desc; // 描述
    @NotEmpty(groups = Step.Step2Group.class, message = "key: key字段必填")
    @TableField("`key`")
    private String key; // 需要检查的 key
    private String expected; // 预期结果
    private String runTag; // 运行 tag

    public Long getServerCheckId() {
        return serverCheckId;
    }

    public void setServerCheckId(Long serverCheckId) {
        this.serverCheckId = serverCheckId;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getExpected() {
        return expected;
    }

    public void setExpected(String expected) {
        this.expected = expected;
    }

    public String getRunTag() {
        return runTag;
    }

    public void setRunTag(String runTag) {
        this.runTag = runTag;
    }

    @Override
    public String toString() {
        return "Step{" +
                "serverCheckId=" + serverCheckId +
                ", desc='" + desc + '\'' +
                ", key='" + key + '\'' +
                ", expected='" + expected + '\'' +
                ", runTag='" + runTag + '\'' +
                "} " + super.toString();
    }

    public static interface Step1Group {
    }

    public static interface Step2Group {
    }
}

2、修改 StepController 

package com.bjy.qa.controller.servercheck;

import com.bjy.qa.entity.servercheck.Step;
import com.bjy.qa.entity.user.User;
import com.bjy.qa.service.servercheck.IStepService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.validation.Valid;

@Controller
@RequestMapping("/serverCheck/step")
@Validated
public class StepController {
    private final Log logger = LogFactory.getLog(getClass());

    @Resource
    IStepService iStepService;

    @RequestMapping("/add")
    @ResponseBody
    @Validated(Step.Step1Group.class)
    public Object add(@RequestBody @Valid Step step) throws Exception {
        logger.info("add step:" + step);

        return iStepService.add(step);
    }

    @RequestMapping("/add1")
    @ResponseBody
    @Validated(Step.Step2Group.class)
    public Object add1(@RequestBody @Valid Step step) throws Exception {
        logger.info("add step:" + step);

        return iStepService.add(step);
    }
}

 

  3、重新运行

调用接口 add1 接口提示 desc 必填

调用接口 add1 接口时提示 key 必填

 

八、高阶sql

上例完成的是基础的 CRUD 操作,那遇到 关联查询、批量插入 该如何处理呢。

1、关联查询

1、在对应 dao 中增加如下代码,此例有 3 个表(user、company、project),其中 user 是主表,company、project 表左连接 user 表

    @Select("SELECT " +
            "   `user`.*, " +
            "   company.NAME AS company_name, " +
            "   project.NAME AS project_name " +
            "FROM " +
            "   `user` " +
            "   LEFT JOIN company ON `user`.company_id = company.id " +
            "   LEFT JOIN project ON `user`.project_id = project.id " +
            "WHERE " +
            "   `user`.id = #{id}")
    public User selectById(Long id);

 2、批量插入

下例中传入的是一个 list warehouseDetails,使用 foreach 语句批量插入数据到 server_check_step_log 表中

    @Insert("<script>" +
            "INSERT INTO server_check_step_log (project_id, server_check_task_id, server_check_step_id, status, actual, error_msg, is_delete, updated_at, created_at) VALUES" +
            "<foreach collection='list' item='detail' separator=','> " +
                "(#{detail.projectId}, #{detail.serverCheckTaskId}, #{detail.serverCheckStepId}, #{detail.status}, #{detail.actual}, #{detail.errorMsg}, #{detail.isDelete}, #{detail.updatedAt}, #{detail.createdAt})" +
            "</foreach> " +
            "</script>")
    boolean insertBatch(@Param("list") List<ServerCheckStepLog> warehouseDetails);

 3、当然也支持 mybatis XML 文件的方式

在对应 dao 中增加一个接口,同目录下创建 xml 文件。

注意:dao 中的方法名,要跟 xml 文件中的 id 相同。

 

 

    /**
     * 查询用户完整信息,包括公司及项目列表(需要关联多个表一般不要使用)
     * @param id 用户id
     * @return 用户数据
     */
    public User selectWholeInfoById(@Param("userId") Long id);
<?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.bjy.qa.dao.user.UserDao">
    <resultMap id="AllColumnMap" type="com.bjy.qa.entity.user.User">
        <result column="id" property="id"/>
        <result column="updated_at" property="updatedAt"/>
        <result column="created_at" property="createdAt"/>
        <result column="is_delete" property="delete"/>
        <result column="name" property="name"/>
        <result column="avatar" property="avatar"/>
        <result column="account" property="account"/>
        <result column="company_id" property="companyId"/>
        <result column="project_id" property="projectId"/>
        <collection property="companyList" ofType="com.bjy.qa.entity.manage.Company">
            <result column="c.id" property="id" />
            <result column="c.name" property="name" />
            <collection property="projectList" ofType="com.bjy.qa.entity.manage.Project">
                <result column="p.id" property="id" />
                <result column="p.name" property="name" />
            </collection>
        </collection>
    </resultMap>

    <select id="selectWholeInfoById" resultMap="AllColumnMap">
        SELECT
            u.*,
            c.id AS `c.id`,
            c.`name` AS `c.name`,
            p.id AS `p.id`,
            p.`name` AS `p.name`
        FROM
            `user` AS u
                LEFT JOIN ( SELECT * FROM user_company_project WHERE is_delete = FALSE ) AS ucp ON ucp.user_id = u.id
                LEFT JOIN ( SELECT * FROM company WHERE is_delete = FALSE ) AS c ON c.id = ucp.company_id
                LEFT JOIN ( SELECT * FROM project WHERE is_delete = FALSE ) AS p ON p.id = ucp.project_id
        WHERE
            u.id = #{userId,jdbcType=NUMERIC}
          AND u.is_delete = FALSE
    </select>
</mapper>

 

posted @ 2023-03-03 10:06  rslai  阅读(75)  评论(0编辑  收藏  举报