Spring Boot框架主要解决了创建工程后需要进行繁琐的配置的问题,是一个“开箱即用”的框架,其核心思想是“约定大于配置”。
使用IntelliJ IDEA的创建向导中的Spring Initializer
即可创建Spring Boot工程。
在创建时,如果 https://start.spring.io 无响应,可尝试替换为 https://start.springboot.io。
在创建过程中,需要填写并关注的几项有:
cn.tedu
boot-demo
8
Group Id
和Artifact Id
组成注意:如果Artifact Id
中使用减号分隔了多个单词,在Package
中默认并没有分开,通常建议手动添加小数点(.
)进行分隔
注意:此处Package
决定了默认的组件扫描,所以,在后续开发代码时,所有的组件类都必须放在此包或其子孙包下,在开发实践中,其实会把所有创建的类、接口都放在此包或其子孙包下,不是组件的类不添加组件即可
注意:当工程已经创建出来后,不要修改包的名称,除非你已经掌握了解决方案!
在添加依赖项时,首先需要注意的就是Spring Boot的版本号,通常非常不建议使用较新的版本号,建议使用的是半年或1年之内的版本即可!如果在创建向导的界面没有需要的版本号,可以随便选一下,当项目创建成功后,打开pom.xml
,修改<parent>
子级的<version>
节点的值即可。
当项目创建成功后,在src/main/java
下默认就存在一个包,是由创建项目时填写的Package
决定的,就是当前项目组件扫描的包,相当于默认就有了@ComponentScan("cn.tedu.boot.demo")
。
项目中默认就存在BootDemoApplication
类,此类的名称是由创建项目时填写的Artifact Id
加上Application
单词组成的,这个类名称是可以改的,这个类中有main()
方法,执行此方法就会启动整个项目,将加载项目中所有依赖所需的环境。
在src/main/resources
下默认存在application.properties
配置文件,它是项目默认会加载的配置文件。另外,Spring Boot的自动配置机制要求此处的许多配置是使用固定的属性名的!
客户端发出请求,最终增加管理员信息。
在pom.xml
中添加必要的依赖项:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
当添加以上依赖项,如果启动项目(执行BootDemoApplication
类中的main()
方法)会报告错误,因为Spring Boot允许自动配置,当添加以上依赖项后,就会自动读取连接数据库的相关信息,并自动配置数据源,甚至Mybatis所需要其它基础配置,而目前并没有配置连接数据库的相关信息,所以出现错误!
则在application.properties
中添加配置:
spring.datasource.url=jdbc:mysql://localhost:3306/mall_ams?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
完成后,在src/test/java
下找到默认即存在的测试类,在此测试类中尝试获取数据库连接对象:
@SpringBootTest
class BootDemoApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws Exception {
System.out.println(dataSource.getConnection());
}
}
如果能顺利执行此测试,则表示以上配置是正确的!
为了简化编写POJO类,通常会在项目中添加Lombok
依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
提示:当使用了Lombok
后,应该在开发工具中安装Lombok插件,否则,在编写代码时,所有相关的Setters & Getters都没有自动提示,也会报告语法错误,但是不影响运行。
在插入数据时,需要使用实体类封装即将插入到表中的多个数据,则在cn.tedu.boot.demo
包下创建entity
子包,并在其下创建Admin
类:
@Data
public class Admin implements Serializable {
private Long id;
private String username;
private String password;
private String nickname;
private String avatar;
private String phone;
private String email;
private String description;
private Integer isEnable;
private String lastLoginIp;
private Integer loginCount;
private LocalDateTime gmtLastLogin;
private LocalDateTime gmtCreate;
private LocalDateTime gmtModified;
}
要执行的SQL语句大致是:
insert into ams_admin (除了id以外的字段列表……) values (值列表)
则在cn.tedu.boot.demo
包下创建mapper
子包,并在其下创建AdminMapper
接口,在接口中添加抽象方法:
package cn.tedu.boot.demo.mapper;
import cn.tedu.boot.demo.entity.Admin;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminMapper {
int insert(Admin admin);
}
还需要进行配置,使得Mybatis知道这些接口文件在哪里!则在cn.tedu.boot.demo
下创建config
包,并在此包下创建MybatisConfiguration
类,通过@MapperScan
配置接口文件所在的包:
package cn.tedu.boot.demo.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("cn.tedu.boot.demo.mapper")
public class MybatisConfiguration {
}
提示:关于@MapperScan
注解,还可以配置在项目的启动类上(BootDemoApplication
),因为启动类上有@SpringBootApplication
注解,其元注解中有@SpringBootConfiguration
,其元注解中有@Configuration
,所以,启动类本身也是配置类!但是,如果项目中的配置较多,不建议全部写在启动类中,所以,可以分为多个配置类,独立配置。
接下来,在src/main/resources
下创建mapper
文件夹,并从前序项目中复制粘贴得到AdminMapper.xml
文件(删除原文件中已经配置的SQL等代码),然后,在此文件中配置抽象方法映射的SQL:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.boot.demo.mapper.AdminMapper">
<!-- int insert(Admin admin); -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into ams_admin (
username, password, nickname, avatar,
phone, email, description, is_enable,
last_login_ip, login_count, gmt_last_login, gmt_create,
gmt_modified
) values (
#{username}, #{password}, #{nickname}, #{avatar},
#{phone}, #{email}, #{description}, #{isEnable},
#{lastLoginIp}, #{loginCount}, #{gmtLastLogin}, #{gmtCreate},
#{gmtModified}
)
</insert>
</mapper>
完成后,还是应该配置这些XML文件的位置,需要在application.properties
中添加配置:
mybatis.mapper-locations=classpath:mapper/*.xml
接下来,应该通过测试检验以上代码是否可以正确运行,为了保证测试时可以正确的断言,应该在src/test
下创建resources
文件夹,并从前序项目中复制脚本文件,至少包含清空并还原数据表、插入测试数据这2个脚本文件。
然后,在src/test/java
下的cn.tedu.boot.demo
包下创建mapper
子包,并在其下创建AdminMapperTests
测试类,在类上添加@SpringBootTest
注解,在类中自动装配AdminMapper
类型的对象,并编写、执行测试:
package cn.tedu.boot.demo.mapper;
import cn.tedu.boot.demo.entity.Admin;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
@SpringBootTest
public class AdminMapperTests {
@Autowired
AdminMapper mapper;
// 测试插入数据是成功的
@Test
@Sql(scripts = {"classpath:truncate.sql"})
@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testInsertSuccessfully() {
// 准备测试数据
String username = "admin001";
String password = "000000";
Admin admin = new Admin();
admin.setUsername(username);
admin.setPassword(password);
// 断言测试过程中不会抛出异常
Assertions.assertDoesNotThrow(() -> {
// 执行测试
int rows = mapper.insert(admin);
// 断言结果
Assertions.assertEquals(1, rows);
Assertions.assertEquals(1L, admin.getId());
});
}
}
基于当前数据表的设计,每个管理员的“用户名”必须是唯一的,在提交增加管理员(或注册)时,必须先检查用户名是否被占用,如果被占用,将不允许增加(或注册)。判断“是否被占用”可以通过“根据用户名查询用户数据”来分析!
此部分练习请自行完成!
业务逻辑层是制定数据访问规则的层,此前的数据访问层只有功能,没有规则,例如执行“插入管理员数据”,则对应的方法就一定会执行,并不考虑是否合理,有关“合理”、“是否允许”等这样规则都是通过业务逻辑层来实现的。
业务逻辑层是数据访问层的调用者,通过调用相关的功能来保证规则的合理性、完整性、有效性!例如,在业务逻辑层中,可以先调用“根据用户名查询管理员信息”,再根据调用的返回值来决定是否执行“插入管理员数据”,就可以保证“每个管理员的用户名都是唯一的”这样的规则。
另外,业务逻辑层还需要考虑数据的完整性,因为在执行数据访问时,并不是所有必须的数据都会由客户端提交过来,在这样的过程中,业务逻辑层就需要补全一些数据,例如在“增加管理员”时,“是否启用”可能不会设计为客户端提交的数据,则业务逻辑层就可以补全此属性再调用数据访问进行插入数据操作。
对于一些特殊的数据,可能还需要在业务逻辑层中进行特殊的处理,以保证数据的合理性或有效性,典型的例如各用户的密码,由客户端提交过来的密码通常是明文,在业务逻辑层就应该对密码进行加密处理,并得到密文,然后再向数据库中写入。
在实际编写代码时,业务逻辑层的关键字是Service
,通常业务逻辑层的类或接口名中都有此关键字。
业务逻辑层通常有2个部分,一个是接口,另一个是此接口的实现类。
注意:强烈建议在业务逻辑层先定义接口,再编写实现类!这样做是一种基于接口编程的做法,是提倡的,并且,在后续使用基于Spring JDBC的事务管理中,也要求业务逻辑层必须有接口!
在编写业务逻辑层,所有视为“失败”的情况都应该将异常抛出,而不要处理!
为了更好的在业务逻辑层表现“错误”(操作失败,例如增加管理员时,用户名已存在,即视为错误),应该自定义一些异常类型,并在处理业务逻辑的过程中,当出现错误时抛出异常!
则在cn.tedu.boot.demo
下创建ex
子包,并在其下创建ServiceException
异常类,继承自RuntimeException
,并且,至少添加带String
参数的构造方法,便于抛出异常时可以快捷封装错误的描述文本。
package cn.tedu.boot.demo.ex;
public class ServiceException extends RuntimeException {
public ServiceException() {
}
public ServiceException(String message) {
super(message);
}
}
提示:自定义的业务异常应该继承自RuntimeException
,因为当抛出RuntimeException
对象时,不需要在方法的声明上使用throws
声明抛出,并且,此方法的调用者还必须通过try...catch
或throws
解决语法问题,同时,由于业务逻辑层不适合处理异常,应该始终抛出,并且,业务逻辑层的调用者是控制器层,在Spring MVC中有统一处理异常的机制,所以在控制器中也应该是始终抛出即可,那么,对于异常的语法使用是固定的,而使用RuntimeException
就可以避免受到语法的约束!另外,在后续基于Spring JDBC的事务管理中,默认也是根据RuntimeException
进行失败的处理的!
需要自定义类型将“增加管理员”的各数据封装起来,则在cn.tedu.boot.demo
下创建dto
子包,并在其下创建AdminAddNewDTO
类,并在这个类中声明各属性:
@Data
public class AdminAddNewDTO implements Serializable {
private String username;
private String password;
private String nickname;
}
在cn.tedu.boot.demo
包下创建service
子包,并在其下创建IAdminService
接口,并在接口中声明“增加管理员”的抽象方法:
public interface IAdminService {
void addNew(AdminAddNewDTO adminAddNewDTO);
}
提示:在业务逻辑层的抽象方法中,设计返回值时,仅以操作成功为前提来设计即可,因为所有的失败都会通过抛出异常的方式来表现。
提示:关于抽象方法的参数,如果参数的数量较少,直接声明即可,如果参数数量较多,则应该封装,在封装时,应该注意“将客户端会提交的数据封装在一起,如果某些数据不是客户端提交过来的,则不要封装在一起”。
SLF4j是一款主流的日志框架,用于在代码中添加一些输出日志的语句,最终这些日志可以输出到控制台,或文件,甚至数据库中。
在SLF4j日志框架中,会将日志的重要程度分为几个级别,常用级别中,从不重要到非常重要,依次是:
在使用时,可以控制日志的显示级别,较低级别的将不会被显示,例如:
info
时,只会显示info
、warn
、error
debug
时,只会显示debug
、info
、warn
、error
trace
时,会显示所有级别的日志在Spring Boot项目中,在spring-boot-starter
中已经集成了日志的依赖项,是可以直接使用的!在application.properties
中添加配置,可以控制日志的显示级别,例如:
logging.level.cn.tedu.boot.demo.service.impl=info
在以上属性名中,配置的包是“根包”,例如配置为cn.tedu
时,其子孙包中都会应用此配置。
当项目中已经添加了Lombok依赖后,可以在需要输出日志的类上添加@Slf4j
注解,然后,在类中就可以使用名为log
的变量来输出日志!
输出日志的示例代码:
log.trace("输出trace级别的日志");
log.debug("输出debug级别的日志");
log.info("输出info级别的日志");
log.warn("输出warn级别的日志");
log.error("输出error级别的日志");
在开发实践中,应该根据要输出的内容的敏感程度、重要性来选择调用某个方法,以输出对应级别的日志,例如涉及关键数据的应该使用trace
或debug
级别,这样的话,当交付项目时,将设置日志显示级别的配置删除,或显式的配置为info
级别,则trace
、debug
级别的日志将不会被输出。
另外,warn
和error
级别的日志不受显示级别的限制。
关于输出日志的方法,都是被重载了多次的!如果输出的内容只是1个字符串,应该使用例如:
public void debug(String msg);
如果这个字符串中需要拼接多个变量的值,则应该使用:
public void debug(String format, Object... arguments);
使用示例如下:
log.debug("已经对密码进行加密处理,原文={},密文={}", rawPassword, encodedPassword);
以上这种做法会缓存、预编译字符串,再将值代入去执行,所以执行效率还远高于System.out.println()
的输出语句!
另外,需要注意的是,SLF4j只是一个日志框架,它提供了使用日志的标准,并没有实现输出日志的具体功能,在现行版本的Spring Boot中,还依赖了SLF4j的具体实现,默认是logback
框架。
通常,会在service
包下再创建impl
子包,用于存放业务接口的实现类,并且,实现类的名称通常是“接口名(不包含首字母I
) + Impl
”。
业务实现类应该实现业务接口,并且,还应该添加@Service
注解。
所以,在cn.tedu.boot.demo.service.impl
中创建AdminServiceImpl
类,实现IAdminService
接口,在类上添加@Service
注解,并重写接口中的抽象方法:
package cn.tedu.boot.demo.service.impl;
import cn.tedu.boot.demo.dto.AdminAddNewDTO;
import cn.tedu.boot.demo.service.IAdminService;
import org.springframework.stereotype.Service;
@Service
public class AdminServiceImpl implements IAdminService {
@Override
public void addNew(AdminAddNewDTO adminAddNewDTO) {
}
}
接下来,在编写业务方法(实现接口中的抽象方法)之前,应该整理此业务的编写思路:
package cn.tedu.boot.demo.service.impl;
import cn.tedu.boot.demo.dto.AdminAddNewDTO;
import cn.tedu.boot.demo.entity.Admin;
import cn.tedu.boot.demo.ex.ServiceException;
import cn.tedu.boot.demo.mapper.AdminMapper;
import cn.tedu.boot.demo.service.IAdminService;
import cn.tedu.boot.demo.util.GlobalPasswordEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Slf4j
@Service
public class AdminServiceImpl implements IAdminService {
// 自动装配AdminMapper
@Autowired
private AdminMapper adminMapper;
@Override
public void addNew(AdminAddNewDTO adminAddNewDTO) {
// 通过参数adminAddNewDTO中的username,调用AdminMapper的Admin getByUsername(String username)执行查询,并获取查询结果
log.debug("即将增加管理员:{}", adminAddNewDTO);
String username = adminAddNewDTO.getUsername();
Admin queryResult = adminMapper.getByUsername(username);
// 判断查询结果是否【不为null】
if (queryResult != null) {
// 是:表示用户名已经被占用,抛出ServiceException:增加管理员失败,用户名已经被占用
log.warn("增加管理员失败,用户名({})已经被占用!", username);
throw new ServiceException("增加管理员失败,用户名已经被占用!");
}
// 以参数adminAddNewDTO中的password作为明文,执行加密,得到密文密码
String rawPassword = adminAddNewDTO.getPassword();
String encodedPassword = GlobalPasswordEncoder.encode(rawPassword);
log.debug("已经对密码进行加密处理,原文={},密文={}", rawPassword, encodedPassword);
// 创建新的Admin对象
Admin admin = new Admin();
// 为Admin对象的属性赋值:username,nickname来自参数adminAddNewDTO
admin.setUsername(username);
admin.setNickname(adminAddNewDTO.getNickname());
// 为Admin对象的属性赋值:password > 密文密码
admin.setPassword(encodedPassword);
// 为Admin对象的属性赋值:avatar, phone, email, description保持为null
// 为Admin对象的属性赋值:isEnable > 1
admin.setIsEnable(1);
// 为Admin对象的属性赋值:lastLoginIp > null
// 为Admin对象的属性赋值:loginCount > 0
admin.setLoginCount(0);
// 为Admin对象的属性赋值:gmtLastLogin > null
// 为Admin对象的属性赋值:gmtCreate, gmtModified > LocalDateTime.now()
LocalDateTime now = LocalDateTime.now();
admin.setGmtCreate(now);
admin.setGmtModified(now);
// 调用AdminMapper对象的int insert(Admin admin)方法插入管理员数据,并获取返回值
log.debug("即将执行插入管理员数据:{}", admin);
int rows = adminMapper.insert(admin);
// 判断返回值是否不为1
if (rows != 1) {
// 抛出ServiceException:服务器忙,请稍后再次尝试
log.warn("服务器忙,请稍后再次尝试!");
throw new ServiceException("服务器忙,请稍后再次尝试!");
}
}
}
完成后,在src/test/java
下的cn.tedu.boot.demo
下创建service
子包,并在其下创建AdminServiceTests
测试类,编写并执行测试:
package cn.tedu.boot.demo.service;
import cn.tedu.boot.demo.dto.AdminAddNewDTO;
import cn.tedu.boot.demo.ex.ServiceException;
import cn.tedu.boot.demo.service.impl.AdminServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
@SpringBootTest
public class AdminServiceTests {
@Autowired
IAdminService service;
@Test
@Sql(scripts = {"classpath:truncate.sql"})
@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testAddNewSuccessfully() {
// 测试数据
String username = "admin001";
String password = "123456";
String nickname = "管理员";
AdminAddNewDTO adminAddNewDTO = new AdminAddNewDTO()
.setUsername(username)
.setPassword(password)
.setNickname(nickname);
// 断言不会抛出异常
Assertions.assertDoesNotThrow(() -> {
// 执行测试
service.addNew(adminAddNewDTO);
});
}
@Test
@Sql(scripts = {"classpath:truncate.sql", "classpath:insert_data.sql"})
@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testAddNewFailBecauseUsernameConflict() {
// 测试数据
String username = "admin001";
String password = "123456";
String nickname = "管理员";
AdminAddNewDTO adminAddNewDTO = new AdminAddNewDTO()
.setUsername(username)
.setPassword(password)
.setNickname(nickname);
// 断言不会抛出异常
Assertions.assertThrows(ServiceException.class, () -> {
// 执行测试
service.addNew(adminAddNewDTO);
});
}
}
当需要开发控制器时,需要在项目中存在spring-boot-starter-web
的依赖项,此依赖项将包含此前学习时涉及的spring-webmvc
、jackson-databind
等依赖项。
在具体操作方面,并不需要追加添加这个依赖项,只需要将spring-boot-starter
改为spring-boot-starter-web
即可,并且,在spring-boot-starter-web
中也包含了spring-boot-starter
,所以,对此项目原本的依赖也不产生影响。
在cn.tedu.boot.demo
下创建controller
子包,并在其下创建AdminController
类,作为处理“管理员”数据相关请求的控制器类,并在这个类中处理“增加管理员”的请求:
package cn.tedu.boot.demo.controller;
import cn.tedu.boot.demo.dto.AdminAddNewDTO;
import cn.tedu.boot.demo.service.IAdminService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
private IAdminService adminService;
// http://localhost:8080/admin/add-new?username=admin001&password=1234&nickname=a001
@RequestMapping("/add-new")
public String addNew(AdminAddNewDTO adminAddNewDTO) {
adminService.addNew(adminAddNewDTO);
return "OK";
}
}
因为spring-boot-starter-web
中依赖了Tomcat,相当于每个Spring Boot工程都有一个内置的Tomcat,并且将Context Path配置为空字符串,所以在URL上并不需要添加其它路径,最后,启动项目时,就会自动打包部署此项目到内置的Tomcat上。
所以,执行BootDemoApplication
,打开浏览器,通过 http://localhost:8080/admin/add-new?username=admin001&password=1234&nickname=a001 即可增加管理员。
以上只是简单的实现了数据访问,还需要解决的问题有:
将此前学习Spring MVC时设计的JsonResult
复制到此项目的cn.tedu.boot.demo.web
包中,并且将处理请求的方法的返回值类型改为JsonResult
类型:
// http://localhost:8080/admin/add-new?username=admin001&password=1234&nickname=a001
@RequestMapping("/add-new")
public JsonResult<Void> addNew(AdminAddNewDTO adminAddNewDTO) {
adminService.addNew(adminAddNewDTO);
return JsonResult.ok();
}
完成后,重启项目,通过正确的参数即可成功增加管理员,并且可以看到响应的结果是JSON格式的数据,例如:
{"state":20000,"message":null,"data":null}
以上数据中,message
和data
都没有数据,是多余的!可以在application.properties
中添加配置,以去除JSON数据中为null
的部分:
spring.jackson.default-property-inclusion=non_null
重启服务后,响应的JSON数据中将不再包含为null
的部分!
目前,在业务逻辑层抛出了2种不同原因导致的异常,异常的类型是完全相同的,会导致处理异常时,无法判断是哪种情况导致的异常,所以,应该先改造异常类,在类中添加State
属性,并要求通过构造方法传入,则每个异常对象中都会包含异常的状态码和错误时的文本描述:
package cn.tedu.boot.demo.ex;
import cn.tedu.boot.demo.web.JsonResult;
public class ServiceException extends RuntimeException {
private JsonResult.State state;
public ServiceException() {
}
public ServiceException(JsonResult.State state, String message) {
super(message);
this.state = state;
}
public JsonResult.State getState() {
return state;
}
}
由于抛出异常时既包含了状态码,又包含了错误的描述文本,在JsonResult
中还可以添加一个更加便捷的静态方法:
public static JsonResult<Void> fail(ServiceException e) {
return fail(e.getState(), e.getMessage());
}
为了保证能够对当前已分析的2种错误进行区分,应该在State
枚举中添加对应的状态码:
public enum State {
OK(20000),
ERR_CONFLICT(40900),
ERR_INTERNAL_ERROR(50000);
Integer value;
State(Integer value) {
this.value = value;
}
public Integer getValue() {
return value;
}
}
经过以上调整,原本的业务逻辑层的实现类将会报告错误,需要在创建并抛出异常时,除了传入错误的描述文本,还需要传入状态码
@Override
public void addNew(AdminAddNewDTO adminAddNewDTO) {
// 忽略此次不需要调整的代码... ...
// 判断查询结果是否【不为null】
if (queryResult != null) {
// 是:表示用户名已经被占用,抛出ServiceException:增加管理员失败,用户名已经被占用
log.warn("增加管理员失败,用户名({})已经被占用!", username);
throw new ServiceException(JsonResult.State.ERR_CONFLICT, "增加管理员失败,用户名已经被占用!");
}
// 忽略此次不需要调整的代码... ...
// 判断返回值是否不为1
if (rows != 1) {
// 抛出ServiceException:服务器忙,请稍后再次尝试
log.warn("服务器忙,请稍后再次尝试!");
throw new ServiceException(JsonResult.State.ERR_INTERNAL_ERROR, "服务器忙,请稍后再次尝试!");
}
}
在cn.tedu.boot.demo.controller
包下创建handler
子包,并在其下创建GlobalExceptionHandler
统一处理异常的类,在类上添加@RestControllerAdvice
注解,并在类中处理异常。
@RestControllerAdvicd
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public JsonResult<Void> handleServiceException(ServiceException e) {
return JsonResult.fail(state, e.getMessage());
}
}