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 mapper
PUBLIC "-//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;
@Override
public 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();
// 实现类文件
@Override
public 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 stu
select * from stu where age = 1
select * 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);
// 实现类文件
@Override
public 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);
// 实现类文件
@Override
public 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);
// 实现类文件
@Override
public 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);
// 实现类文件
@Override
public 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 c
on s.cid = c.cid
</select>
service
// 查询所有学生信息以及关联的班级信息
List<StudentVO> findAllStudentAndClassroom();
@Override
public 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 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 = #{sid}
</select>
service
// 根据id查询1个学生信息以及关联的所有学科信息
StudentVO findStudentAndAllSubjectByStuid(int sid);
@Override
public 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 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
</select>
service
// 根据id查询1个学生信息以及关联的所有学科信息以及每个学科关联的老师信息
StudentVO findStudentAndAllSubjectAndTeacherByStuid(int sid);
@Override
public 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 mapper
PUBLIC "-//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查出关联表中所关联的所有学科id
List<Integer> subIdList = mapper.findAllSubjectId(sid);
// 遍历所有id
subIdList.forEach(subId -> {
// 再根据学科id获得每一个学科对象
Subject subject = subjectMapper.findSubjectById(subId);
// 存储学科对象
subjectList.add(subject);
});
// 封装数据到VO
StudentVO 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主类要放在最外层,扫描到所有包内的类