初识 spring springboot mybatis
手把手带你做一个简单的 mvc 应用。
一、代码结构

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

浙公网安备 33010602011771号