实现一个数据库和前端的交互
三层结构
- 恒久层开发:依据前端页面的设置规划相关的sql语句,以及进行配置
- 业务层开发:焦点功能控制、业务操纵以及异常的处理惩罚
- 控制层开发:前后端连接,接受请求,处理惩罚响应
完备的数据响应流程如下:
- 前端发起请求: 前端通过浏览器或其他客户端发起HTTP请求,请求后端提供的某个API接口或页面资源。请求可以包括HTTP方法(GET、POST、PUT、DELETE等)、URL、请求头和请求体等信息。
- 请求到达服务器: 请求通过网络到达服务器。服务器会根据请求的URL和HTTP方法来决定路由到哪个Controller处理惩罚。
- DispatcherServlet处理惩罚: Spring Boot中利用了DispatcherServlet作为前端控制器,它会吸收到所有的HTTP请求,然后将请求分发给合适的Controller。
- Controller处理惩罚: 根据请求的URL和HTTP方法,DispatcherServlet将请求转发给对应的Controller处理惩罚。Controller是Spring MVC中的组件,负责处理惩罚详细的请求并生成响应。
- 业务逻辑处理惩罚: 在Controller中,可以进行业务逻辑的处理惩罚,包括调用Service层的方法、读取数据库、盘算等。
- 调用Service层: 通常,业务逻辑的处分析涉及调用Service层的方法。Service层负责业务逻辑的组织和封装。
- 访问数据库或其他资源: 在Service层,大概需要访问数据库或其他外部资源来获取数据或执行一些操纵。
- 返回响应数据: 处理惩罚完业务逻辑后,Controller会生成响应数据,通常是一个视图模板、JSON数据或其他格式的数据。
- 响应返回到前端: Controller生成的响应数据会通过HTTP协议返回给前端,响应包括HTTP状态码、响应头和响应体等信息。
接下来就进行一个完备的后端的三层框架的应用。
底子配置
根据以下过程初始化一个springBoot框架,这个实在也可以用Maven进行创建,但是Spring Initializr会帮助我们创建好一些测试文件和依靠。
下图是一样寻常我们在创建项目时都会选择通用一点的JDK8版本,我用的spring boot版本是2.4左右。如果版本都太新的话,大概会发生很多莫名其妙的不兼容题目。但是默认的服务器好像已经没有8了,我们按照以下步骤来修改。
- application.properties
- spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
- spring.datasource.username=root
- spring.datasource.password=12345
复制代码 配置到这一步的话,再共同我[上一篇的配置数据库的过程](小白手把手教学用spring框架实现mybatis和mysql以及工作原理 - ivanlee717 - 博客园),我们就完成了初步的配置。上述文件放在src/main/resources/application.properties文件里面,然后我们进行单位测试。
单位测试
- D:.
- ├─.idea
- │ ├─dataSources
- │ ├─inspectionProfiles
- │ └─libraries
- ├─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─ivan
- │ │ │ └─store
- │ │ └─resources
- │ │ ├─mapper
- │ └─test
- │ └─java
- │ └─com
- │ └─ivan
- │ └─store
- │ ├─mapper
- │ └─service
复制代码 这是初始的一个目次结构,test目次里有完全一样的结构,就是为了方便我们在完成任何一项功能的测试。以下是我们的测试代码。- # com/ivan/store/StoreApplicationTests.java
- package com.ivan.store;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import javax.sql.DataSource;
- import java.sql.SQLDataException;
- import java.sql.SQLException;
- @SpringBootTest
- class StoreApplicationTests {
- @Autowired //自动装配
- private DataSource dataSource;
- @Test
- void contextLoads() {
- }
- @Test # 测试连接数据库
- void getConnection() throws SQLException {
- System.out.println(dataSource.getConnection());
- }
- }
复制代码 @Test就是表明我们只运行该函数来测试我们的数据库连通性。
- /**
- * 数据库连接池
- * 1. DBCP
- * 2. Spring MVC: C3P0
- * 3. Hikari 默认内部连接池
- * HikariProxyConnection@2127123542 默认内部连接池
- * wrapping com.mysql.cj.jdbc.ConnectionImpl@748a654a
- */
复制代码 idea对于Js代码的兼容姓比较,所以有时间不能够正常的加载。方法:清楚idea缓存,大概maven的clean,install,
rebuild项目。
到此为止我们初步的配置就都得到了测试通过,接下里进行必要的类创建,
创建类
entity实体构造
首先我们创建一个entity的软件包,这里相当于是和model功能同等的数据库,只不过用于存放我们的实体类,与数据库中的属性值根本保持同等,实现set和get的方法。我们先通过sql语句创建一个数据库,详细的字段如下
- create table t_user(
- uid int AUTO_INCREMENT comment '用户id',
- username varchar(20) not null unique comment '用户名',
- password char(32) not null comment '密码',
- salt char(36) comment '盐值',
- phone varchar(20) comment '电话号码',
- email varchar(30) comment '电子邮箱',
- gender int comment '0女1男',
- avatar varchar(50) comment '头像',
- is_delete int comment '0未删除,1已删除',
- create_user varchar(20) comment '日志创建人',
- create_time DATETIME comment '创建时间',
- modified_user varchar(20) comment '修改执行人',
- modified_time DATETIME comment '修改时间',
- primary key (uid)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
注意在创建时间时,我们用到的类的库时util里面的。
因为我们有很多个表里面的字段是完全一样的,所以如果每个类都写一遍相同的代码很不方便,于是通过表的结构提取出表的公共字段,放在一个实体类的基类里面BaseEntity,然后再用其他的类来继续这个类。- BaseEntity.java
- package com.ivan.store.entity;
- import java.io.Serializable;
- import java.util.Date;
- import java.util.Objects;
- //作为实体类的基类,需要满足一些约束/为了便于数据传输,实现序列化接口
- //@Data
- public class BaseEntity implements Serializable {
- private String createUser;
- private Date createTime;
- private String modifiedUser;
- private Date modifiedTime;
复制代码 这四个字段就是我们公共的表信息,但是仔细的人会发现数据库里的命名方式是create_user,而类中定义的是createUser这种写法,这是一种开发规范,我们在后续恒久层代码中会偏重关注这一点。- public Date getCreateTime() {
- return createTime;
- }
- public String getCreateUser() {
- return createUser;
- }
- public Date getModifiedTime() {
- return modifiedTime;
- }
- public String getModifiedUser() {
- return modifiedUser;
- }
- public void setCreateTime(Date createTime) {
- this.createTime = createTime;
- }
- public void setCreateUser(String createUser) {
- this.createUser = createUser;
- }
- public void setModifiedTime(Date modifiedTime) {
- this.modifiedTime = modifiedTime;
- }
- public void setModifiedUser(String modifiedUser) {
- this.modifiedUser = modifiedUser;
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof BaseEntity)) return false;
- BaseEntity that = (BaseEntity) o;
- return Objects.equals(getCreateUser(), that.getCreateUser()) && Objects.equals(getCreateTime(), that.getCreateTime()) && Objects.equals(getModifiedUser(), that.getModifiedUser()) && Objects.equals(getModifiedTime(), that.getModifiedTime());
- }
- @Override
- public int hashCode() {
- return Objects.hash(getCreateUser(), getCreateTime(), getModifiedUser(), getModifiedTime());
- }
- @Override
- public String toString() {
- return "BaseEntity{" +
- "createUser='" + createUser + '\'' +
- ", createTime=" + createTime +
- ", modifiedUser='" + modifiedUser + '\'' +
- ", modifiedTime=" + modifiedTime +
- '}';
- }
- }
复制代码 这一段话并不需要我们自己写,在idea中按下快捷键alt+insert,选中我们的表字段就可以自动生成对应的方法,至于为什么一定要写这些东西,可以参见[这篇文章](面试官:重写 equals 时为什么一定要重写 hashCode?-腾讯云开发者社区-腾讯云)

并且所有的实体类都需要进行方法重写。
现在我们再编写用户的实体类- User
- package com.ivan.store.entity;
- import org.springframework.stereotype.Component;
- import java.io.Serializable;
- import java.util.Date;
- import java.util.Objects;
- //@Component 自动的对象创建和修饰
- public class User extends BaseEntity implements Serializable {
- private Integer uid;
- private String username;
- private String password;
- private String salt;
- private String phone;
- private String email;
- private Integer gender;
- private String avatar;
- private Integer isDelete;
- //get和set方法,equal方法和hashcode,tostring方法
- //alt+insert批量插入
- public Integer getUid() {
- return uid;
- }
- public void setUid(Integer uid) {
- this.uid = uid;
- }
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- public String getSalt() {
- return salt;
- }
- public void setSalt(String salt) {
- this.salt = salt;
- }
- public String getPhone() {
- return phone;
- }
- public void setPhone(String phone) {
- this.phone = phone;
- }
- public String getEmail() {
- return email;
- }
- public void setEmail(String email) {
- this.email = email;
- }
- public Integer getGender() {
- return gender;
- }
- public void setGender(Integer gender) {
- this.gender = gender;
- }
- public String getAvatar() {
- return avatar;
- }
- public void setAvatar(String avatar) {
- this.avatar = avatar;
- }
- public Integer getIsDelete() {
- return isDelete;
- }
- public void setIsDelete(Integer isDelete) {
- this.isDelete = isDelete;
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof User)) return false;
- if (!super.equals(o)) return false;
- User user = (User) o;
- return Objects.equals(getUid(), user.getUid()) && Objects.equals(getUsername(), user.getUsername()) && Objects.equals(getPassword(), user.getPassword()) && Objects.equals(getSalt(), user.getSalt()) && Objects.equals(getPhone(), user.getPhone()) && Objects.equals(getEmail(), user.getEmail()) && Objects.equals(getGender(), user.getGender()) && Objects.equals(getAvatar(), user.getAvatar()) && Objects.equals(getIsDelete(), user.getIsDelete());
- }
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), getUid(), getUsername(), getPassword(), getSalt(), getPhone(), getEmail(), getGender(), getAvatar(), getIsDelete());
- }
- @Override
- public String toString() {
- return "User{" +
- "uid=" + uid +
- ", username='" + username + '\'' +
- ", password='" + password + '\'' +
- ", salt='" + salt + '\'' +
- ", phone='" + phone + '\'' +
- ", email='" + email + '\'' +
- ", gender=" + gender +
- ", avatar='" + avatar + '\'' +
- ", isDelete=" + isDelete +
- '}';
- }
- }
复制代码 恒久层
所谓的恒久层就是把数据可以永久保持的存储到设备中,不像放到内存中那样断电就消散,一样寻常来说,恒久层为直接的理解就是对数据库的各种操纵,如CRUD(增加,删除,修改,查询),更新等操纵
恒久层,就是把恒久的动作封装成一个独立的层,这是为了低落功能代码之间的关联.创建一个更清楚的抽象,提高代码的内聚力,低落代码的耦合度,提高可维护性和复用性.
详细到代码开发,就是规划需要的sql语句,mybatis操纵数据库insert into t_user(username,password) value(),用户注册时要去查询当前的用户名是否存在,如果存在不能注册select * from t_user where username = ?
此时我们就需要用到mapper这个概念,这个东西涉及到了映射
详细功能就是从我们的实体类里面获取到数据信息,然后编写一些接口功能,这些功能又可以服务于service层里的详细实现函数。代码如下:
创建mapper目次,创建差别的接口UserMapper,注意这里选择的是接口
我们知道,有抽象方法的类被称为抽象类,也就意味着抽象类中还能有不是抽象方法的方法。这样的类就不能算作纯粹的接口,只管它也可以提供接口的功能——只能说抽象类是普通类与接口之间的一种中庸之道。
语法层面上
- 抽象类可以提供成员方法的实现细节,而接口中只能存在 public abstract 方法;
- 抽象类中的成员变量可以是各种范例的,而接口中的成员变量只能是 public static final 范例的;
- 接口中不能含有静态代码块,而抽象类可以有静态代码块;
- 一个类只能继续一个抽象类,而一个类却可以实现多个接口。
接口定义sql语句的抽象方法,启动类定义接口注解- package com.ivan.store.mapper;
- import com.ivan.store.entity.User;
- import org.apache.ibatis.annotations.Mapper;
- /**
- * 用户模块的持久层接口
- */
- //@Mapper 缺陷:一个项目有很多个mapper,在启动类里添加注解MapperScan
- public interface UserMapper {
- /**
- * 插入用户的数据
- * @param user 用户的数据
- * @return 受影响的行数(增删改查影响的行数作为返回值,判断是否执行成功)
- */
- Integer insert(User user);
- /**
- * 根据用户名查询用户的数据
- * @param username 用户名
- * @return 找到则返回用户数据,没有找到返回null
- */
- User findByUsername(String username);
- }
复制代码 //@Mapper 缺陷:一个项目有很多个mapper,在启动类里添加注解MapperScan这里的注释意思是每一个接口文件都要添加这个注解,不如在启动类中直接添加一个MapperScan注解,在启动时就会自动扫描所有的映射然后放到spring里面。
现在编写好接口类之后,详细应该怎么实现呢
答:编写映射,定义xml映射文件和对应的接口进行关联。属于资源文件,要放到resource目次下,对应的映射文件和接口名字保持同等
mapper.xml
src/main/resources/mapper/UserMapper.xml代码如下,我们依次来解析这段xml语言- [/code]这是代码固定的开头,之后我们要编写mapper标签里详细的内容。
- namespace命名空间是要告诉spring去哪里找到对应的接口,需要一个完备的路径。在这个大标签下,我们对两个接口函数进行编写。
- 这个resultMap标签是自定义映射规则标签必须带的,焦点属性id表示分配一个id值,对应的是resultMap属性的值,type表示对查询结果与java的哪一个类的映射。之前因为数据库字段和我们类中定义的规范不一样,我们就需要在这里进行一个映射
- [code]<resultMap id="UserEntityMap" type="com.ivan.store.entity.User">
-
- <id column="uid" property="uid"></id>
- <result column="is_delete" property="isDelete"></result>
- <result column="create_user" property="createUser"></result>
- <result column="create_time" property="createTime"></result>
- <result column="modified_user" property="modifiedUser"></result>
- <result column="modified_time" property="modifiedTime"></result>
- </resultMap>
复制代码 然后将规则统一之后,我们就可以根据差别的规则来进行sql语句的编写- <insert id="insert" useGeneratedKeys="true" keyProperty="uid">
- INSERT INTO t_user(
- username ,password,salt, phone, email, gender,
- avatar, is_delete, create_user, create_time, modified_user,
- modified_time)
- VALUES
- ( #{username} ,#{password},#{salt}, #{phone}, #{email}, #{gender},
- #{avatar}, #{isDelete}, #{createUser}, #{createTime}, #{modifiedUser},
- #{modifiedTime})
- </insert>
- id属性:表示映射的接口方法的名称,直接在标签的内容来编写sql语句-->
- </p>
- </blockquote>
- <p>查询语句</p>
- [code]<select id="findByUsername" resultMap="UserEntityMap">
- SELECT * FROM t_user WHERE username = #{username}
- </select> 这里就用到了前面定义规则的id
复制代码 单位测试
每个独立的层要编写单位测试方法,测试每层的代码范围,在test包结构下创一个mapper包,在这个包下面再创建恒久层的测试
runwith知识点
@runWith注解作用:
- @RunWith就是一个运行器
- @RunWith(JUnit4.class)就是指用JUnit4来运行
- @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环 境,以便在测试开始的时间自动创建Spring的应用上下文
- @RunWith(Suite.class)的话就是一套测试集合
- package com.ivan.store.mapper;
- import com.ivan.store.entity.User;
- import org.apache.ibatis.annotations.Mapper;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
- @SpringBootTest // 表示标注当前的类是一个测试类,不会随同项目一块打包
- @RunWith(SpringRunner.class)//表示启动这个单元测试类(独立的单元测试不能运行),参数必须是SpringRunner的实例类型
- public class UserMapperTests {
- // idea有检测的功能,接口是不能直接创建bean的,用动态代理解决
- @Autowired
- private UserMapper userMapper;
- /**
- * 单元测试方法:单独运行,不用启动整个项目,可以做单元测试
- * 1. 必须被Test注解修饰
- * 2. 返回值类型必须是void
- * 3. 方法的参数列表不能指定任何类型
- * 4. 方法的访问修饰符必须是public
- */
- @Test
- public void insert(){
- User user = new User();
- user.setUsername("ivan");
- user.setPassword("regina");
- Integer rows = userMapper.insert(user);
- System.out.println(rows);
- }
- @Test
- public void findByUsername(){
- User user = userMapper.findByUsername("ivan");
- System.out.println(user);
- }
- }
复制代码 idea有检测的功能,接口是不能直接创建bean的,用动态代理办理,否则会让userMapper里面的函数变成static状态,这样的话又无法被调用。所以下列给出了修改以及运行测试的过程。




可以看到这个地方输出了数据库信息,说明恒久层写好了
业务层
规划异常
实现业务的主要逻辑,是体系架构中表现焦点价值的部分。将一个业务中所有的操纵封装成一个方法,同时保证方法中所有的数据库更新操纵,即保证同时乐成或同时失败。克制部分乐成部分失败引起的数据混乱操纵。在完成业务逻辑实现之前要先规划异常,比如用户在进行注册的时间大概产生用户名已存在,抛出一个异常
RuntimeException异常是一样寻常异常,作为这种异常的子类,然后再去定义详细的异常来继续。serviceException继续作为RuntimeException的基类去做拓展。
以下方法实现了对基类异常的重写
- package com.ivan.store.service.ex;
- /**业务异常的基类**/
- public class ServiceException extends RuntimeException{
- //重写
- public ServiceException() {
- super(); //只想抛一个异常 throw new ServiceException()
- }
- public ServiceException(String message) {
- super(message); //throw new ServiceException("业务层产生未知的异常")
- }
- 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);
- }
- }
复制代码根据业务层差别的功能来详细定义详细的异常的范例,统一去继续serviceException
注册时的异常:用户名已存在UsernameDepulicatedExpecption
未知的异常:执行数据插入操纵的时间,服务器大概数据库宕机。处于正在执行插入的过程中所产生的异常:InsertException
设计接口:抽象方法和定义,在service包下创建一个以I开头的名字,IUserService
接口和impl实现
创建一个实现IUserService接口的类UserServiceImpl的实现方法
同样的我们定义IUserService是我们的接口,然后创建一个新的软件包impl包来对所有的接口进行实现。- package com.ivan.store.service;
- import com.ivan.store.entity.User;
- /**用于模块业务层接口**/
- public interface IUserService {
- /**
- * 用户注册方法
- */
- void reg(User user);
- }
复制代码- package com.ivan.store.service.impl;
- import com.ivan.store.entity.User;
- import com.ivan.store.mapper.UserMapper;
- import com.ivan.store.service.IUserService;
- import com.ivan.store.service.ex.InsertException;
- import com.ivan.store.service.ex.UsernameDuplicatedException;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.util.DigestUtils;
- import java.nio.charset.StandardCharsets;
- import java.util.Date;
- import java.util.Locale;
- import java.util.UUID;
- /**用户模块业务层的实现类**/
- @Service//不添加这个会报错
- /**
- * @Service:将当前类的对象交给Spring管理,自动创建对象以及对象的维护
- */
- public class UserServiceImpl implements IUserService {
- @Autowired
- //private User user;
- private UserMapper userMapper;
- /**调用mapper层的方法**/
- @Override
- public void reg(User user) {
- //通过User参数获取传递过来的username
- String username = user.getUsername();
- //调用mapper的findByUsername方法判断是否用户已存在
- User result = userMapper.findByUsername(username);
- if (result!=null){
- //抛出异常
- throw new UsernameDuplicatedException("用户名被占用");
- }
- /**
- * 数据补全:
- * is_delete->0,
- * 创建时间
- */
- user.setIsDelete(0);
- user.setCreateUser(user.getUsername());
- user.setModifiedUser(user.getUsername());
- Date date = new Date();
- user.setCreateTime(date);
- user.setModifiedTime(date);
- // 执行注册业务功能
- Integer rows = userMapper.insert(user);
- if(rows != 1){
- throw new InsertException("注册过程出错");
- }
- }
复制代码 单位测试
在单位测试包下创建UserServiceTest类测试与上述功能

ctrl+alt+t自动加一个异常捕获

这个需要添加Service注解,在IUserServiceImpl.java里面添加注解表示这是service层的功能,不可以缺失
 - package com.ivan.store.service;
- import com.ivan.store.entity.User;
- import com.ivan.store.mapper.UserMapper;
- import com.ivan.store.service.ex.ServiceException;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.stereotype.Service;
- import org.springframework.test.context.junit4.SpringRunner;
- @SpringBootTest // 表示标注当前的类是一个测试类,不会随同项目一块打包
- @RunWith(SpringRunner.class)//表示启动这个单元测试类(独立的单元测试不能运行),参数必须是SpringRunner的实例类型
- public class UserServiceTests {
- // idea有检测的功能,接口是不能直接创建bean的,用动态代理解决
- @Autowired
- private IUserService userService;
- /**
- * 单元测试方法:单独运行,不用启动整个项目,可以做单元测试
- * 1. 必须被Test注解修饰
- * 2. 返回值类型必须是void
- * 3. 方法的参数列表不能指定任何类型
- * 4. 方法的访问修饰符必须是public
- */
- @Test
- public void reg(){
- try {
- User user = new User();
- user.setUsername("regina1");
- user.setPassword("ivan");
- userService.reg(user);
- System.out.println("test ok");
- } catch (ServiceException e) {
- //获取类的对象,再获取类的名称
- System.out.println(e.getClass().getSimpleName());
- //异常的具体描述信息
- System.out.println(e.getMessage());
- }
- }
- }
复制代码
salt加密
这一步就是为了实现一个密码的安全性,详细不做太多讲解。
- //UserServiceImpl.java
- private String getMd5Pwd(String pwd, String salt){
- return DigestUtils.md5DigestAsHex((salt+pwd+salt).getBytes(StandardCharsets.UTF_8)).toUpperCase();
- }
- public void reg(User user) {
- //通过User参数获取传递过来的username
- String username = user.getUsername();
- //调用mapper的findByUsername方法判断是否用户已存在
- User result = userMapper.findByUsername(username);
- if (result!=null){
- //抛出异常
- throw new UsernameDuplicatedException("用户名被占用");
- }
- //密码加密处理的实现 (Test之后补充)
- //salt + pwd + salt
- String pwd = user.getPassword();
- String salt = UUID.randomUUID().toString().toUpperCase();
- String newpwd = getMd5Pwd(salt,pwd);
- user.setPassword(newpwd);
- user.setSalt(salt);
复制代码
控制层
创建响应
状态码、状态描述信息、数据。这部分功能封装在一个类中,这个类作为方法的返回值返回给浏览器,我们统一设置在utils.JsonResult类里面。
- package com.ivan.store.util;
- import java.io.Serializable;
- /**
- * Json格式的数据相应
- */
- public class JsonResult<E> implements Serializable {
- private Integer state;//状态码
- private String message; //描述信息
- private E data; //用E表示任何类型的数据类型
复制代码 然后要构造一些相应的的构造函数,差别的参数可以接受差别的响应数据- public JsonResult(){
- }
- public JsonResult(Integer state) {
- this.state = state;
- }
- public JsonResult(Integer state, E data) {
- this.state = state;
- this.data = data;
- }
- public JsonResult(Integer state, String msg, E data) {
- this.state = state;
- this.message = msg;
- this.data = data;
- }
- public JsonResult(Throwable e){
- this.message = e.getMessage();
- }
- public Integer getState() {
- return state;
- }
- public void setState(Integer state) {
- this.state = state;
- }
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- public E getData() {
- return data;
- }
- public void setData(E data) {
- this.data = data;
- }
- }
复制代码 向后端服务器发送请求:依据当前的业务功能模块进行设计如下。- 请求路径:/users/reg
- 请求参数: User user
- 请求类型:POST/GET
- 响应数据:JsonResult<void>
复制代码 处理惩罚请求
- 创建一个控制层对应的类UserController依靠于业务层的接口。
- //@Controller
- @RestController //@RestController = //@Controller + @ResponseBody
- @RequestMapping("/users")
- public class UserController {
- @Autowired
- private IUserService userService;
- //@ResponseBody // 表示此方法的响应结果以json格式进行数据响应
- @RequestMapping("/reg")
- public JsonResult<Void> reg(User user) {
- JsonResult<Void> res = new JsonResult<>();
- try {
- userService.reg(user);
- res.setState(200);
- res.setMessage("用户注册成功");
- } catch (UsernameDuplicatedException e) {
- res.setState(4000);
- res.setMessage("用户名被占用");
- } catch (InsertException e) {
- res.setState(5000);
- res.setMessage("注册时产生未知的异常");
- }
- return res;
- }
- }
复制代码 Void 是 Java 中的一个特别范例,它实际上并不表示任何详细的值。
泛型介绍
在 Java 中,泛型允许你在类、接口和方法中利用范例参数,从而使得这些类、接口和方法可以处理惩罚多种差别范例的对象,同时提供编译时范例检查。
JsonResult 类
假设 JsonResult 是一个泛型类,定义如下:- public class JsonResult<T> {
- private int code;
- private String message;
- private T data;
复制代码- public JsonResult(T data) {
- this.data = data;
- }
- // 其他构造函数、getter和setter方法
复制代码 }
在这个类定义中,T 是一个范例参数,表示 data 字段的详细范例可以在实例化时指定。
实例化 JsonResult
当你创建一个 JsonResult 对象时,T 被详细化为 Void 范例。这意味着 data 字段将不会包含任何实际的数据,因为它被设置为 Void 范例。- Exception e = new Exception("An error occurred");
- JsonResult<Void> res = new JsonResult<>(e);
复制代码 解释
泛型参数 Void:
JsonResult 表示 JsonResult 的 data 字段将不会包含任何实际的数据,因为 Void 范例不能实例化任何对象。
这通常用于表示某些方法或操纵没有详细的返回值,但仍然需要返回一个包含其他信息(如状态码和消息)的响应对象。
只管 JsonResult 的构造函数接受一个 T 范例的参数,但在 JsonResult 中,T 被详细化为 Void。
因此,通报一个 Exception 对象 e 给构造函数看起来有些奇怪,但实际上,e 大概会被用于初始化 JsonResult 的其他字段(如 message 或 code),而不是 data 字段。
示例实现
为了更清楚地理解这一点,我们可以看一下 JsonResult 类的一个完备实现示例:
泛型参数 Void:
JsonResult 表示 JsonResult 的 data 字段将不会包含任何实际的数据,因为 Void 范例不能实例化任何对象。
这通常用于表示某些方法或操纵没有详细的返回值,但仍然需要返回一个包含其他信息(如状态码和消息)的响应对象。
构造函数参数 e:
只管 JsonResult 的构造函数接受一个 T 范例的参数,但在 JsonResult 中,T 被详细化为 Void。
因此,通报一个 Exception 对象 e 给构造函数看起来有些奇怪,但实际上,e 大概会被用于初始化 JsonResult 的其他字段(如 message 或 code),而不是 data 字段。
示例实现
为了更清楚地理解这一点,我们可以看一下 JsonResult 类的一个完备实现示例:- public class JsonResult<T> {
- private int code;
- private String message;
- private T data;} public JsonResult(int code, String message, T data) { this.code = code; this.message = message; this.data = data;}public JsonResult(int code, String message) { this(code, message, null);}public JsonResult(T data) { this(200, "OK", data);}// Getter and Setter methodspublic int getCode() { return code;}public void setCode(int code) { this.code = code;}public String getMessage() { return message;}public void setMessage(String message) { this.message = message;}public T getData() { return data;}public void setData(T data) { this.data = data;}
复制代码 }
利用示例- Exception e = new Exception("An error occurred");
- JsonResult<Void> res = new JsonResult<>(200, "OK", null);
复制代码 // 大概利用单参数构造函数
JsonResult res2 = new JsonResult(null);
结论
通过 JsonResult res = new JsonResult(e); 这段代码,res 的范例是 JsonResult,这意味着 res 的 data 字段将不会包含任何实际的数据。如果你盼望通报异常信息,可以通过其他字段(如 message)来通报。
详细的数据形式如图所示:

运行之后前端可以吸收到对应的数据信息。

优化:将控制层优化设计
在控制层抽离一个父类来统一处理惩罚关于异常的相关内容 BaseController,因为一个项目会有很多的controller,我们把公共异常设置为一个父类,然后再用子类来继续。- package com.ivan.store.controller;
- import com.ivan.store.service.ex.InsertException;
- import com.ivan.store.service.ex.ServiceException;
- import com.ivan.store.service.ex.UsernameDuplicatedException;
- import com.ivan.store.util.JsonResult;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- /**
- * 控制层类的基类
- */
- public class BaseController {
- public static final int RegIsOK = 200; //操作成功
- /**
- * 请求处理方法,这个方法的返回值就是需要传递给前端的数据
- * 自动将异常对象传递给此方法的参数列表上
- * 当项目中产生了异常,会被统一拦截到此方法中,这个方法就是请求处理方法
- */
- @ExceptionHandler(ServiceException.class) // 用户统一处理抛出的异常
- public JsonResult<Void> handlerException(Throwable e){
- JsonResult<Void> res = new JsonResult<>(e);
- if (e instanceof UsernameDuplicatedException){
- res.setState(4000);
- res.setMessage("用户名已被占用");
- }
- else if (e instanceof InsertException){
- res.setState(5000);
- res.setMessage("注册时产生未知错误");
- }
- return res;
- }
- }
复制代码- package com.ivan.store.controller;
- import com.ivan.store.entity.User;
- import com.ivan.store.service.IUserService;
- import com.ivan.store.service.ex.InsertException;
- import com.ivan.store.service.ex.UsernameDuplicatedException;
- import com.ivan.store.util.JsonResult;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- //@Controller
- @RestController //@RestController = //@Controller + @ResponseBody
- @RequestMapping("/users")
- public class UserController extends BaseController{
- @Autowired
- private IUserService userService;
- //@ResponseBody // 表示此方法的响应结果以json格式进行数据响应
- @RequestMapping("/reg")
- public JsonResult<Void> reg(User user){
- userService.reg(user);
- return new JsonResult<>(RegIsOK,"用户注册成功",null);
- }
- }
复制代码
结果是一样的。
这样的话就把三层框架的利用根本讲清楚了,后续还会写一些更复杂一点的东西。如果需要代码源码可以接洽博主,有什么题目尽请咨询。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |