引言
很多网站的后端都是基于Spring boot - mybatis进行开发的。
本文以此技术栈进行一个功能模块的开发
以最常见的注册功能为例
全部用最新的版本进行开发
工具
- 操作系统:Windows 10
- Java开发包:JDK 21
- 项目管理工具:Maven 3.6.3
- 项目开发工具:IntelliJ IDEA 2021.1.3 x64
- 数据库:Mysql
- 浏览器:Edge
- 服务器架构:Spring Boot 3.2.5 + MyBatis 3.0.3 + AJAX
部分坐标
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version>
</dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>3.0.3</version><scope>test</scope>
</dependency>
功能模块分析
本文开发的是注册模块,所以我们只会用一张表,其他模块思路与本文一致。
开发顺序
-
我们应该遵循:数据库 -> 持久层 -> 业务层 -> 表现层 -> 前端页面 这样的顺序进行开发。
-
功能上,我们应该先做基础功能,并遵循 增 -> 查 -> 删 -> 改的顺序来开发。
创建数据库
制作注册功能,本质是将用户的信息存放到数据库中。
我们建立一个数据库名为db
CREATE DATABASE db character SET utf8;
此时我们就需要考虑用户表中应该有哪些字段。
- 首先应该有一个id并且这个id是唯一且不能为空,也就是主键,并且我们也可以为这个字段设置为自增字段。
- 主要信息。在我们常用的软件上不难看出厂商保留了我们的哪些信息。
如:用户名:username,密码:password。 - 上面这两个是必不可少的,根据我们实际的项目,也可以保存其他的信息
如:手机号:phone,电子邮箱:email,性别:sex,头像:avatar等。
此处我们就选用:自增主键id,用户名username,密码password,性别sex,手机号phone。
后续可能会有一些补充
建表语句:
CREATE TABLE `user` (`id` bigint NOT NULL,`username` varchar(32) NOT NULL,`password` varchar(32) NOT NULL,`sex` int,`phone` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
建表后在配置文件中连接数据库
此处使用yml类型的配置文件
spring:datasource:url: jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456
项目开发
此处开始我们代码的正式开发
创建实体类
我们在给数据库添加数据时,不能将字段全部用变量传入,这样很麻烦,也不方便后续的一些操作,所以我们会创建表对应的实体类对象
User
java">package com.angelday.test.entity;public class User {private Integer id;private String username;private String password;private Integer age;private String phone;/*后面是构造方法,Setter、Getter、toString方法由于可以使用IDEA自动生成,此处不再赘述*/
}
持久层
前面说过,注册的本质就是向数据库中添加一行数据
所以它的SQL语句是:
INSERT INTO db (username, password, age, phone) VALUES (值列表);
本项目使用的是mybatis进行开发
我们在根目录下创建一个包 mapper,并且在这个包下创建一个接口
java">package com.angelday.test.mapper;import com.angelday.test.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {/*** 添加用户* @param user 添加来的user对象* @return 受影响的行数*/Integer insertUser(User user);
}
并在resources包下创建一个包mapper用作UserMapper接口的映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.angelday.test.mapper.UserMapper"><!--id为对应接口的方法名,useGennerateadKeys开启自增,keyProperty指明自增字段--><insert id="insertUser" useGeneratedKeys="true" keyProperty="uid">INSERT INTO t_user(username, password, phone, sex)VALUES (#{username}, #{password}, #{phone}, #{sex})</insert>
</mapper>
只有这个配置文件SpringBoot并不能识别到它,所以我们应该在配置文件中指定扫描路径
mybatis:mapper-locations: classpath:mapper/*.xml
单元测试
昨晚持久层之后我们要利用Springboot提供的@SpringBootTest进行测试,测试一定不要省略,这样可以减小问题的范围。
java">package com.angelday.store;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class StoreApplicationTests {@Autowiredprivate UserMapper userMapper@Testvoid userMapperTest(){User user = new User();user.setUsername("Zhangsan");user.setPassword("123456");user.setSex(1);//一般都是规定1是男0是女user.setPhone("12332112321");//该方法返回int类型的整数表示受影响的行数int rows = insertUser(User user);}
}
执行这个方法之后去数据库中查看信息,如果信息没有任何问题,即可执行下一步。
业务层
包结构一般为根目录下建一个service包
service包中有impl和ex两个包分别装Service的实现类与业务层中可能出现的异常。
规划异常
我们的用户名并没有设置为唯一,所以没有常见的用户名重复异常,我们就建立一个最大的异常作为所有Service层中会发生的异常
java">package com.angelday.test.service.ex;/*** 业务异常基类,所有的业务层异常都继承这个异常*/
public class ServiceException extends RuntimeException{public ServiceException() {super();}public ServiceException(String message) {super(message);}public ServiceException(String message, Throwable cause) {super(message, cause);}public ServiceException(Throwable cause) {super(cause);}protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
开发Service接口
我们在service包下建立一个UserService接口
java">package com.angelday.test.service;import com.angelday.test.entity.User;
import org.springframework.stereotype.Service;/*** 用户模块*/
@Service
public interface IUserService {/*** 用户注册* @param user*/void reg(User user);
}
实现类
在现实项目中一般我们的表中会有创建人创建时间修改人修改时间等字段用来记录日志,
而这些日志信息前端不会主动传来,所以我们会在业务层中补充这些字段
java">package com.angelday.store.service.impl;import ...@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper userMapper;/*** 用户注册* @param user 表单内容*/@Overridepublic void reg(User user){//将user中的信息完善如:创建人创建时间等user.setCreatedUser(user.getUsername());user.setModifiedUser(user.getUsername());Date date = new Date();user.setCreatedTime(date);user.setModifiedTime(date);//密码一般不会用明文的形式存储到数据库中,此处就用常见的md5加密,对密码加密//md5加密String password = user.getPassword();password = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));user.setPassword(password);//执行成功返回1Integer rows = userMapper.insertUser(user);if (rows != 1){throw new ServiceException("产生未知错误");}}
}
单元测试
记住,一定要测试!一定要测试!一定要测试!!!
java">@SpringBootTest
class StoreApplicationTests {@Autowiredprivate UserService userService;@Testvoid userServiceTest(){User user = new User();user.setUsername("Lisi");user.setPassword("654321");user.setSex(1);user.setPhone("12332112321");//该方法返回int类型的整数表示受影响的行数int rows = reg(User user);}
}
执行后检查数据库的数据是否正确
表现层
这里我们需要确定前端具体要访问什么路径,一般都是由项目经理提前订好了,此处我们就自定义一个user/reg
路径,前端传来User对象,请求方式为POST
请求参数:/user/reg
请求参数:User user
请求类型:POST
响应结果:Result< Void>
约定响应结果
后端所有的响应结果都要封装到这个类中,它一般包含以下三个变量
1.状态码 2. 消息 3. 数据
在com.angelday.test.util
下建立Result
类
java">package com.angelday.test.util;/*** 统一返回结果类*/
public class JsonResult<E> {private Integer state;private String message;private E data;//构造方法 & Getter和Setter方法
}
Controller
根据与前端的约定,我们创建UserController
类
java">@RestController
@RequestMapping("users")
public class UserController{@Autowiredprivate UserService userService;/*** 用户注册* @param user 表单内的注册信息* @return 返回成功状态码*/@RequestMapping("reg")public Result<Void> reg(User user){userService.reg(user);return new Result<>(200);}
}
优化
写到这里发现:
- 还有业务层的异常没有处理
- 成功的状态码不应该直接我们自己写,而是由前后端约定好的值,声明为
常量
。
集中处理异常,声明状态码与消息的常量
创建一个Controler的基类,所有的表现层都继承这个类,在这个类里我们进行集中处理异常,并且封装一些状态码和信息做为常量。
使用@ExceptionHandler可以集中处理异常
java">/*** 控制类的基类,负责管理通用的操作与数据*/
public class BaseController {public static final int OK = 200;/*** 集中处理异常* @param e 异常对象* @return 返回的json格式数据*/@ExceptionHandler({ServiceException.class,FileUploadException.class})public Result<Void> handlerException(Throwable e){Result<Void> result = new Result(e);if (e instanceof ServiceException){result.setState(1000);result.setMessage("在插入数据时发生未知错误");}}
}
最后我们让UserController继承BaseController
测试
此处可以使用PostMan或者直接用浏览器访问该路径
localhost:8080/users/reg?username=zhangsan&password=123456
如果数据成功添加到数据库中,本模块开发完成
前端页面
页面的组件此处不详细讲解,只是向大家说明,表单的id
为form-register
,提交表单数据的按钮id
为button-register
。
大家只需要根据自己的表单id与按钮id修改名称即可。
AJAX请求
一般在body标签的结尾前添加script标签,用来添加函数
<body>
..........
<!--页面的组件--><script>javascript">//首先监听按钮是否被点击//此处使用id选择器,并且为其添加click事件$("#button-register").click(function (){//按钮点击后发送AJAX请求$.ajax({url: "/users/reg",type: "POST",// 使用serialize()方法会自动将表单json数据转化为username=Xxx的形式data: $("#form-register").serialize(),dataType: "JSON",//成功的回调函数success: function (json) {if (json.state == 200){alert("注册成功");} else {alert("注册失败");}},//失败的回调函数error: function (xhr) {alert("注册时产生未知错误,请稍后重新尝试!"+ xhr.status);} });});</script>
</body>
单元测试
此处我们可以运行TestApplication主程序开启服务,并在页面填入数据进行提交,如果返回警告框:注册成功
,并且数据库中的数据没有异常,整个模块功能的开发阶段,完成!
结语
如果有其他的问题欢迎私信我,后续有其他的优化:如:使用Redis、使用会话跟踪技术、拦截器/过滤器等,我会再创一个分栏。