【案例篇】 实现简单SSM工程-后端

[复制链接]
发表于 2025-7-31 19:59:27 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
简介
        本篇文章将带你从0到1的实现一个SSM项目,通过此案例可以让你在项目中对SpringBoot的使用有一个更加具体的认识,渴望这个简单的案例能够帮到你。文章内容若存在错误或需改进的地方,欢迎各人指正!若对操作有任何疑问欢迎留言!非常感谢!
目次
一、创建SpringBoot工程
二、项目初始化
1、初始化MySQL的库和表
2、初始化实体类
三、实现数据访问层
1、实现
2、测试
3、拓展
(1)开启MyBatis-Plus的日记
(2)实现分页查询
(3)条件查询
(4)处理异常IllegalArgumentException: argument type mismatch
四、实现业务逻辑层
1、实现
2、测试
3、拓展
(1)业务逻辑层整合MyBatis-Plus
(2)分页校验
五、实现表现层
1、实现
2、测试
3、拓展
(1)表现层消息划一性处理
六、统一异常处理类
1、场景
2、解决办法


一、创建SpringBoot工程

        起首,创建一个SpringBoot工程,然后在pom里面导入web、MyBatis-Plus、MySQL驱动、Druid的坐标依靠,若这里如果有不太清楚怎么使用的可以看【第四篇】 SpringBoot整合第三方技能文章的内容。


        然后配置文件使用.yaml文件,并且还在文件中配置了项目的启动端口号为8888,防止8080端口出现冲突,别的设置了数据库的连接信息配置。


二、项目初始化

1、初始化MySQL的库和表

  1. create schema demo;
  2. create table demo.student
  3. (
  4.     id      int auto_increment comment '主键'
  5.         primary key,
  6.     name    varchar(128) null comment '名字',
  7.     age     int          null comment '年龄',
  8.     address varchar(128) null comment '家庭住址'
  9. )
  10.     comment '学生表';
  11. INSERT INTO demo.student (id, name, age, address) VALUES (1, '张三', 18, '北京');
  12. INSERT INTO demo.student (id, name, age, address) VALUES (2, '李四', 19, '南京');
  13. INSERT INTO demo.student (id, name, age, address) VALUES (3, '王五', 20, '苏州');
  14. INSERT INTO demo.student (id, name, age, address) VALUES (4, '赵六', 21, '杭州');
  15. INSERT INTO demo.student (id, name, age, address) VALUES (5, '马七', 22, '成都');
  16. INSERT INTO demo.student (id, name, age, address) VALUES (6, '刘八', 23, '重庆');
  17. INSERT INTO demo.student (id, name, age, address) VALUES (7, '蔡九', 24, '上海');
  18. INSERT INTO demo.student (id, name, age, address) VALUES (8, '康十', 25, '深圳');
  19. INSERT INTO demo.student (id, name, age, address) VALUES (9, '杨十一', 26, '广州');
  20. INSERT INTO demo.student (id, name, age, address) VALUES (10, '杨十二', 27, '珠海');
  21. INSERT INTO demo.student (id, name, age, address) VALUES (11, '杨十三', 28, '太原');
  22. INSERT INTO demo.student (id, name, age, address) VALUES (12, '杨十四', 29, '西安');
  23. INSERT INTO demo.student (id, name, age, address) VALUES (13, '杨十五', 30, '济南');
复制代码
2、初始化实体类

        在引导类所在包下创建domain.pojo包,然后在pojo包里创建Student实体类,并给出get/set、toString、equals/hashCode方法。这里使用的Lombok的@Data实实际体类方法的简化。下面会分析Lombok的用法。


Lombok的用法
(1)导入对应的依靠坐标


(2)使用
Lombok提供的主要注解:


  • @Data:为当前实体类在编译期设置对应的get/set、toString、equals/hashCode方法。
  • @AllArgsConstructor:为当前实体类在编译期设置对应的全参构造方法
  • @NoArgsConstructor:为当前实体类在编译期设置对应的空参构造方法
三、实现数据访问层

1、实现

        在引导类所在包下创建一个mapper包,然后在mapper包中创建一个StudentMapper的接口,使用@Mapper注解标注该类,并且让该接口继承BaserMapper接口指定泛型为与数据库表映射的实体类。这样数据访问层的基础接口就已经写好了,因为BaserMapper接口中已经提供了各种用于操作数据库的接口,所以在使用的使用直接注入StudentMapper接口,然后调用即可。


2、测试

        在test包下同样的目次下创建一个mapper包,然后在mapper包中创建一个StudentMapper的测试类,然后再类中注入StudentMapper的对象,末了编写测试方法即可。
  1. @SpringBootTest
  2. public class StudentMapperTest {
  3.     @Autowired
  4.     private StudentMapper studentMapper;
  5.     /**
  6.      * 测试查询单个数据
  7.      */
  8.      /**
  9.      * 测试查询单个数据
  10.      */
  11.     @Test
  12.     void testGetById() {
  13.         System.out.println(studentMapper.selectById(1));
  14.     }
  15.     /**
  16.      * 测试新增数据
  17.      */
  18.     @Test
  19.     void testSave() {
  20.         Student student = new Student();
  21.         student.setName("demo1");
  22.         student.setAge(18);
  23.         student.setAddress("青海");
  24.         studentMapper.insert(student);
  25.     }
  26.     /**
  27.      * 测试修改数据
  28.      */
  29.     @Test
  30.     void testUpdate() {
  31.         Student student = new Student();
  32.         student.setName("demo1");
  33.         student.setAge(18);
  34.         student.setAddress("青海");
  35.         studentMapper.updateById(student);
  36.     }
  37.     /**
  38.      * 测试删除数据
  39.      */
  40.     @Test
  41.     void testDelete() {
  42.         studentMapper.deleteById(1);
  43.     }
  44.     /**
  45.      * 测试查询所有数据
  46.      */
  47.     @Test
  48.     void testGetaAll() {
  49.         System.out.println(studentMapper.selectList(null));
  50.     }
  51. }
复制代码
3、拓展

(1)开启MyBatis-Plus的日记

        只需要在application.yaml配置文件中配置MyBatis-Plus开启日记打印的配置即可。
  1. mybatis-plus:
  2.   configuration:
  3.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码


(2)实现分页查询

        MyBatis-Plus中是提供了分页查询的功能的,但是需要手动显示的配置。只需要将MyBatis-Plus的拦截器的对象注册为IOC的Bean,并在注册之前在这个拦截器的Bean里面添加想要的配置即可,比如添加分页功能。
        步骤:在启动类所在包下创建config包,用于存放配置类,在config包下创建MyBatis-Plus的配置类,并将MyBatis-Plus的拦截器的对象注册为IOC的Bean。
  1. /**
  2. * MyBatis-Plus的配置类
  3. */
  4. @Configuration
  5. public class MPConfig {
  6.     /**
  7.      * 将MyBatis-Plus的拦截器对象注册为IOC的Bean
  8.      * @return
  9.      */
  10.     @Bean
  11.     public MybatisPlusInterceptor mybatisPlusInterceptor() {
  12.         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  13.         // 将MyBatis-Plus的分页插件对象加入拦截器对象中
  14.         interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
  15.         return interceptor;
  16.     }
  17. }
复制代码


        测试分页查询,MyBatis-Plus提供的有分页查询方法selectPage,此中接收Page对象和条件构造器,条件构造器会在下面分析。创建一个IPage对象,然后传参给查询第几页和一页展示多少数据,然后调用分页查询方法,方法的返回值还是这个page对象,然后page对象提供了一些方法,具体方法什么含义在下面截图中有分析。
  1.     @Test
  2.     void testPage() {
  3.         IPage page = new Page(1,5);
  4.         studentMapper.selectPage(page,null);
  5.     }
复制代码




(3)条件查询

        使用MyBatis-Plus提供的QueryWrapper和LambdaQueryWrapper,两个选一个就行,区别在于QueryWrapper的条件构造中是直接使用的字符串参数,LambdaQueryWrapper是在字符串的基础上使用了方法引用,QueryWrapper是有可能因为人为缘故原由写错的,而LambdaQueryWrapper则避免了这个题目,所以今后推荐使用LambdaQueryWrapper。别的在条件表达式中还支持添加判断表达式,若为true则拼接该条件,反之则不拼接,这样可以对接收到的参数进行一个非空判断,防止接收空参报错。


(4)处理异常IllegalArgumentException: argument type mismatch

        产生这个题目的缘故原由是因为MyBatis-Plus自动天生id值的战略是采用雪花算法,然而雪花算法天生的值是超出了int的范围,于是就产生了这个异常,可以在配置文件中设置id的战略为数据库自增


四、实现业务逻辑层

1、实现

        起首,在启动类所在包下创建一个service包,然后在service包中创建IStudentService接口,并在接口中界说所要使用方法,这里简单列举了几个。

        然后,在service包中创建一个impl包,在里面创建实现类StudentServiceImpl并实现IStudentService接口,重写所有方法。在该类上标注@Service注解,并在类里面注入数据访问层StudentMapper的对象。


        末了,编写实现类StudentServiceImpl中方法的逻辑,并使用数据访问层对象操作数据库。
  1. @Service
  2. public class StudentServiceImpl implements IStudentService {
  3.     @Autowired
  4.     private StudentMapper studentMapper;
  5.     @Override
  6.     public Boolean save(Student student) {
  7.         return studentMapper.insert(student) > 0;
  8.     }
  9.     @Override
  10.     public Boolean update(Student student) {
  11.         return studentMapper.updateById(student) > 0;
  12.     }
  13.     @Override
  14.     public Boolean delete(Integer id) {
  15.         return studentMapper.deleteById(id) > 0;
  16.     }
  17.     @Override
  18.     public Student getById(Integer id) {
  19.         return studentMapper.selectById(id);
  20.     }
  21.     @Override
  22.     public List<Student> getAll() {
  23.         return studentMapper.selectList(null);
  24.     }
  25.     @Override
  26.     public IPage<Student> getPage(int pageNum, int pageSize) {
  27.         IPage<Student> page = new Page<>(pageNum, pageSize);
  28.         studentMapper.selectPage(page, null);
  29.         return page;
  30.     }
  31. }
复制代码
2、测试

        在test包下同样的目次下创建一个servicer包,然后在servicer包中创建一个IStudentServicer的测试类,然后再类中注入IStudentServicer的对象,末了编写测试方法即可。
  1. @SpringBootTest
  2. public class IStudentServiceTest {
  3.     @Autowired
  4.     private IStudentService studentService;
  5.     /**
  6.      * 测试查询单个数据
  7.      */
  8.     @Test
  9.     void testGetById() {
  10.         System.out.println(studentService.getById(1));
  11.     }
  12.     /**
  13.      * 测试新增数据
  14.      */
  15.     @Test
  16.     void testSave() {
  17.         Student student = new Student();
  18.         student.setName("demo1");
  19.         student.setAge(18);
  20.         student.setAddress("青海");
  21.         studentService.save(student);
  22.     }
  23.     /**
  24.      * 测试修改数据
  25.      */
  26.     @Test
  27.     void testUpdate() {
  28.         Student student = new Student();
  29.         student.setId(14);
  30.         student.setName("demo1");
  31.         student.setAge(18);
  32.         student.setAddress("青海");
  33.         studentService.update(student);
  34.     }
  35.     /**
  36.      * 测试删除数据
  37.      */
  38.     @Test
  39.     void testDelete() {
  40.         studentService.delete(14);
  41.     }
  42.     /**
  43.      * 测试查询所有数据
  44.      */
  45.     @Test
  46.     void testGetaAll() {
  47.         System.out.println(studentService.getAll());
  48.     }
  49.     /**
  50.      * 测试分页查询
  51.      */
  52.     @Test
  53.     void testPage() {
  54.         IPage<Student> page = studentService.getPage(1, 5);
  55.         System.out.println(page.getPages());
  56.         System.out.println(page.getTotal());
  57.         System.out.println(page.getRecords());
  58.         System.out.println(page.getCurrent());
  59.         System.out.println(page.getSize());
  60.     }
  61. }
复制代码
3、拓展

(1)业务逻辑层整合MyBatis-Plus

        MyBatis-Plus提供了业务逻辑层通用接口(IService<T>)和业务逻辑层通用实现类(ServiceImpl<M,T>),基于这二者能够直接在业务层对数据库进行操作。注意:如果通用类提供的方法不足以满足业务逻辑层中的需求,那么可以在通用类的基础上做功能重载大概功能追加,千万不能覆盖原始方法!
步骤:


  • 业务层接口继承通用接口(IService<T>),并指定实体类泛型
  • 业务实现层   

    • 继承通用实现类(ServiceImpl<M,T>),第一个泛型数据访问层,第二个是实体类。
    • 实现业务层接口。
    • 使用@Service注解声明





        颠末上面业务逻辑层整合MyBatis-Plus,下面是测试示例:
  1. @SpringBootTest
  2. public class IStudentService2Test {
  3.     @Autowired
  4.     private IStudentService2 studentService2;
  5.     /**
  6.      * 测试查询单个数据
  7.      */
  8.     @Test
  9.     void testGetById() {
  10.         System.out.println(studentService2.getById(1));
  11.     }
  12.     /**
  13.      * 测试新增数据
  14.      */
  15.     @Test
  16.     void testSave() {
  17.         Student student = new Student();
  18.         student.setName("demo1");
  19.         student.setAge(18);
  20.         student.setAddress("青海");
  21.         studentService2.save(student);
  22.     }
  23.     /**
  24.      * 测试修改数据
  25.      */
  26.     @Test
  27.     void testUpdate() {
  28.         Student student = new Student();
  29.         student.setId(14);
  30.         student.setName("demo1");
  31.         student.setAge(18);
  32.         student.setAddress("青海");
  33.         studentService2.updateById(student);
  34.     }
  35.     /**
  36.      * 测试删除数据
  37.      */
  38.     @Test
  39.     void testDelete() {
  40.         studentService2.removeById(14);
  41.     }
  42.     /**
  43.      * 测试查询所有数据
  44.      */
  45.     @Test
  46.     void testGetaAll() {
  47.         System.out.println(studentService2.list());
  48.     }
  49.     /**
  50.      * 测试分页查询
  51.      */
  52.     @Test
  53.     void testPage() {
  54.         IPage<Student> page = new Page<>(1, 5);
  55.         studentService2.page(page);
  56.         System.out.println(page.getPages());
  57.         System.out.println(page.getTotal());
  58.         System.out.println(page.getRecords());
  59.         System.out.println(page.getCurrent());
  60.         System.out.println(page.getSize());
  61.     }
  62. }
复制代码
(2)分页校验

        当前的分页是存在一点点题目的,因为没有对分页参数进行校验,当页码和页展示数对不上的时候就会发生异常。比方,在末了一页只有一条数据,如果删除之后,还是在当前页,但是当前页已经没有数据了。后端校验是有弊端的,有并发风险,如果正在重查,然后别人又删除了,就会有题目,所以后端校验属于补救步伐。
处理办法


  • 前端在做删除的操作之后,立即返回第一页(推荐)
  • 后端做校验   

    • 若页码大概页展示数是非法值,那么就给默认值;
    • 若查询页与返回页对不上,那么再重查一次;



  1. @Override
  2.     public IPage<Student> getPage(int pageNum, int pageSize) {
  3.         // 判断参数是否非法
  4.         pageNum = pageNum <= 0 ? 1 : pageNum;
  5.         pageSize = pageSize <= 0 ? 10 : pageSize;
  6.         // 若查询页码与总页数不一致,则重查
  7.         IPage<Student> page = new Page<>(pageNum, pageSize);
  8.         studentMapper.selectPage(page, null);
  9.         if (pageNum > page.getPages()){
  10.             page = studentMapper.selectPage(Page.of(page.getPages(), pageSize), null);
  11.         }
  12.         return page;
  13.     }
复制代码
五、实现表现层

        表现层基于Restful开发,并且使用APIfox进行接口测试
1、实现

        起首,在启动类所在包下创建一个controller包,然后在controller包中创建StudentController接口,然后使用@RestController注解声明为表现层类、@RequestMapping("/students")声明哀求路径前缀并在接口中界说所要使用方法,这里简单列举了几个。


  1. @RestController
  2. @RequestMapping("/students")
  3. public class StudentCroller {
  4.     @Autowired
  5.     private IStudentService studentService;
  6.     @GetMapping
  7.     public List<Student> getAll() {
  8.         return studentService.getAll();
  9.     }
  10.     @GetMapping("/{id}")
  11.     public Student getById(@PathVariable Integer id) {
  12.         return studentService.getById(id);
  13.     }
  14.     @PostMapping
  15.     public Boolean save(@RequestBody Student student) {
  16.         return studentService.save(student);
  17.     }
  18.     @PutMapping
  19.     public Boolean update(@RequestBody Student student) {
  20.         return studentService.update(student);
  21.     }
  22.     @DeleteMapping
  23.     public Boolean delete(Integer id) {
  24.         return studentService.delete(id);
  25.     }
  26. }
复制代码
2、测试

        这里使用的APIfox构造的哀求,也可以使用Postman。





3、拓展

(1)表现层消息划一性处理

        设计表现层返回效果的模子类,用于后端与前端进行数据格式统一,也称为前后端数据协议。在启动类所在包下创建一个utilsr包,然后在utilsr包中创建R类,并给三个属性code、msg、data,code等于200期间表哀求成功500代表失败,别的相应码可以看约定。msg是相应信息,data是相应数据。也可以界说一些静态方法,方便调用。


  1. @Data
  2. public class R {
  3.     private int code;
  4.     private String msg;
  5.     private Object data;
  6.     public static R ok(Object data) {
  7.         R r = new R();
  8.         r.setCode(200);
  9.         r.setMsg("success");
  10.         r.setData(data);
  11.         return r;
  12.     }
  13.     public static R ok() {
  14.         R r = new R();
  15.         r.setCode(200);
  16.         r.setMsg("success");
  17.         r.setData(null);
  18.         return r;
  19.     }
  20.     public static R fail(String msg) {
  21.         R r = new R();
  22.         r.setCode(500);
  23.         r.setMsg(msg);
  24.         r.setData(null);
  25.         return r;
  26.     }
  27. }
复制代码
        然后在表现层就需要统一返回值为R,那么前端就可以根据code的值来进行不同操作,也知道数据一定在daata里面,这样就形成了统一。
  1. @RestController
  2. @RequestMapping("/unified/students")
  3. public class StudentCroller2 {
  4.     @Autowired
  5.     private IStudentService studentService;
  6.     @GetMapping
  7.     public R getAll() {
  8.         return R.ok(studentService.getAll());
  9.     }
  10.     @GetMapping("/{id}")
  11.     public R getById(@PathVariable Integer id) {
  12.         return R.ok(studentService.getById(id));
  13.     }
  14.     @GetMapping("/{pageNum}/{pageSize}")
  15.     public R page(@PathVariable Integer pageNum, @PathVariable Integer pageSize) {
  16.         return R.ok(studentService.getPage(pageNum, pageSize));
  17.     }
  18.     @PostMapping
  19.     public R save(@RequestBody Student student) {
  20.         return studentService.save(student) ? R.ok() : R.fail("添加失败");
  21.     }
  22.     @PutMapping
  23.     public R update(@RequestBody Student student) {
  24.         return studentService.update(student) ? R.ok() : R.fail("修改失败");
  25.     }
  26.     @DeleteMapping
  27.     public R delete(Integer id) {
  28.         return studentService.delete(id) ? R.ok() : R.fail("删除失败");
  29.     }
  30. }
复制代码


六、统一异常处理类

1、场景

                开发中难免会遇到错误,那么当程序中出现错误的时候就会抛异常,如果按照上面的方式来写项目的话,一旦程序中发生异常,那么就会直接抛给前端,导致无法正确剖析数据,下面是一个演示。




2、解决办法

        尽管代码出现了异常,那后端也应该将异常统一做处理,做成前端能够统一处理的格式。SpingBoot也提供的有统一异常类的实现,先在utils中创建一个异常处理类,并使用@RestControllerAdvice注解声明,然后可以在类中写异常处理的方法,并通过@ExceptionHandler注解声明要拦截什么样的异常,可以针对不同的异常做不同的处理,下面示例是直接将所有的异常捕捉并统一处理。


  1. @RestControllerAdvice
  2. public class UnifiedExceptionHandle {
  3.     @ExceptionHandler(Exception.class)
  4.     public R handleException(Exception e){
  5.         e.printStackTrace();
  6.         return R.fail("服务器故障!");
  7.     }
  8. }
复制代码


回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表