用一个项目把控制层、业务层、持久层将明白了,每一句话都讲的很清楚

news/2024/11/14 18:32:49/文章来源:https://www.cnblogs.com/ivanlee717/p/18540551

实现一个数据库和前端的交互

三层结构

image-20241111170345140

  • 持久层开发:依据前端页面的设置规划相关的sql语句,以及进行配置

  • 业务层开发:核心功能控制、业务操作以及异常的处理

  • 控制层开发:前后端连接,接受请求,处理响应

完整的数据响应流程如下:

  1. 前端发起请求: 前端通过浏览器或其他客户端发起HTTP请求,请求后端提供的某个API接口或页面资源。请求可以包括HTTP方法(GET、POST、PUT、DELETE等)、URL、请求头和请求体等信息。

  2. 请求到达服务器: 请求通过网络到达服务器。服务器会根据请求的URL和HTTP方法来决定路由到哪个Controller处理。

  3. DispatcherServlet处理: Spring Boot中使用了DispatcherServlet作为前端控制器,它会接收到所有的HTTP请求,然后将请求分发给合适的Controller。

  4. Controller处理: 根据请求的URL和HTTP方法,DispatcherServlet将请求转发给对应的Controller处理。Controller是Spring MVC中的组件,负责处理具体的请求并生成响应。

  5. 业务逻辑处理: 在Controller中,可以进行业务逻辑的处理,包括调用Service层的方法、读取数据库、计算等。

  6. 调用Service层: 通常,业务逻辑的处理会涉及调用Service层的方法。Service层负责业务逻辑的组织和封装。

  7. 访问数据库或其他资源: 在Service层,可能需要访问数据库或其他外部资源来获取数据或执行一些操作。

  8. 返回响应数据: 处理完业务逻辑后,Controller会生成响应数据,通常是一个视图模板、JSON数据或其他格式的数据。

  9. 响应返回到前端: Controller生成的响应数据会通过HTTP协议返回给前端,响应包括HTTP状态码、响应头和响应体等信息。

接下来就进行一个完整的后端的三层框架的应用。

基础配置

根据以下过程初始化一个springBoot框架,这个其实也可以用Maven进行创建,但是Spring Initializr会帮助我们创建好一些测试文件和依赖。

下图是一般我们在创建项目时都会选择通用一点的JDK8版本,我用的spring boot版本是2.4左右。如果版本都太新的话,可能会发生很多莫名其妙的不兼容问题。但是默认的服务器好像已经没有8了,我们按照以下步骤来修改。

image-20241102100948436

image-20241102101043721

image-20241102101208269

application.propertiesspring.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.javapackage 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;@Testvoid contextLoads() {}@Test # 测试连接数据库void getConnection() throws SQLException {System.out.println(dataSource.getConnection());}}  

@Test就是表明我们只运行该函数来测试我们的数据库连通性。

image-20241102104821732

/*** 数据库连接池* 1. DBCP* 2. Spring MVC: C3P0* 3. Hikari 默认内部连接池* HikariProxyConnection@2127123542 默认内部连接池* wrapping com.mysql.cj.jdbc.ConnectionImpl@748a654a*/

idea对于Js代码的兼容姓比较,所以有时候不能够正常的加载。方法:清楚idea缓存,或者maven的clean,install,

rebuild项目。

image-20241102105835992

到此为止我们初步的配置就都得到了测试通过,接下里进行必要的类创建,

创建类

entity实体构造

首先我们创建一个entity的软件包,这里相当于是和model功能一致的数据库,只不过用于存放我们的实体类,与数据库中的属性值基本保持一致,实现set和get的方法。我们先通过sql语句创建一个数据库,具体的字段如下

image-20241111172139399

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;

image-20241102154116436

注意在创建时间时,我们用到的类的库时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;}@Overridepublic 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());}@Overridepublic int hashCode() {return Objects.hash(getCreateUser(), getCreateTime(), getModifiedUser(), getModifiedTime());}@Overridepublic String toString() {return "BaseEntity{" +"createUser='" + createUser + '\'' +", createTime=" + createTime +", modifiedUser='" + modifiedUser + '\'' +", modifiedTime=" + modifiedTime +'}';}
}

这一段话并不需要我们自己写,在idea中按下快捷键alt+insert,选中我们的表字段就可以自动生成对应的方法,至于为什么一定要写这些东西,可以参见[这篇文章](面试官:重写 equals 时为什么一定要重写 hashCode?-腾讯云开发者社区-腾讯云)

image-20241111172817274

并且所有的实体类都需要进行方法重写。

现在我们再编写用户的实体类

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;}@Overridepublic 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());}@Overridepublic int hashCode() {return Objects.hash(super.hashCode(), getUid(), getUsername(), getPassword(), getSalt(), getPhone(), getEmail(), getGender(), getAvatar(), getIsDelete());}@Overridepublic String toString() {return "User{" +"uid=" + uid +", username='" + username + '\'' +", password='" + password + '\'' +", salt='" + salt + '\'' +", phone='" + phone + '\'' +", email='" + email + '\'' +", gender=" + gender +", avatar='" + avatar + '\'' +", isDelete=" + isDelete +'}';}
}

持久层

所谓的持久层就是把数据可以永久保持的存储到设备中,不像放到内存中那样断电就消失,一般来说,持久层为直接的理解就是对数据库的各种操作,如CRUD(增加,删除,修改,查询),更新等操作

持久层,就是把持久的动作封装成一个独立的层,这是为了降低功能代码之间的关联.创建一个更清晰的抽象,提高代码的内聚力,降低代码的耦合度,提高可维护性和复用性.

image-20241111173823885

具体到代码开发,就是规划需要的sql语句,mybatis操作数据库insert into t_user(username,password) value(),用户注册时要去查询当前的用户名是否存在,如果存在不能注册select * from t_user where username = ?

此时我们就需要用到mapper这个概念,这个东西涉及到了映射image-20241111174835797

具体功能就是从我们的实体类里面获取到数据信息,然后编写一些接口功能,这些功能又可以服务于service层里的具体实现函数。代码如下:

创建mapper目录,创建不同的接口UserMapper,注意这里选择的是接口

image-20241102163248012

我们知道,有抽象方法的类被称为抽象类,也就意味着抽象类中还能有不是抽象方法的方法。这样的类就不能算作纯粹的接口,尽管它也可以提供接口的功能——只能说抽象类是普通类与接口之间的一种中庸之道。

语法层面上

  • 抽象类可以提供成员方法的实现细节,而接口中只能存在 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里面。image-20241111175739152

现在编写好接口类之后,具体应该怎么实现呢

答:编写映射,定义xml映射文件和对应的接口进行关联。属于资源文件,要放到resource目录下,对应的映射文件和接口名字保持一致

mapper.xml

src/main/resources/mapper/UserMapper.xml代码如下,我们依次来解析这段xml语言

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">

这是代码固定的开头,之后我们要编写mapper标签里具体的内容。

<mapper namespace="com.ivan.store.mapper.UserMapper">namespace命名空间是要告诉spring去哪里找到对应的接口,需要一个完整的路径。在这个大标签下,我们对两个接口函数进行编写。

<resultMap id="UserEntityMap" type="com.ivan.store.entity.User">这个resultMap标签是自定义映射规则标签必须带的,核心属性id表示分配一个id值,对应的是resultMap属性的值,type表示对查询结果与java的哪一个类的映射。之前因为数据库字段和我们类中定义的规范不一样,我们就需要在这里进行一个映射

<resultMap id="UserEntityMap" type="com.ivan.store.entity.User"><!--将表的字段和类的属性不一致的字段进行匹配指定column是表的字段,property是映射名称--><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语句--><!--useGeneratedKeys="true": uid主键自增 keyProperty="uid":uid作为主键

select在执行的时候,查询的结果是一个对象或者多个对象
单个对象:resultType:表示查询的结果及类型,需要指定对应的映射类的类型,并且包含完整的包接口
resultMap:表的字段和类的对象属性字段不一致时,来自定义查询结果集的映射规则,-->

查询语句

<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的,用动态代理解决@Autowiredprivate UserMapper userMapper;/*** 单元测试方法:单独运行,不用启动整个项目,可以做单元测试* 1. 必须被Test注解修饰* 2. 返回值类型必须是void* 3. 方法的参数列表不能指定任何类型* 4. 方法的访问修饰符必须是public*/@Testpublic void insert(){User user = new User();user.setUsername("ivan");user.setPassword("regina");Integer rows = userMapper.insert(user);System.out.println(rows);}@Testpublic void findByUsername(){User user = userMapper.findByUsername("ivan");System.out.println(user);}
}

idea有检测的功能,接口是不能直接创建bean的,用动态代理解决,否则会让userMapper里面的函数变成static状态,这样的话又无法被调用。所以下列给出了修改以及运行测试的过程。

image-20241105144333368

image-20241105144643977

image-20241105163745734

image-20241105164917058

可以看到这个地方输出了数据库信息,说明持久层写好了

业务层

规划异常

实现业务的主要逻辑,是系统架构中体现核心价值的部分。将一个业务中所有的操作封装成一个方法,同时保证方法中所有的数据库更新操作,即保证同时成功或同时失败。避免部分成功部分失败引起的数据混乱操作。在完成业务逻辑实现之前要先规划异常,比如用户在进行注册的时候可能产生用户名已存在,抛出一个异常

RuntimeException异常是一般异常,作为这种异常的子类,然后再去定义具体的异常来继承。serviceException继承作为RuntimeException的基类去做拓展。

以下方法实现了对基类异常的重写

image-20241105172933433

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的实现方法

image-20241111201919727

同样的我们定义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层的方法**/@Overridepublic void reg(User user) {//通过User参数获取传递过来的usernameString 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类测试与上述功能

image-20241111103641395

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

image-20241111104024620

这个需要添加Service注解,在IUserServiceImpl.java里面添加注解表示这是service层的功能,不可以缺失

image-20241111104242458

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的,用动态代理解决@Autowiredprivate IUserService userService;/*** 单元测试方法:单独运行,不用启动整个项目,可以做单元测试* 1. 必须被Test注解修饰* 2. 返回值类型必须是void* 3. 方法的参数列表不能指定任何类型* 4. 方法的访问修饰符必须是public*/@Testpublic 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());}}}

image-20241111104609081

salt加密

这一步就是为了实现一个密码的安全性,具体不做太多讲解。

image-20241111105641567

//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参数获取传递过来的usernameString username = user.getUsername();//调用mapper的findByUsername方法判断是否用户已存在User result = userMapper.findByUsername(username);if (result!=null){//抛出异常throw new UsernameDuplicatedException("用户名被占用");}//密码加密处理的实现 (Test之后补充)//salt + pwd + saltString pwd = user.getPassword();String salt = UUID.randomUUID().toString().toUpperCase();String newpwd = getMd5Pwd(salt,pwd);user.setPassword(newpwd);user.setSalt(salt);

image-20241111112606714

控制层

创建响应

状态码、状态描述信息、数据。这部分功能封装在一个类中,这个类作为方法的返回值返回给浏览器,我们统一设置在utils.JsonResult类里面。

image-20241111142144167

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>

处理请求

  1. 创建一个控制层对应的类UserController依赖于业务层的接口。
//@Controller
@RestController //@RestController = //@Controller + @ResponseBody
@RequestMapping("/users")
public class UserController {@Autowiredprivate 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 methods
public 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<Void> res2 = new JsonResult<>(null);
结论
通过 JsonResult<Void> res = new JsonResult<>(e); 这段代码,res 的类型是 JsonResult<Void>,这意味着 res 的 data 字段将不会包含任何实际的数据。如果你希望传递异常信息,可以通过其他字段(如 message)来传递。

具体的数据形式如图所示:

image-20241111203203040

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

image-20241111144150267

优化:将控制层优化设计

在控制层抽离一个父类来统一处理关于异常的相关内容 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{@Autowiredprivate IUserService userService;//@ResponseBody // 表示此方法的响应结果以json格式进行数据响应@RequestMapping("/reg")public JsonResult<Void> reg(User user){userService.reg(user);return new JsonResult<>(RegIsOK,"用户注册成功",null);}
}

image-20241111145650954

image-20241111204705601效果是一样的。

这样的话就把三层框架的使用基本讲清楚了,后续还会写一些更复杂一点的东西。如果需要代码源码可以联系博主,有什么问题尽请咨询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/831982.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

『模拟赛』NOIP2024加赛4

『模拟赛记录』NOIP2024加赛4Rank 给我唐完了,又名,【MX-S5】梦熊 NOIP 2024 模拟赛 1。A. 王国边缘 好像简单倍增就做完了。 由于昨天 T5 在我脑海中留下了挥之不去的印象,今天一上来看到这题就发现是一个内向基环树森林。然后被硬控硬控硬控,最后一个小点加一点优化就能过…

使用 roslyn 的 Source Generator 自动完成依赖收集和注册

roslyn Source Generator 使用案例在 使用 Hosting 构建 WPF 程序 提到,因为不使用 Stylet 默认的 IOC 容器,所以不能自动收集和注册 View/ViewModel,需要动手处理。 如果项目比较大,手动处理显然过于麻烦。这里使用 roslyn 的 Source Generator 自动完成依赖收集和注册。 …

高级语言程序设计第七次作业

这个作业属于哪个课程:https://edu.cnblogs.com/campus/fzu/2024C/ 这个作业要求在哪里: https://edu.cnblogs.com/campus/fzu/2024C/homework/13304 学号:102400204 姓名:刘嘉奕 1.2.需要思维贯穿,逻辑性强3.刚开始没用指针,导致结果为负数,但在使用指针过程中还存在不…

叉积

向量基本运算点积叉积一.Transmitters由叉积的基本定理得出,要判断一个点c再一条直线ab的左边还是右边,只要看(b-a)*(c-a)大于0还是小于0,对于这题,我们可以将左右边看成发射范围的半圆,因为要找出覆盖最多点的数量,所以最优解肯定有一个点再直径上,再通过叉积找出所…

内核脏牛提权

脏牛靶机 已知靶机账号密码:msfadmin/msfadmin 靶机开启ssh服务开启靶机获取IP msfadmin/msfadmin登录 ifconfig #获取靶机IP得到靶机IP:192.168.213.247 开启kali ssh链接靶机 直接输入ssh msfadmin@192.168.213.247进行链接会出现报错 出现报错的原因是ssh客户端和服务器之…

[极客大挑战 2019]Secret

首页什么都没有,查看网页源码,发现有一个链接,提示found除去view-source访问,得到以下页面,点击secret直接查阅结束,没回显flag,依旧在卖关子。使用burp suite抓包获取返回的源码,找到注释中有个文件secr3t.php访问secr3t.php得到代码里说flag在flag.php里,直接访问fl…

今年测试这工资是认真的吗?

在如今的求职市场中,软件测试面试中的“八股文”几乎成为了一种必考内容。所谓“八股文”,指的是一系列标准化的技术面试题目,这些题目涵盖了测试理论、接口自动化测试、测试工具框架、性能测试、项目场景等多个方面...📝 博主首页 : 「码上生花」 ,同名公众号 :「伤心的…

开发人员,千万不要去碰那该死的业务参数,无论什么时候!

你好呀,我是歪歪。 前几天发了一个牢骚:本来只是单纯的吐槽一下,但是好多人对其中的细节比较感兴趣。 大家都是搞技术的嘛,对于“踩 BUG”这种喜闻乐见的事情,有兴趣是很正常的。 其实我这个 BUG,其实严格意义上不能叫做 BUG,因为和程序无关,甚至和技术的关系都不算大。…

java小课设:使用MySQL做一个聊天室

bro是个懒狗,耗时一个晚上,只写了一些基础功能,其他的可以根据需要自己添加实现思路:在MySQL数据库中设置一个message表,用来存储聊天信息,聊天界面输入的内容写入message表,用户程序每秒从MySQL中获取一次聊天记录,并加载进入自己的页面,实现聊天室。食用方法: Chat…

Quartz集群增强版_00.How to use?(如何使用)

Quartz集群增强版_00.How to use?(如何使用)转载请著名出处 https://www.cnblogs.com/funnyzpc/p/18540378开源地址 https://github.com/funnyzpc/quartz 表的基本结构 总的来说任务的配置及开发基本遵从上图的表的基本关系,除 app 以及 node 之外均需要手动手动配置,app…

开源三代示波器的高速波形刷新方案开源,支持VNC远程桌面,手机,Pad,电脑均可访问(2024-11-11)

说明: 1、本来这段时间是一年一度Hackaday硬件设计开源盛宴,但hackaday电子大赛在去年终结了。所以我开源个我的吧。 2、三代示波器的高速波形刷新方案,前两年就做好了,这两年忙H7-TOOL的更新比较多,三代示波器的更新就搁置了。但刷新方案是没问题的,开源分享给大家。 3、…

PSQL 环境安装配置

准备工作:安装包 plsqldev1400x64.msi 注册码 汉化包 chinese.exe 轻量级数据 instantclient_11_2 安装【PSQL】第一步大法操作! 默认的安装路径:C:\Program Files\PLSQL Developer 14安装【轻量级 instantclient_11_2】 复制或解压到 C:\Program Files\PLSQL Developer…