springboot实现CRUD
一、需求
实现对一张表数据的增删改查,使用springboot+ssm实现后端接口代码,实现接口工具测试
二、搭建环境
2.1 数据库环境
创建数据库: springboot_crud
注意: 确定是否有该库
创建表:
create table stu (id int primary key auto_increment,sname varchar(255),age int,sex char(1),score double(10,2),birthday date );
2.2 项目环境
- 创建springboot注意jdk和springboot版本
- 找之前项目复制pom依赖和yml文件
- 创建项目包结构
- controller,service,mapper,model,util
- 根据表创建实体类
三、 实现功能(单表)
编码思路
- 正写 controller -->service --> mapper
- 反写 mapper -> service --> controller
后续前后端分离开发,一般会有接口文档,文档中定义了请求类型,路径,参数已经响应的结果实例
3.1 查询一个数据
mapper
// 接口
public interface StudentMapper {// 查询一个数据Student findById(int id);
}
<!-- 映射文件 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace关联接口文件 -->
<mapper namespace="com.qf.mapper.StudentMapper"><!-- id是接口的方法名, resultType是返回值结果类型--><select id="findById" resultType="Student">select * from stu where id = #{id}</select>
</mapper>
service
// 接口文件
public interface StudentService {// 查询一个数据Student findById(int id);
}
// 实现类文件
@Service // 创建业务层对象
public class StudentServiceImpl implements StudentService {@Autowired // 注入mapper对象private StudentMapper mapper;@Overridepublic Student findById(int id) {return mapper.findById(id);}
}
controller
// 接收请求
@RestController // 全部返回JSON
@RequestMapping("/stu")
public class StudentController {@Autowired // 注入Service对象private StudentService service;@GetMapping("/find")public R findById(int id){Student student = service.findById(id);return R.ok(student);}
}
主类扫描mapper
@SpringBootApplication
@MapperScan("com.qf.mapper") // 扫描mapper接口产生代理对象
public class Day39SpringbootCrudApplication {public static void main(String[] args) {SpringApplication.run(Day39SpringbootCrudApplication.class, args);}}
启动项目,接口工具测试
3.2 查询全部数据
mapper
// 接口// 无条件查询全部List<Student> findAll();
<!-- 映射文件 --><select id="findAll" resultType="Student">select * from stu</select>
service
// 接口文件// 无条件查询全部List<Student> findAll();
// 实现类文件@Overridepublic List<Student> findAll() {return mapper.findAll();}
controller
// 接收请求
@GetMapping("/list")
public R findAll(){List<Student> list = service.findAll( );return R.ok(list);
}
测试
3.3 条件查询
需求: 模拟搜索框根据年龄或者姓名查询学生
mapper
// 接口// [重点]模拟搜索框根据年龄或者模糊搜索姓名查询学生List<Student> findByKeyword(HashMap<String,Object> map);
<!-- 映射文件 -->
<select id="findByKeyword" resultType="Student"><!--select * from stuselect * from stu where age = 1select * from stu where age = 1 and sname like '%老%'-->select * from stu<where><!-- test中的age是Map中的key以及#{age}中的age也是map中的key--><if test="age != null">and age = #{age}</if><if test="sname != null and sname != ''"><!-- 以下写法不行,是因为#{}相当于?占位符,对字符串会自动拼接'' --><!-- 会报错 --><!--and sname like '%#{sname}%'-->and sname like concat('%',#{sname},'%')</if></where>
</select>
service
// 接口文件List<Student> findByKeyword(HashMap<String,Object> map);
// 实现类文件@Overridepublic List<Student> findByKeyword(HashMap<String, Object> map) {return mapper.findByKeyword(map);}
controller
// 接收请求/*** 如果不熟悉: 看SpringBootV12.md笔记第五章5.3.5 封装Map* @param map* @return*/@GetMapping("/search")public R findAll(@RequestParam HashMap<String,Object> map){List<Student> list = service.findByKeyword(map);return R.ok(list);}
测试
3.4 增加
mapper
// 接口// 增加(参数列表一定是对象)void add(Student student);
<!-- 映射文件 -->
<insert id="add"><!-- #{}内写的对象的属性,是因为从对象中取值 -->insert into stu (sname,age,sex,score,birthday)values (#{sname},#{age},#{sex},#{score},#{birthday})</insert>
service
// 接口文件// 增加void add(Student student);
// 实现类文件@Overridepublic void add(Student student) {mapper.add(student);}
controller
// 接收请求/*** 添加(向数据库发送的,请求方式一般是post)* 能封装数据的前提是前端参数和对象属性名一致*/@PostMapping("/add")public R add(Student student){service.add(student);return R.ok();}
测试
3.5 修改
方案1: 全表更新(推荐)
方案2: 部分更新
mapper
// 接口
// 更新void edit(Student student);
<!-- 映射文件 --><update id="edit">update stu set sname=#{sname},age=#{age},sex=#{sex},score=#{score},birthday=#{birthday} where id = #{id}</update>
service
// 接口文件// 更新void edit(Student student);
// 实现类文件@Overridepublic void edit(Student student) {mapper.edit(student);}
controller
// 接收请求/*** 更新(向数据库发送的,请求方式一般是post)*/@PostMapping("/edit")public R edit(Student student){service.edit(student);return R.ok();}
测试
3.6 删除
mapper
// 接口// 删除void deleteById(int id);
<!-- 映射文件 --><delete id="deleteById">delete from stu where id = #{id}</delete>
service
// 接口文件// 删除void deleteById(int id);
// 实现类文件@Overridepublic void deleteById(int id) {mapper.deleteById(id);}
controller
// 接收请求@GetMapping("/del")public R del(int id){service.deleteById(id);return R.ok();}
测试
四、多表联查
(场景:高中学校)
现有学生表 stu,再设计教室表classroom,与学生表一对一关系,即一个学生固定在一个教室
学科表subject, 与学生是多对多,即一个学生学习多种学科,一个学科多个学生学习
老师表teacher,与学生表是多对多,即一个学生对应多个老师,一个老师对应多个学生,
与学科表是一对一,即一个老师教授一个学科
4.1 一对一(两表)
需求: 查询学生信息以及关联的教室信息
4.1.1 数据库环境
教室表
create table classroom(cid int primary key comment 'id',cnum varchar(255) comment '教室编号',seatNum int comment '座位数'
);
学生与教室是一对一关系,但是教室与学生是一对多关系,所以两表的关联列应该设置在学生表里面。修改学生表,添加cid列,当然Student实体类也需要添加cid属性
4.1.2 实体类
教室类
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Classroom {private int cid;private String cnum;private int seatNum;
}
封装一对一扩展类StudentVO
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO extends Student{private Classroom classroom;
}
4.1.3 一对一查询
StudentMapper
// 查询所有学生信息以及关联的班级信息
List<StudentVO> findAllStudentAndClassroom();
<resultMap id="studentAndClassroomResultMap" type="studentVO"><id column="id" property="id"/><result column="sname" property="sname"/><result column="age" property="age"/><result column="sex" property="sex"/><result column="score" property="score"/><result column="birthday" property="birthday"/><!-- 1对1--><association property="classroom" javaType="Classroom"><id column="cid" property="cid"/><result column="cnum" property="cnum"/><result column="seatNum" property="seatNum"/></association></resultMap><select id="findAllStudentAndClassroom" resultMap="studentAndClassroomResultMap">select * from stu s left join classroom con s.cid = c.cid</select>
service
// 查询所有学生信息以及关联的班级信息List<StudentVO> findAllStudentAndClassroom();
@Overridepublic List<StudentVO> findAllStudentAndClassroom() {return mapper.findAllStudentAndClassroom();}
controller
@GetMapping("/class")public R findAllStudentAndClassroom(){List<StudentVO> list = service.findAllStudentAndClassroom( );return R.ok(list);}
测试结果
{"code": 20000,"msg": "成功","data": [{ // 学生对象"id": 1,"sname": "老王","age": 18,"sex": "男","score": 100.0,"birthday": "2023-08-30T16:00:00.000+00:00","cid": 0,"classroom": { // 教室对象"cid": 1,"cnum": "101","seatNum": 50}},{"id": 2,"sname": "老李","age": 19,"sex": "男","score": 200.0,"birthday": "2023-08-29T16:00:00.000+00:00","cid": 0,"classroom": {"cid": 1,"cnum": "101","seatNum": 50}},{"id": 3,"sname": "老杭","age": 20,"sex": "女","score": 50.0,"birthday": "2023-07-31T16:00:00.000+00:00","cid": 0,"classroom": {"cid": 2,"cnum": "102","seatNum": 70}}]
}
4.2 一对多(两表)
需求: 查询一个学生信息以及所学习的所有学科信息
注意: 虽然学生表和学科表是多对多,但是从单方向看,查询一个学生对应的多个学科的话就是1对多
4.2.1 数据库环境
学科表
create table subject(sub_id int primary key comment '学科id',sub_name varchar(255) comment '学科名'
);
因为学生表和学科表是多对多,所以需要设计中间关联表
CREATE TABLE `stu_sub` (`sid` int(11) NOT NULL COMMENT '学生id',`sub_id` int(11) NOT NULL COMMENT '科目id',PRIMARY KEY (`sid`,`sub_id`) -- 联合主键
);
再次强调需求: 查询一个学生信息以及所学习的所有学科信息
select stu.*,sub.* from stu left join `stu_sub` ss on stu.id = ss.sid left join `subject` sub on ss.sub_id = sub.sub_id where stu.id = 1
4.2.2 实体类
学科类
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Subject {private int subId;private String subName;
}
封装一对多扩展类StudentVO(就是之前那个)
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO extends Student{private Classroom classroom;// 封装多个学科信息private List<Subject> subjectList;
}
4.2.3 一对多查询
StudentMapper
// 根据id查询1个学生信息以及关联的所有学科信息StudentVO findStudentAndAllSubjectByStuid(int sid);
<!-- 一对多封装学生+所有学科信息 --><resultMap id="studentAndAllSubjectResultMap" type="studentVO"><id column="id" property="id"/><result column="sname" property="sname"/><result column="age" property="age"/><result column="sex" property="sex"/><result column="score" property="score"/><result column="birthday" property="birthday"/><!-- 1对多--><collection property="subjectList" ofType="Subject"><id column="sub_id" property="subId"/><result column="sub_name" property="subName"/></collection></resultMap><select id="findStudentAndAllSubjectByStuid" resultMap="studentAndAllSubjectResultMap">select stu.*,sub.* from stuleft join `stu_sub` ss on stu.id = ss.sidleft join `subject` sub on ss.sub_id = sub.sub_idwhere stu.id = #{sid}</select>
service
// 根据id查询1个学生信息以及关联的所有学科信息StudentVO findStudentAndAllSubjectByStuid(int sid);
@Overridepublic StudentVO findStudentAndAllSubjectByStuid(int sid) {return mapper.findStudentAndAllSubjectByStuid(sid );}
controller
@GetMapping("/subjects")public R findStudentAndAllSubjectByStuid(int sid){StudentVO studentVO = service.findStudentAndAllSubjectByStuid(sid);return R.ok(studentVO);}
测试结果
{"code": 20000,"msg": "成功","data": { // 1个学生对象"id": 1,"sname": "老王","age": 18,"sex": "男","score": 100.0,"birthday": "2023-08-30T16:00:00.000+00:00","cid": 0,"classroom": null,"subjectList": [ // 多个学科对象,是数组{"subId": 1,"subName": "语文"},{"subId": 2,"subName": "数学"},{"subId": 3,"subName": "英语"},{"subId": 4,"subName": "物理"}]}
}
4.3 一对多对一(三表)
需求: 查询一个学生信息以及所学习的所有学科信息,以及每个学科关联的老师信息
4.3.1 数据库环境
学生表,学科表已有,不再赘述
老师表
create table teacher(tid int primary key comment '老师id',tname varchar(255) comment '老师名字',tage int comment '老师年龄',education varchar(255) comment '学历'sub_id int comment '关联的学科id'
);
学生与学科一对多,学科与老师一对一
再次强调需求: 查询一个学生信息以及所学习的所有学科信息,以及每个学科关联的老师信息
select stu.*,sub.*,t.* from stu
left join `stu_sub` ss on stu.id = ss.sid
left join `subject` sub on ss.sub_id = sub.sub_id
left join teacher t on t.sub_id = sub.sub_id
where stu.id = 1
4.3.2 实体类
老师类
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {private int tid;private String tname;private int tage;private String education;private int subId;}
设置封装学科和老师的扩展类SubjectVO
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class SubjectVO extends Subject{private Teacher teacher;
}
封装一对多扩展类StudentVO,用于封装学生多个学科,对没错,还是上一章的一对多
但是!!! 有变化,因为这次不但要封装Subject学科信息,还需要封装学科对应的老师信息,所以此处要修改为SubjectVO,那么之前涉及到这一块的mapper中的代码要修改!!!
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO extends Student{private Classroom classroom;// 封装多个学科信息private List<SubjectVO> subjectList;
}
4.3.3 一对多对一查询
mapper
// 根据id查询1个学生信息以及关联的所有学科信息以及每个学科关联的老师信息StudentVO findStudentAndAllSubjectAndTeacherByStuid(int sid);
<!-- 一对多封装学生+所有学科信息一对一封装学科和老师信息--><resultMap id="studentAndAllSubjectAndTeacherResultMap" type="StudentVO"><id column="id" property="id"/><result column="sname" property="sname"/><result column="age" property="age"/><result column="sex" property="sex"/><result column="score" property="score"/><result column="birthday" property="birthday"/><!-- 1对多封装多个学科--><collection property="subjectList" ofType="SubjectVO"><id column="sub_id" property="subId"/><result column="sub_name" property="subName"/><!-- 每个学科对象还需要封装对应的老师信息 --><!-- 【特别注意】此处property里面的属性会显示报错,不用管是idea认为该属性没有set方法,无法赋值,其实是有的如下图--><association property="teacher" javaType="Teacher"><id column="tid" property="tid"/><result column="tname" property="tname"/><result column="tage" property="tage"/><result column="education" property="education"/><result column="sub_id" property="subId"/></association></collection></resultMap><select id="findStudentAndAllSubjectAndTeacherByStuid" resultMap="studentAndAllSubjectAndTeacherResultMap">select stu.*,sub.*,t.* from stuleft join `stu_sub` ss on stu.id = ss.sidleft join `subject` sub on ss.sub_id = sub.sub_idleft join teacher t on t.sub_id = sub.sub_idwhere stu.id = 1</select>
service
// 根据id查询1个学生信息以及关联的所有学科信息以及每个学科关联的老师信息StudentVO findStudentAndAllSubjectAndTeacherByStuid(int sid);
@Overridepublic StudentVO findStudentAndAllSubjectAndTeacherByStuid(int sid) {return mapper.findStudentAndAllSubjectAndTeacherByStuid(sid );}
controller
@GetMapping("/subjects/teacher")public R findStudentAndAllSubjectAndTeacherByStuid(int sid){StudentVO studentVO = service.findStudentAndAllSubjectAndTeacherByStuid(sid);return R.ok(studentVO);}
测试
{"code": 20000,"msg": "成功","data": { // 1个学生信息"id": 1,"sname": "老王","age": 18,"sex": "男","score": 100.0,"birthday": "2023-08-30T16:00:00.000+00:00","cid": 0,"classroom": null,"subjectList": [ // 多个学科信息{"subId": 1,"subName": "语文","teacher": { // 每个学科信息都关联对应一个老师信息"tid": 1,"tname": "老邱","tage": 18,"education": "本科","subId": 1}},{"subId": 2,"subName": "数学","teacher": {"tid": 2,"tname": "老邢","tage": 38,"education": "本科","subId": 2}},{"subId": 3,"subName": "英语","teacher": {"tid": 3,"tname": "老王","tage": 35,"education": "本科","subId": 3}},{"subId": 4,"subName": "物理","teacher": {"tid": 4,"tname": "老万","tage": 30,"education": "本科","subId": 4}}]}
}
五、业务层拆解多表联查
5.1 一对一
还是这个需求: 查询学生信息以及关联的教室信息
但是这次不进行多表联查,而是进行单表操作, 思路是什么?如何实现?底层逻辑是什么?
- 首先这个需求的目的是:得到学生信息以及关联的教室信息 ,
- 那么我只要想办法获得学生信息和教室信息就行了,
- 我可以先根据学生id查询出学生信息,
- 再通过学生信息中的教室id查出教室信息
- 然后再把学生信息和教室信息封装到一起就行
- 这样就变成了单表查询,一次查询学生表,一次查询教室表
控制层代码
/*** 学生 --> 教室一对一查询,V2* @return*/@GetMapping("/class/v2")public R findAllStudentAndClassroomV2(){List<StudentVO> list = service.findAllStudentAndClassroomV2( );return R.ok(list);}
业务层代码
@Autowired
private StudentMapper stuMapper;@Autowired
private ClassroomMapper classroomMapper;@Override
public List<StudentVO> findAllStudentAndClassroomV2() {// 创建最终的结果集合,存储学生信息及教室信息List<StudentVO> list = new ArrayList<>( );// 查询所有学生List<Student> studentList = stuMapper.findAll( );// 遍历得到所有学生studentList.forEach(student -> {// 再根据学生对象中的教室id查询对应的教室Classroom classroom = classroomMapper.findClassroomById(student.getCid( ));// 创建最终封装数据的对象StudentVO studentVO = new StudentVO( );// BeanUtils是spring框架提供的工具类,copyProperties方法用与拷贝对象属性// 此处是将student对象中的属性赋值到studentVO中BeanUtils.copyProperties(student, studentVO);// studentVO中存储教室信息studentVO.setClassroom(classroom);// 将每一个StudentVO添加到List集合list.add(studentVO);});return list;
}
持久层代码
StudentMapper.xml (之前就有该方法,直接使用的)
<select id="findAll" resultType="Student">select * from stu</select>
新创建ClassroomMapper.java和ClassroomMapper.xml映射文件
public interface ClassroomMapper {Classroom findClassroomById(int cid);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.ClassroomMapper"><select id="findClassroomById" resultType="Classroom">select * from classroom where cid = #{cid}</select>
</mapper>
5.2 一对多
需求: 查询一个学生信息以及所学习的所有学科信息
思路
- 先根据学生id查出一个学生信息
- 再根据学生id去中间关联表中查出该学生对应的所有学科id
- 再根据每一个学科id查出每一个学科对象
- 然后组装到一起
根据id查询学生 – StudentMapper.xml(之前就有的,直接调用)
<select id="findById" resultType="Student">select * from stu where id = #{id}</select>
根据学生id查询关联表的中对应的所有学科id,这个方法之前没有,需要在StudentMappe中添加接口方法和Mapper中映射语句
List<Integer> findAllSubjectId(int sid);
<select id="findAllSubjectId" resultType="java.lang.Integer">select sub_id from stu_sub where sid = #{sid}</select>
业务层
@Autowired
private StudentMapper mapper;@Autowired
private SubjectMapper subjectMapper;@Override
public StudentVO findStudentAndAllSubjectByStuidV2(int sid) {// 先根据学生id查出1个学生Student student = mapper.findById(sid);// 创建集合准备存储每个学科对象List<Subject> subjectList = new ArrayList<>( );// 再根据学生id查出关联表中所关联的所有学科idList<Integer> subIdList = mapper.findAllSubjectId(sid);// 遍历所有idsubIdList.forEach(subId -> {// 再根据学科id获得每一个学科对象Subject subject = subjectMapper.findSubjectById(subId);// 存储学科对象subjectList.add(subject);});// 封装数据到VOStudentVO studentVO = new StudentVO( );BeanUtils.copyProperties(student,studentVO);studentVO.setSubjectListV2(subjectList);return studentVO;
}
控制层
/*** 一对多演示,1个学生对应多个学科信息V2* @param sid* @return*/@GetMapping("/subjects/v2")public R findStudentAndAllSubjectByStuidV2(int sid){StudentVO studentVO = service.findStudentAndAllSubjectByStuidV2(sid);return R.ok(studentVO);}
BUG
1 数据库不存在
2 数据表不存在
3 数据长度不匹配
4 创建项目没有选择jdk版本和springboot版本导致版本过高
5 application.yml或者properties文件图标应该是绿色树叶如果不是就是不正常
6 application.yml文件中不能出现黄色阴影,出现说明不识别
7 修改yml中的内容(数据源信息) , 以及yml语法的换行,缩进,空格
8 执行mapper中的sql 报错提示某表不存在, 某某列未找到 , sql语法错误
9 忘了加注解@MapperScan @Service就不会创建对象,那么@Autowired时就会报错找不到该对象
10 代码都对,检查无误,但是不执行 --> 找target看代码是否编译,删除target重新编译代码运行
11 报错的状态码 404路径未找到 500后端代码有报错(空指针,sql语法) 400(数据解析出错,基本都是日期格式)
12 添加/更新时日期 后台需要加@DateTimeFormat
13 SpringBoot主类要放在最外层,扫描到所有包内的类