一、需求分析
核心需求:
- 前端提交「班级基本信息」和「需要绑定的教师 ID 列表」
- 后端完成:校验班级唯一性 → 新增班级 → 批量绑定教师与班级的关系
- 要求:所有数据库操作要么全部成功,要么全部失败(事务保障)
核心原则:教师和班级是多对多关系,无需重复插入教师数据,只需维护两者的关联关系表。
二、数据库设计
多对多关系的核心是「主表 + 关系表」设计,避免直接在主表中嵌套关联数据。
1. 核心表结构
(1)班级表(t_class)
sql
CREATE TABLE t_class (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '班级ID',
class_name VARCHAR(50) NOT NULL COMMENT '班级名称',
class_code VARCHAR(20) NOT NULL COMMENT '班级编号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_class_code (class_code) COMMENT '班级编号唯一'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级表';
(2)教师表(t_teacher)
sql
CREATE TABLE t_teacher (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '教师ID',
teacher_name VARCHAR(50) NOT NULL COMMENT '教师姓名',
teacher_no VARCHAR(20) NOT NULL COMMENT '教师工号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_teacher_no (teacher_no) COMMENT '教师工号唯一'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师表';
(3)班级 - 教师关系表(t_teacher_class)
sql
CREATE TABLE t_teacher_class (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
class_id BIGINT NOT NULL COMMENT '班级ID',
teacher_id BIGINT NOT NULL COMMENT '教师ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY uk_class_teacher (class_id, teacher_id) COMMENT '一个教师只能关联一个班级一次'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级教师关联表';
三、代码实现
1. 基础依赖(pom.xml)
核心依赖:SpringBoot Starter Web、MyBatis-Plus、MySQL 驱动、SpringBoot Starter Transaction
XML
<dependencies>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 事务支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
2. 实体类设计
(1)班级实体(ClassEntity)
java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_class")
public class ClassEntity {
/**
* 班级ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 班级名称
*/
private String className;
/**
* 班级编号
*/
private String classCode;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
(2)班级 - 教师关系实体(TeacherClassEntity)
java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_teacher_class")
public class TeacherClassEntity {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 班级ID
*/
private Long classId;
/**
* 教师ID
*/
private Long teacherId;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
3. DTO
java
import lombok.Data;
import java.util.List;
/**
* 新增班级并关联教师的入参DTO
*/
@Data
public class ClassTeacherDTO {
/**
* 班级基本信息
*/
private ClassEntity classInfo;
/**
* 要关联的教师ID列表
*/
private List<Long> teacherIds;
}
4. Mapper 层
(1)班级 Mapper(ClassMapper)
XML
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.ClassEntity;
@Repository
public interface ClassMapper extends BaseMapper<ClassEntity> {
/**
* 根据班级编号查询班级是否存在
*/
ClassEntity selectByClassCode(@Param("classCode") String classCode);
}
(2)班级 - 教师关系 Mapper(TeacherClassMapper)
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.TeacherClassEntity;
import java.util.List;
@Repository
public interface TeacherClassMapper extends BaseMapper<TeacherClassEntity> {
/**
* 批量插入班级-教师关系
*/
@Insert("<script>" +
"INSERT INTO t_teacher_class (class_id, teacher_id, create_time) " +
"VALUES " +
"<foreach collection='list' item='item' separator=','> " +
"(#{item.classId}, #{item.teacherId}, NOW()) " +
"</foreach>" +
"</script>")
int batchInsert(@Param("list") List<TeacherClassEntity> list);
}
5. Service 层(核心业务逻辑)
(1)Service 接口
java
import com.example.demo.dto.ClassTeacherDTO;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.ClassEntity;
public interface ClassService extends IService<ClassEntity> {
/**
* 新增班级并批量关联教师
*/
boolean addClassWithTeachers(ClassTeacherDTO dto);
}
(2)Service 实现类(核心)
java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.dto.ClassTeacherDTO;
import com.example.demo.entity.ClassEntity;
import com.example.demo.entity.TeacherClassEntity;
import com.example.demo.mapper.ClassMapper;
import com.example.demo.mapper.TeacherClassMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Service
public class ClassServiceImpl extends ServiceImpl<ClassMapper, ClassEntity> implements ClassService {
@Resource
private ClassMapper classMapper;
@Resource
private TeacherClassMapper teacherClassMapper;
/**
* 新增班级并批量关联教师(添加事务注解,保证多表操作原子性)
*/
@Override
@Transactional(rollbackFor = Exception.class) // 异常时回滚所有操作
public boolean addClassWithTeachers(ClassTeacherDTO dto) {
// 步骤1:校验班级是否已存在(根据班级编号)
ClassEntity existClass = classMapper.selectByClassCode(dto.getClassInfo().getClassCode());
if (existClass != null) {
throw new RuntimeException("班级编号已存在,无法新增");
}
// 步骤2:插入班级信息
boolean saveClass = save(dto.getClassInfo());
if (!saveClass) {
throw new RuntimeException("班级新增失败");
}
Long newClassId = dto.getClassInfo().getId(); // 获取新增班级的ID
// 步骤3:批量插入班级-教师关系(核心:只维护关系,不插入教师)
List<Long> teacherIds = dto.getTeacherIds();
if (teacherIds != null && !teacherIds.isEmpty()) {
List<TeacherClassEntity> relationList = new ArrayList<>();
for (Long teacherId : teacherIds) {
TeacherClassEntity relation = new TeacherClassEntity();
relation.setClassId(newClassId);
relation.setTeacherId(teacherId);
relationList.add(relation);
}
// 批量插入关系数据
int insertCount = teacherClassMapper.batchInsert(relationList);
if (insertCount != teacherIds.size()) {
throw new RuntimeException("教师与班级关联失败");
}
}
return true;
}
}
6. Controller 层
java
import com.example.demo.dto.ClassTeacherDTO;
import com.example.demo.service.ClassService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/class")
public class ClassController {
@Resource
private ClassService classService;
/**
* 新增班级并关联教师
*/
@PostMapping("/addWithTeachers")
public Map<String, Object> addClassWithTeachers(@RequestBody ClassTeacherDTO dto) {
Map<String, Object> result = new HashMap<>();
try {
boolean success = classService.addClassWithTeachers(dto);
result.put("code", 200);
result.put("msg", "操作成功");
result.put("data", success);
} catch (RuntimeException e) {
result.put("code", 500);
result.put("msg", e.getMessage());
result.put("data", false);
}
return result;
}
}
四、核心要点说明
1. 事务控制
- 使用
@Transactional(rollbackFor = Exception.class)注解,确保「新增班级」和「关联教师」两步操作要么全成、要么全败。 rollbackFor = Exception.class表示所有异常都触发回滚(默认只回滚运行时异常)。
2. 多对多关系处理
- 不直接操作教师表,仅维护「班级 - 教师」关系表,符合数据库设计规范。
- 关系表添加唯一索引
uk_class_teacher,避免重复关联。
3. 数据校验
- 新增前校验班级编号唯一性,避免重复数据。
- 批量插入关系数据后,校验插入条数与传入的教师 ID 数量一致,确保关联完整。
五、接口测试示例
请求参数(JSON)
{
"classInfo": {
"className": "三年级1班",
"classCode": "CLASS001"
},
"teacherIds": [1001, 1002, 1003]
}
响应结果(成功)
{
"code": 200,
"msg": "操作成功",
"data": true
}
响应结果(班级已存在)
{
"code": 500,
"msg": "班级编号已存在,无法新增",
"data": false
}
六、总结
- 多对多关系必须通过中间关系表维护,避免主表冗余。
- 多表操作一定要加事务控制,保证数据一致性。
- 前端入参设计为「主数据 + 关联 ID 列表」,符合实际业务场景。
- 核心流程:数据校验 → 新增主数据 → 批量插入关系数据,每一步都要做异常处理和结果校验。