SpringBoot项目后端开发逻辑全面梳理
作者:宁顾取。
SpringBoot项目中包含Mapper层(Dao层)、Entity层(model层)、DTO层、VO层、Service层和Controller层(本篇以学生信息表增删改查为例,梳理各个层之间的逻辑关系、开发流程和注意事项)。
一、各层之间的逻辑关系
1.Controller层、Service层、Mapper层、Entity层的逻辑关系
2.每层的理解
1.mapper层(Dao层)。
mapper层是操作数据库的一层。想要访问数据库并且操作,只能通过mapper层向数据库发送sql语句,将这些通过接口传给service层,对数据库进行操作。主要实现增删改查操作,在mybatis中与xxx.xml内相互一一映射。
mapper层包含xxxDao.java文件和xxxDao.xml
(.xml文件中写sql语句,配置通用查询映射结果)
(Dao.java相当于xml文件的抽象类)
StudentBaseDao.xml
<?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.ai.citicnet.boss.manage.service.dao.student.StudentBaseDao"> <sql id="baseColumns"> id, name, sex, age, chinese, math, tenglish </sql> <select id="queryList" parameterType="com.ai.citicnet.boss.manage.remote.model.vo.student.studentBaseForm" resultType="com.ai.citicnet.boss.manage.remote.model.dto.student.studentDTO"> select * from student </select> <select id="queryRepeat" parameterType="java.util.Map" resultType="com.ai.citicnet.boss.manage.remote.model.dto.student.studentDTO"> select name from student where name = #{abc} <if test="id != null and id != 0"> and id != #{id} </if> </select> </mapper>
StudentBaseDao.java
package com.ai.citicnet.boss.manage.service.dao.student; import com.ai.citicnet.boss.manage.remote.model.dto.rs.RsCheckListInfo; import com.ai.citicnet.boss.manage.remote.model.dto.student.studentDTO; import com.ai.citicnet.boss.manage.remote.model.vo.student.studentBaseForm; import com.ai.citicnet.boss.manage.service.entity.Student.StudentBase; import com.ai.citicnet.boss.manage.service.entity.quote.QuoteBase; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Map; @Mapper @Repository public interface StudentBaseDao extends BaseMapper<StudentBase> { List<studentDTO> queryList(studentBaseForm v); //studentDTO queryRepeat(Map<String,String> param); //List<studentDTO> queryRepeat(studentBaseForm v); List<studentDTO> queryRepeat(String abc,int id); }
注意:
- namespace和resultMap的type要指向正确的地址:namespace指向mapper文件,type指向实体类。
- parameterType用于对应的mapper接口方法接收的参数类型,将信息存入到数据库中。
- resultType用于指定sql输出的结果类型,从数据库中提取相应的数据。
- .xml文件命名的id与.java文件中的名称对应
2.Entity层(model层)
也叫作pojo层,是数据库在项目中的类,在文件包含实体类的属性和对应属性的get、set方法。
实体类中属性同数据库表字段一一对应,对于相应的get、set方法一般不需要书写,实体类上引入@Data注解,会自动注入get、set以及toString方法,减少代码量。
entity表示对数据库中所有标的映射,是根据数据库表字段设计出来的实体(要求表名和类名相同,字段名与成员变量名相同)
StudentBase.java
package com.ai.citicnet.boss.manage.service.entity.Student; import com.ai.citicnet.boss.manage.service.entity.BaseEntity; import lombok.*; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.math.BigDecimal; @Data @NoArgsConstructor @AllArgsConstructor @TableName (value ="student") public class StudentBase { //private static final long serialVersionUID = 1999600885795452253L; @TableField(value = "id") @TableId private int id; @TableField(value = "name") private String name; @TableField(value = "sex") private String sex; @TableField(value = "age") private String age; @TableField(value = "chinese") private String chinese; @TableField(value = "math") private String math; @TableField(value = "english") private String english; }
3.DTO层
数据对象传输层,负责屏蔽后端实体类,将UI要的数据进行重新定义和封装。
DTO里的每个字段,与前端页面对应。
因为后端在实际业务场景中需要储存大量数据,而用户需要的只是一部分,为了快速获取用户需要的数据,应该把用户经常用到的数据在DTO层进行封装,在调用服务时,只需要调用依次便可完成所有的逻辑操作。
逻辑关系如下:
studentDTO.java
package com.ai.citicnet.boss.manage.remote.model.dto.student; import com.ai.citicnet.boss.common.model.BaseInfo; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class studentDTO { private int id; private String name; private String sex; private String age; private String chinese; private String math; private String english; private String sumGrade; // private String sGrade; }
4.VO层
视图对象,用于展示层返回给前端。
把某个指定页面的所有数据封装起来,方便前端获取数据,后端将前端需要的数据做整合,打包成一个类。
使用场景:如果在前端页面需要展示经过某些数据库操作才能展示的特定数据,一般在vo层中把操作过程中涉及到的数据进行封装,方便前端获取。在controller层定义对应接口时把返回类型规定为vo类。
studentBaseForm.java
package com.ai.citicnet.boss.manage.remote.model.vo.student; import com.ai.citicnet.boss.manage.remote.utils.ManagePageRequest; import lombok.Data; @Data public class studentBaseForm extends ManagePageRequest { private int id; private String name; private int sex; private int age; private int chinese; private int math; private int english; }
5.service层
业务服务层,调用mapper层API并提供给controller层使用,间接和数据库打交道。
包括两部分,接口文件(Service.java)和接口实现类文件(ServiceImpl.java)。
接口文件中定义在controller层中调用的service层方法;接口实现类文件中完成service层接口中定义的方法的实现。
在该层进行复杂的业务逻辑处理,在对多个mapper层查到的数据进行组装、处理,然后将结果返回给controller。因此,在一般情况下,一个controller中可能包括多个Service,而一个Service中又或许包含多个mapper。
注意:这里接口实现类中方法的实现是指业务逻辑的实现,可能有些方法并不能在实现类里完成真正意义上的实现,还需要在mapper层文件完成其真正意义上的实现(主要是和数据库交互)。
StudentBaseService.java
package com.ai.citicnet.boss.manage.service.service.student; import com.ai.citicnet.boss.manage.remote.model.dto.student.studentDTO; import com.ai.citicnet.boss.manage.remote.model.vo.student.studentBaseForm; import com.ai.citicnet.boss.manage.remote.utils.ManagePageResult; import com.ai.citicnet.boss.manage.service.entity.Student.StudentBase; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; public interface StudentBaseService extends IService<StudentBase> { ManagePageResult studentList(studentBaseForm vo); studentDTO studentDetail(int name); void saveStudent(studentDTO studentDTO) throws Exception; void delUser(int name); }
StudentBaseServiceImpl.java
package com.ai.citicnet.boss.manage.service.service.impl.student; import com.ai.citicnet.boss.manage.remote.model.dto.student.studentDTO; import com.ai.citicnet.boss.manage.remote.model.vo.student.studentBaseForm; import com.ai.citicnet.boss.manage.remote.utils.ManagePageResult; import com.ai.citicnet.boss.manage.service.dao.student.StudentBaseDao; import com.ai.citicnet.boss.manage.service.entity.Student.StudentBase; import com.ai.citicnet.boss.manage.service.exception.ManageException; import com.ai.citicnet.boss.manage.service.service.UserInfoService; import com.ai.citicnet.boss.manage.service.service.student.StudentBaseService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Slf4j @Service("studentBaseService") public class StudentBaseServiceImpl extends ServiceImpl<StudentBaseDao, StudentBase> implements StudentBaseService { @Autowired StudentBaseService studentBaseService; @Autowired UserInfoService userInfoService; @Override public ManagePageResult studentList(studentBaseForm vo) { PageHelper.startPage(vo.getPageNum(), vo.getPageSize()); List<studentDTO> pageList = this.baseMapper.queryList(vo); for(studentDTO item : pageList){ int chinese = Integer.parseInt(item.getChinese()); int math = Integer.parseInt(item.getMath()); int english = Integer.parseInt(item.getEnglish()); int sum = chinese+math+english; if((sum)>180){ item.setSumGrade("及格"); }else{ item.setSumGrade("不及格"); } } PageInfo<studentDTO> pageInfo = new PageInfo<>(pageList); return ManagePageResult.getPageResult(pageInfo); } @Override public studentDTO studentDetail(int studentNo) { if (ObjectUtils.isEmpty(studentNo)) { throw new ManageException("error!"); } QueryWrapper<StudentBase> wrapper = new QueryWrapper<>(); wrapper.eq("id", studentNo); StudentBase studentBase = this.baseMapper.selectOne(wrapper); if (ObjectUtils.isEmpty(studentBase)) { throw new ManageException("未找到对应的信息,请检查输入是否正确!"); } studentDTO studentDTO = new studentDTO(); BeanUtils.copyProperties(studentBase, studentDTO); return studentDTO; } @Override @Transactional(rollbackFor = Exception.class) public void saveStudent(studentDTO studentDTO) throws Exception { //保存信息 StudentBase studentBase = new StudentBase(); BeanUtils.copyProperties(studentDTO, studentBase); List<studentDTO> result = this.baseMapper.queryRepeat(studentBase.getName(),studentBase.getId()); if(ObjectUtils.isEmpty(result)){ this.save(studentBase); }else{ throw new ManageException("用户名重复"); } } //删除 @Override @Transactional(rollbackFor = Exception.class) public void delUser(int name) { log.info("删除信息入参:【{}】", name); if (ObjectUtils.isEmpty(name)) { throw new ManageException("入参错误!"); } // QueryWrapper<StudentBase> wrapper = new QueryWrapper<>(); // wrapper.eq("id", name); // StudentBase studentBase = this.baseMapper.selectOne(wrapper); this.removeById(name); } }
6.Controller层
本层定义接口并调用service逻辑设计层的接口来控制业务流程。
功能:接收前端请求,调用service,接收service返回的数据,再将处理结果返回到前端(接收前端数据,返回页面请求信息)。
Controller层是不允许直接操作数据库的!它就像一个服务员,有客人需要点菜,就喊服务员;对前端需要完成什么业务,就告诉Controller。Controller只是一个中间者或转发者。
不能在Controller里暴露Service的业务逻辑,而应该直接转发Service的业务处理结果。
QuoteBaseController11.java
package com.ai.citicnet.boss.manage.service.controller.quote; import com.ai.citicnet.boss.manage.remote.model.dto.student.studentDTO; import com.ai.citicnet.boss.manage.remote.model.vo.student.studentBaseForm; import com.ai.citicnet.boss.manage.remote.utils.ManagePageResult; import com.ai.citicnet.boss.manage.remote.utils.ResponseResult; import com.ai.citicnet.boss.manage.service.enumeration.common.RespStatus; import com.ai.citicnet.boss.manage.service.service.student.StudentBaseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/studentBase") public class QuoteBaseController11 { @Autowired // spring自动注入studentBaseService赋值 private StudentBaseService studentBaseService; //PostMapping用于认证方法,将http post请求映射到特定处理程序 @PostMapping("/studentList") public ResponseResult queryList(@RequestBody studentBaseForm vo) { try { ManagePageResult page = studentBaseService.studentList(vo); return ResponseResult.ok().put(page); } catch (Exception e) { e.printStackTrace(); return ResponseResult.error(RespStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); } } @PostMapping("/studentDetail") public ResponseResult studentDetail(@RequestParam("name") int studentNo) { try { studentDTO studentDTO = studentBaseService.studentDetail(studentNo); return ResponseResult.ok().put(studentDTO); } catch (Exception e) { e.printStackTrace(); return ResponseResult.error(RespStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); } } //保存数据 @PostMapping("/saveStudent") public ResponseResult saveStudent(@RequestBody studentDTO studentDTO) { try { studentBaseService.saveStudent(studentDTO); return ResponseResult.ok().put(true); } catch (Exception e) { e.printStackTrace(); return ResponseResult.error(RespStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); } } //删除数据 @PostMapping("/deleteStudent") public ResponseResult deleteStudent(@RequestParam("name") int name) { try { studentBaseService.delUser(name); return ResponseResult.ok().put(true); } catch (Exception e) { e.printStackTrace(); return ResponseResult.error(RespStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); } } }
二、运行流程
- 1.控制层接收前端请求,调用对应的业务层接口方法
- 2.业务层实现类去实现业务层接口
- 3.业务层实现类的方法调用数据层的接口
- 4.数据层实现文件(mapper.xml)实现数据层接口
- 5.处理结果,逐层返回。
三、开发流程
- 1.实体类
- 2.service接口
- 3.XML文件
- 4.mapper接口
- 5.serviceImpl实现类
- 6.controller层调用接口
- 7.最终接口展示
1、实体类
实体类连接着前端接口内容,返回的响应参数、参数的数据类型和注释都来自于这里;所以我们把创建实体类作为开始编码的第一步。
建立实体类,就能知道我们最终是想要得到什么值返回给前端,这样,我们以后的步骤都是围绕着如何得到我们的实体类而来。
注意:
因为很多时候返回给前端的是一个集合类型,存储着很多的数据。这个时候,我们一般建立两个实体类,一个用来存储List<>集合,另一个用来存储该集合里的内部数据。
package com.hncr.system.domain.vo; //用户集合实体类 @Data public class UserVo { @ApiModelProperty("用户集合") private List<UserListVo> userListVos; }
package com.hncr.system.domain.vo; //用户类 @Data public class UserListVo { @ApiModelProperty("姓名") private String name; @ApiModelProperty("年龄") private Integer age; @ApiModelProperty("性别:(0:男,1:女)") private Integer sex; }
2、Service接口
“接口编程”思想大概就是基于Service接口进行的吧!
返回值类型是UserVo实体类(因为最终要得到的值就是要返回一个集合给前端,所以这里的返回类型就是该实体类)
package com.hncr.system.service; import com.hncr.system.domain.vo.UserVo; //Service接口 public interface IUserService { UserVo userSummary(); }
3、xml文件
接下来,我们将sql语句写好(很重要!!!)
确定需要多少个sql语句才能解决具体的Service接口问题。
可以先在Navicate中做一些实力操作(根据年龄进行排序)
SELECT name, age, sex FROM `user` ORDER BY age DESC
(在实际项目中,要查询或者改变的操作会很多,数据量也会很大,很多时候要对某个字段做索引操作才会缩短查询时间,查询时间超过3秒或4秒的sql,在前端来看就是一个相当慢的接口了。)
数据量大的数据库,我们尽量做到只访问一次,以免浪费不必要的时间,影响用户体验。
在Navicat中查到想要的数据后,把sql语句加入到我们的XML文件中,结合我们的namespace、id、resultType(第四步会配置)。
<?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.hncr.system.mapper.UserMapper"> <select id="userSummaryMapper" resultType="java.util.Map"> SELECT name, age, sex FROM `user` ORDER BY age DESC </select> </mapper>
4、Mapper接口
第三步确定,一条sql语句就可以搞定想要的效果,那Mapper接口写一个即可(反正就是sql写了几个,我们对应的就得写几个Mapper接口)。
package com.hncr.system.mapper; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Map; //Mapper接口 @Repository public interface UserMapper { List<Map> userSummaryMapper(); }
可以看到,这里的返回值类型不太一样了,我们要确保xml文件中的resultType要和Mapper接口中的一一对应上,否则查询不到数据,接口报错。
(在实际项目中,后续的这种接口方式用的较多,list、Map、List<Map>)
集合很重要,项目中大多数都会使用集合!!!
id对应上我们的Mapper接口名字,这里是“userSUmmaryMapper”
注意:
- xml文件多少个sql,mapper接口就写多少个。
- mapper接口返回类型,接口名称要与xml文件中resultType、id 等属性一一对上。
5、ServiceImpl实现类
至关重要的一步!!!
在这里要实现数据的交互、代码逻辑。
package com.hncr.system.service.impl; import com.hncr.system.domain.vo.UserListVo; import com.hncr.system.domain.vo.UserVo; import com.hncr.system.mapper.UserMapper; import com.hncr.system.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; @Service public class UserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; List<Map> userSummaryMapper(){ return userMapper.userSummaryMapper(); } public UserVo userSummary(){ UserVo userVo = new UserVo(); List<Map> list1 = userSummaryMapper(); List<UserListVo> list2 = new ArrayList<>(); for (int i = 0; i < list1.size(); i++) { UserListVo userListVo = new UserListVo(); Map map = list1.get(i); userListVo.setName((String) map.get("name")); userListVo.setAge((Integer) map.get("age")); userListVo.setSex((Integer) map.get("sex")); list2.add(userListVo); } userVo.setUserListVos(list2); return userVo; } }
6、Controller层调用接口
走到这里,基本就迎来了收尾工作。
Controller层就是一个调用的过程,将相关的注解写好,注入相关的接口方法,然后返回给前端。这样我们就完成了其中一个接口。同理再逐个完成其他接口就可以啦。
package com.hncr.web.controller.system; import com.hncr.system.domain.JsonResult; import com.hncr.system.domain.vo.UserVo; import com.hncr.system.service.IUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Api(value = "用户信息", tags = "用户信息") @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; @ApiOperation(value = "用户信息汇总", notes = "{<br/>" + " \"msg\": \"操作成功\",<br/>" + " \"code\": 200,<br/>" + " \"data\": {<br/>" + " }<br/>" + "}") @GetMapping("UserSummary") public JsonResult<UserVo> userSummary(){ UserVo userVo = userService.userSummary(); return JsonResult.success(userVo); } }
一个完整的SpringBoot项目的完整后端开发就这样完成啦!!!
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。