单体架构项目开发(插入数据为例)

目录

创建项目

开发mapper层的准备工作:

开发mapper层:数据持久访问层

创建pojo包:使得整个项目结构清晰

关于POJO的设计规范

关于Mybatis框架

Mybatis的用法

创建mapper包:在src/main/java的根包下,创建mapper包

单元测试!!!

Service:业务逻辑层

概念:

开发Service层的准备工作:

事务(Transaction):保证数据的安全性。

参数:

实现:增加品牌

异常:

业务逻辑层的开发:

单元测试:

Controller:控制器

概念:

关于Spring MVC

关于控制器的基本使用

关于处理请求的方法:

关于@RequestMapping注解

开发前的准备:

返回值类型:JsonResult

则在项目的根包下创建web.JsonResult类:

当前项目结构:

统一异常处理类:

关于统一处理异常:

Knife4j框架:

关于依赖的代码:(搭建项目时已添加可以忽略)

关于开启增强模式,在application.properties中添加:

当前项目application.properties文件结构:

关于配置类,在项目的根包下创建config.Knife4jConfiguration,代码如下:

当前新建的配置类结构:

Controller层代码开发:

在项目的根包下,创建`controller.BrandController`类,

当前项目结构:

测试:

项目启动

打开浏览器输入http://localhost:11573/doc.html

提交数据

业务逻辑层的判断:

 记录启动项目时报错代码:

报错截图:

解决办法:将SpringBoot版本改为2.5.9即可:



创建项目
 

  • File -> New ->Project

新开idea可以从idea界面中New Project中创建项目

点Next - >直接点Finish创建项目

这边以创建csdn-demo项目为例:

pom.xml导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- 模型版本,是固定的 -->
    <modelVersion>4.0.0</modelVersion>
    <!-- 当前以父项目:Spring Boot v2.5.9为例 -->
    <!-- 父项目:Spring Boot v2.5.9  2.6.0之前版本才支持lombok -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!-- 当前项目的信息 -->
    <groupId>com.example</groupId>
    <artifactId>csdn-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>csdn-demo</name>
    <description>Demo project for Spring Boot</description>
    
    <!-- 属性(含自定义变量) -->
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <!-- 当前项目的依赖项 -->
    <!-- scope属性为test:表示此依赖仅用于测试,
  此依赖项将不可用于src/main/java下的代码,也不会参与编译、打包 -->
    <!-- scope属性为runtime:表示开发过程中并不需要此依赖项,
  但是运行时是必须的 -->
    <!-- scope属性为provided:表示在执行程序时,
  需要执行环境来保证此依赖项是存在的 -->
    <dependencies>
        <!-- Spring Boot对Spring MVC的支持,
      包含Spring Boot的基础依赖项 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Knife4j Spring Boot:在线API -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.9</version>
        </dependency>
        
        <!-- Mybatis整合Spring Boot的依赖项 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        
        <!-- MySQL的依赖项 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Lombok的依赖项,主要用于简化实体类的编写 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- Spring Boot测试的依赖项 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

<!-- Spring Boot Validation,用于检查请求参数的格式基本有效性 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

    </dependencies>
</project>

开发mapper层的准备工作:

  1. application中添加连接数据库的语句,不然会报错:
# 服务端口
server.port=11573

#连接数据库的相关配置  这边以csdn_demo数据库为例
spring.datasource.url=jdbc:mysql://localhost:3306/csdn_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 日志的显示级别
logging.level.com.example.csdndemo=trace

  1. 创建数据库:

打开开始菜单->

M选项中选择MariDB ->

单击MySQL进入dos命令窗口->

输入密码(个人安装MySQL或者MarDB的时候输入的密码):

//创建数据库的sql语句
create database csdn_demo ;

//删除数据库
drop database csdn_demo;
  1. 使用MariDB自带的可视化工具进行创建数据表

双击打开->


选择新建(名称:个人起名)->

单击选择创建好的数据库->

单击查询这里输入创建数据表的sql语句->

-- 品牌表:创建数据表
drop table if exists pms_brand;
create table pms_brand
(
    id                     bigint unsigned auto_increment comment '记录id',
    name                   varchar(50)      default null comment '品牌名称',
    pinyin                 varchar(50)      default null comment '品牌名称的拼音',
    logo                   varchar(255)     default null comment '品牌logo的URL',
    description            varchar(255)     default null comment '品牌简介',
    keywords               varchar(255)     default null comment '关键词列表,各关键词使用英文的逗号分隔',
    sort                   tinyint unsigned default 0 comment '自定义排序序号',
    sales                  int unsigned     default 0 comment '销量(冗余)',
    product_count          int unsigned     default 0 comment '商品种类数量总和(冗余)',
    comment_count          int unsigned     default 0 comment '买家评论数量总和(冗余)',
    positive_comment_count int unsigned     default 0 comment '买家好评数量总和(冗余)',
    enable                 tinyint unsigned default 0 comment '是否启用,1=启用,0=未启用',
    gmt_create             datetime         default null comment '数据创建时间',
    gmt_modified           datetime         default null comment '数据最后修改时间',
    primary key (id)
) comment '品牌' charset utf8mb4;

-- 品牌表:为品牌名称字段添加索引
create index idx_brand_name on pms_brand (name);

-- 品牌表:插入测试数据
insert into pms_brand (name, pinyin, description, keywords, enable)
values ('华为', 'huawei', '华为专注网络设备三十年', '华为,huawei,mate,magicbook', 1),
       ('小米', 'xiaomi', '小米,为发烧而生', '小米,xiaomi,发烧', 1),
       ('苹果', 'pingguo', '苹果,全球知名品牌', '苹果,apple,pingguo,iphone,mac', 1);

单击运行符号:到这里数据库的数据就创建出来了->

查看数据库表:单击鼠标右键刷新数据库->选择创建好的数据表->

单击页面数据即可查看!

开发mapper层:数据持久访问层

创建实体类

关于数据表中的字段的数据类型,与Java类中的属性的数据类型的对应关系:

数据表的字段的数据类型

Java类中的属性的数据类型

tinyint / smallint / int

Integer

bigint

Long

char/ varchar/ text系列

String

date_time

LocalDateTime(Java 8)

decimal

BigDecimal(Java 8)

创建pojo包:使得整个项目结构清晰

关于POJO的设计规范

  • 具有无参数的构造方法;
  • 属性均声明为private的;
  • 生成所有属性对应的规范的Setter和Getter;
  • 生成规范的hashCode()equals()方法;
    • 如果2个对象的hashCode()值相同,则必须保证它们equals()对比的结果为true;如果2个对象的hashCode()值不同,则必须保证它们equals()对比的结果为false
    • 通常,可以使用专业的开发工具生成这2个方法,不必关心这个方法的方法体代码。
  • 【建议,不强制要求】生成(重写)toString()方法;
  • 实现Serializable接口。

使用Lombok可以简化POJO类的编写,在使用之前,需要在项目中添加依赖:

(因创建的时候就有该依赖项了,可不用重复添加)

<!-- Lombok的依赖项,主要用于简化实体类的编写 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
</dependency>

当添加以上依赖项后,在各POJO类上,只需要添加`@Data`注解,即可使得Lombok框架在编译期生成各属性对应的Setter & Getter、`hashCode()`与`equals()`、`toString()`方法。

注意:使用Lombok时,应该(强烈推荐)在开发工具中安装Lombok插件(在IntelliJ IDEA中,点击`File` > `Settings`,在`Plugins`中搜索`Lombok`并安装),如果未安装,在调用由Lombok生成的方法,或使用相关变量时会提示错误,但不影响运行!

在src/main/java的根包下,创建pojo.entity子包

1、创建entity实体类:Brand类

类中的属性:于数据库一一对应的属性

/**
 * 品牌
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
@Data
public class Brand implements Serializable {

    /**
     * 记录id
     */
    private Long id;

    /**
     * 品牌名称
     */
    private String name;

    /**
     * 品牌名称的拼音
     */
    private String pinyin;

    /**
     * 品牌logo的URL
     */
    private String logo;

    /**
     * 品牌简介
     */
    private String description;

    /**
     * 关键词列表,各关键词使用英文的逗号分隔
     */
    private String keywords;

    /**
     * 自定义排序序号
     */
    private Integer sort;

    /**
     * 销量(冗余)
     */
    private Integer sales;

    /**
     * 商品种类数量总和(冗余)
     */
    private Integer productCount;

    /**
     * 买家评论数量总和(冗余)
     */
    private Integer commentCount;

    /**
     * 买家好评数量总和(冗余)
     */
    private Integer positiveCommentCount;

    /**
     * 是否启用,1=启用,0=未启用
     */
    private Integer enable;

    /**
     * 数据创建时间
     */
    private LocalDateTime gmtCreate;

    /**
     * 数据最后修改时间
     */
    private LocalDateTime gmtModified;

}

当前项目结构:


创建配置包config:在src/main/java下的根包下,创建config子包。

当前项目结构:

原因:

关于Mybatis框架

Mybatis框架的主要作用是简化数据库编程

Mybatis的用法

使用Mybatis主要需要:

  • 编写处理数据的抽象方法
    • 抽象方法必须声明在接口中,因为Mybatis框架的底层实现是基于接口的代理模式
    • 接口通常以Mapper作为名称的最后一个单词
  • 配置抽象方法对应的SQL语句

关于接口,必须使得Mybatis框架能够明确这些Mapper接口的位置,或者说,使得Mybatis知道有哪些Mapper接口,可以采取的做法有(二选一):

  • 【不推荐】在每一个Mapper接口上添加@Mapper注解
  • 【推荐】在配置类上添加@MapperScan注解,并在此注解中配置参数,参数值就是Mapper接口所在的根包,并且,确保各Mapper接口在此包下
    • 配置类:在项目的根包下(包含根包下的子孙包下),添加了@Configuration注解的类,就是配置类

1、创建mapper配置类:MybatisConfiguration类,在此类上通过注解配置Mapper接口的根包:

/**
 * Mybatis的配置类
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
@Slf4j
@Configuration
@MapperScan("com.example.csdndemo.mapper")
public class MybatisConfiguration {

    public MybatisConfiguration() {
        log.info("加载配置类:MybatisConfiguration");
    }
    
}

当前项目结构:

创建mapper包:在src/main/java的根包下,创建mapper包

当前项目结构:

创建BrandMapper接口类:

/**
 * 处理品牌数据的Mapper接口
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
@Repository
public interface BrandMapper {
}


当前项目结构:

1、接口中编写抽象方法:

/**
 * 处理品牌数据的Mapper接口
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
@Repository
public interface BrandMapper {

    /**
     * 插入品牌数据
     *
     * @param brand 品牌数据的对象
     * @return 受影响的行数
     */
    int insert(Brand brand);

/**
     * 根据品牌名称统计数据的数量
     *
     * @param name 品牌名称
     * @return 匹配名称的品牌数据的数量
     */
    //业务逻辑层需要用
    int countByName(String name);


    
}

关于抽象方法的声明原则:
- 返回值类型:如果要执行的SQL是增、删、改类型的,推荐使用`int`作为返回值类型,表示“受影响的行数”,其实,也可以使用`void`,并不推荐这样使用;
- 方法名称:自定义,但不要重载

啊里巴巴Java开发手册:
【参考】
获取单个对象的方法用 get 做前缀
获取多个对象的方法用 list 做前缀
获取统计值的方法用 count 做前缀
插入的方法用 save/insert 做前缀
删除的方法用 remove/delete 做前缀
修改的方法用 update 做前缀

- 参数列表:如果需要执行的SQL语句有多个参数,并且具有相关性,则应该将这些参数进行封装,并使用封装的类型作为抽象方法的参数

关于配置抽象方法对应的SQL语句编写,可以(二选一):

- 【不推荐】使用`@Insert`等注解配置SQL语句,并使用相关注解(例如`@Result`等)完成相关配置
- 【推荐】使用专门的XML文件配置SQL语句及相关配置
- SQL语句更加直观,易于阅读
- 相关配置更加直观,易于复用
- 易于实现与DBA(Database Administrator)协同工作

关于配置SQL语句的XML文件:

src/main/resources下创建mapper包下
- 根标签必须是<mapper>(xml(根标签有且仅有一个)
- 必须配置`<mapper>`标签的`namespace`属性,此属性的值是对应的Mapper接口的全限定名
- 在`<mapper>`标签的子级,使用`<insert>` / `<delete>` / `<update>` / `<select>`标签配置SQL语句
- 关于`<insert>`等标签,都必须配置`id`属性,取值为对应的抽象方法的名称(不包括抽象方法的签名的其它部分,例如,不需要括号等)
- 在`<insert>`等标签的内部,编写SQL语句,

注意:在`<insert>`标签的内容不要写任何注释,因为写在此处的注释都会被视为SQL语句的一部分

BrandMapper.xml可以从这里获取:

链接:https://pan.baidu.com/s/1FKBKILi478Z5uzAq8BhKfg
提取码:vec5

复制此文件进行编写xml文件:

<?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">
<!--namespace 对应的值是项目中的接口全限定名-->
<mapper namespace="com.example.csdndemo.mapper.BrandMapper">

  <!-- int insert(Brand brand); -->
  <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO pms_brand (
    name, pinyin, logo, description, keywords,
    sort, sales, product_count, comment_count, positive_comment_count,
    enable
    ) VALUES (
    #{name}, #{pinyin}, #{logo}, #{description}, #{keywords},
    #{sort}, #{sales}, #{productCount}, #{commentCount}, #{positiveCommentCount},
    #{enable}
    )
  </insert>

 <!-- int countByName(String name); -->
    <select id="countByName" resultType="int">
        SELECT count(*) FROM pms_brand WHERE name=#{name}
    </select>

</mapper>

当前项目结构:

在`application.properties`中添加配置,以指定这些XML文件的位置:

# 配置SQL的XML文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml

单元测试!!!

测试:在src/test/java下的根包下,创建mapper子包,在此子包中创建BrandMapperTests测试类。

注意:所有的测试类必须在根包下!

注意:测试类的类名,不可以与被测试的类名/接口名相同!

编写测试方法:

@Slf4j
@SpringBootTest
public class BrandMapperTests {
    @Autowired
    BrandMapper mapper;

    @Test
    void testInsert() {
        Brand brand = new Brand();
        brand.setName("测试品牌123");

        log.debug("插入品牌之前,参数对象={}", brand);
        int rows = mapper.insert(brand);
        log.debug("插入品牌完成,受影响的行数={}", rows);
        log.debug("插入品牌之后,参数对象={}", brand);
    }

  @Test
    void testCountByName() {
        String name = "测试品牌123";
        int count = mapper.countByName(name);
        log.debug("根据品牌名称【{}】统计品牌数据的数量,结果={}", name, count);
    }


}

当前项目结构:

运行成功后就可以看到以下信息:

如根据以上操作不能运行成功或大量报错,可私信博主或者搜飘叶不知秋意关注公众号联系博主!!!!!
 

查看数据

Service:业务逻辑层

概念:

业务逻辑层,也称之为“业务层”(Service Layer),主要:设计业务流程,处理业务逻辑,以保证数据的完整性和安全性。

业务层应该由接口(基于接口的代理模式来实现事务管理的)和实现类这2种文件组成!

关于Service中的方法的定义:

  • 返回值类型:仅以操作成功为前提来设计
  • 方法名称:自定义
  • 参数列表:通常是控制器调用时传入,典型的参数就是客户端提交的请求参数
  • 异常:处理业务时可能的“失败”,通常,使用RuntimeException或其子孙类异常,所以,在声明业务方法时,并不需要显式的声明抛出异常

关于异常,如果使用现有的异常(例如NullPointerException等),可能会产生歧义,所以,通常会自定义异常,继承自RuntimeException

开发Service层的准备工作:

事务(Transaction):保证数据的安全性。

事务是数据库中的可以保证多个(至少2个)写操作(增、删、改)要么全部执行成功,要么全部执行失败的机制!

例子:

没有事务的情况下:
用户A -------------------------(转账)------------------->用户B
扣钱成功-----------(代码运行过程中出现错误)------------没有收到钱
(数据库用户金额减少操作)--------------------------(数据库用户金额修改操作)


有事务的情况下:
用户A -----------------(转账)---------------->用户B
1、扣钱成功-2、(代码运行过程中出现错误(抛出异常3、事务的回滚)))-没有收到钱
钱未扣成功-------------------------------------没有收到钱
(数据库用户金额减少操作)--------------------------(数据库用户金额修改操作)
//事务是数据库中的可以保证多个(至少2个)写操作(增、删、改)
//要么全部执行成功,要么全部执行失败的机制!

在基于Spring JDBC的项目中,使用@Transactional注解,即可使得注解的方法是事务性的。

关于@Transactional注解,可以添加在:

  • 接口上
    • 等效于在每个抽象方法上添加了此注解
  • 接口的抽象方法上
    • 仅作用于当前方法(重写的方法运行时)
  • 实现类上
    • 等效于在每个重写的接口的方法上添加了此注解
  • 实现类中重写的接口的方法上
    • 仅作用于当前方法

提示:此注解可以配置一些参数,如果同时在接口/类、接口的抽象方法/类重写的方法上添加此注解并配置了不同的参数值,则以方法上的配置为准。

注意:Spring JDBC是基于接口的代理模式来实现事务管理的!所以,如果在实现类中的自定义方法上添加@Transactional注解是错误的做法!仅当对应的方法是在业务接口中已经声明的,使用@Transactional才是正确的!

关于事务处理过程中的几个概念:

  • 开启事务:BEGIN
  • 提交事务:COMMIT
  • 回滚事务:ROLLBACK

在Spring JDBC的事务管理中,大致是:

开启事务
try {
	执行你的业务方法
	提交事务
} catch (RuntimeException e) {
	回滚事务
}

可以看到,Spring JDBC的事务管理中,默认将根据RuntimeException进行回滚,可以通过@Transactional注解的rollbackFor / rollbackForClassName这2个属性中的某1个进行修改,设置为对特定的异常进行回滚,还可以配置noRollbackFor / noRollbackForClassName这2个属性,设置对特定的异常不回滚。

【小结】关于事务:

  • 如果某个业务方法涉及超过1次的增、删、改操作,需要保证此业务方法是事务性的;
  • 推荐在业务的抽象方法上添加@Transactional注解即可保证此业务方法是事务性
    • 对于初学者,更推荐在业务接口上添加@Transactional,则此接口中所有抽象方法都是事务性,可能其中的某些抽象方法并不需要是事务性的,但是,这种做法比较稳妥,能避免遗漏导致的错误
  • 为了保证事务能够按照预期进行回滚,需要:
    • 业务层必须由接口和实现类组成
      • Spring JDBC是基于接口代理模式实现事务管理的,如果没有接口,则无法实现
    • 所有增、删、改操作完成后,应该及时获取返回结果,并对结果进行判断,如果结果不符合预期,应该抛出异常,且异常应该是RuntimeException或其子孙类异常
      • Spring JDBC在管理事务时,默认按照RuntimeException进行回滚

参数:

实现:增加品牌

在项目的根包下,创建`pojo.dto.BrandAddNewDTO`类,此类用于封装”增加品牌“时客户端需要提交的请求参数:

/**
 * 添加品牌的DTO类
 *
 * @author zhangxiansheng 
 * @version 0.0.1
 */
@Data
public class BrandAddNewDTO implements Serializable {

    /**
     * 品牌名称
     */
    private String name;

    /**
     * 品牌名称的拼音
     */
    private String pinyin;

    /**
     * 品牌logo的URL
     */
    private String logo;

    /**
     * 品牌简介
     */
    private String description;

    /**
     * 关键词列表,各关键词使用英文的逗号分隔
     */
    private String keywords;

    /**
     * 自定义排序序号
     */
    private Integer sort;

    /**
     * 是否启用,1=启用,0=未启用
     */
    private Integer enable;
}

当前项目结构:

异常:

如果在项目中只使用1种异常类型,不便于不区分同一个业务可能出现的多种“错误”,所以,应该在异常类型中添加某个属性,来区分多种“错误”!关于此属性,可以是intString等各种你认为合适的类型,但是,这些类型的取值范围(值的可能性)非常大,为了限制取值,可以使用枚举类型

例如:

public enum ServiceCode {
    ERR_INSERT(1), ERR_UPDATE, ERR_DELETE;
}


如果仅仅只是以上代码,当尝试输出某个枚举值,输出的结果就是以上名称,例如`ERR_INSERT`,不便于获取此值时编写条件判断相关的代码,通常,使用数值进行判断会更加方便,所以,可以为以上每个枚举值指定相关的数值,同时,需要添加枚举的构造方法,例如:

public enum ServiceCode {
    ERR_INSERT(1), 
    ERR_UPDATE(2), 
    ERR_DELETE(3);
    
    ServiceCode(int value) {}
}


为了保证各个值能够被使用,还需要添加全局属性,用于保存通过构造方法传入的值,并提供相应获取值的方法,使得后续得到得这些数值:

public enum ServiceCode {

    ERR_CONFLICT(1),
    ERR_INSERT(2),
    ERR_DELETE(3),
    ERR_UPDATE(4);

    private int value;

    ServiceCode(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "" + value;
    }

}

另外,各枚举值对应的数值编号应该是有一定规律的,而不是盲目的顺序编号,所以,应该自行设计一套编码规则,或者,如果没有比较成熟的编码规则,可大致参考已有的某套规则,例如参考HTTP响应码:

提示:以上类型创建在项目的根包下的`ex.ServiceCode`

/**
 * 业务状态码的枚举
 */
public enum ServiceCode {

    OK(20000),
    ERR_NOT_FOUND(40400),
    ERR_CONFLICT(40900),
    ERR_INSERT(50000),
    ERR_DELETE(50100),
    ERR_UPDATE(50200);

    private int value;

    ServiceCode(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "" + value;
    }

}


然后,在项目的根包下的`ex`包下创建`ServiceException`

/**
 * 业务异常
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
public class ServiceException  extends RuntimeException{

    private ServiceCode serviceCode;

    public ServiceException(ServiceCode serviceCode, String message) {
        super(message);
        this.serviceCode = serviceCode;
    }

    public ServiceCode getServiceCode() {
        return serviceCode;
    }

}

当前项目结构:

业务逻辑层的开发:

处理“添加品牌”的业务

在根包下创建`service`子包,并在此子包下创建`IBrandService`接口:

当前项目结构:

public interface IBrandService {
    // 添加品牌
    void addNew(BrandAddNewDTO brandAddNewDTO);
}

然后,在`service`包下再创建`impl`子包,并在此子包下创建BrandServiceImpl类,实现以上接口,并在类上添加@Service注解:

当前项目结构:

@Slf4j
@Service
public class BrandServiceImpl implements IBrandService {
     @Autowired
    private BrandMapper brandMapper;


    public BrandServiceImpl() {
        log.info("创建业务对象:BrandServiceImpl");
    }

    @Override
    public void addNew(BrandAddNewDTO brandAddNewDTO) {
        log.debug("开始处理【添加品牌】的业务,参数:{}", brandAddNewDTO);

        // 检查品牌名称是否已经被占用
        String name = brandAddNewDTO.getName();
        int countByName = brandMapper.countByName(name);
        log.debug("尝试添加的品牌名称是:{},在数据库中此名称的品牌数量为:{}", name, countByName);
        if (countByName > 0) {
            String message = "添加品牌失败,品牌名称【" + brandAddNewDTO.getName() + "】已经被占用!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        // 创建品牌对象,用于插入到数据表
        Brand brand = new Brand();
     //同名属性的两个类对象赋值
        BeanUtils.copyProperties(brandAddNewDTO, brand);
        brand.setSales(0);
        brand.setProductCount(0);
        brand.setCommentCount(0);
        brand.setPositiveCommentCount(0);

        // 插入数据
        log.debug("即将向数据库中插入数据:{}", brand);
        int rows = brandMapper.insert(brand);
        if (rows != 1) {
            String message = "添加品牌失败,服务器忙,请稍后再次尝试!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }
    }
}

到此业务逻辑层开发完毕!!!!!

单元测试:

在test的根包下创建service包创建BrandServiceTests测试类

@Slf4j
@SpringBootTest
public class BrandServiceTests {
    @Autowired
    IBrandService service;
    @Test
    void testAddNew() {
        BrandAddNewDTO brandAddNewDTO = new BrandAddNewDTO();
        brandAddNewDTO.setName("海尔");
        try {
            service.addNew(brandAddNewDTO);
            log.debug("添加品牌成功!");
        } catch (ServiceException e) {
            log.debug("serviceCode : {}", e.getServiceCode());
            log.debug("message : {}", e.getMessage());
        }
    }
}

Controller:控制器

概念:

关于Spring MVC

Spring MVC框架主要解决了接收请求、响应结果的相关问题。
在Spring Boot项目中,当需要使用Spring MVC框架及相关的功能时,应该添加`spring-boot-starter-web`依赖项。由于`spring-boot-starter-web`是基于`spring-boot-starter`依赖项的,所以,`spring-boot-starter-web`包含了`spring-boot-starter`,在实际编码时,只需要将`spring-boot-starter`改为`spring-boot-starter-web`即可。
当添加了`spring-boot-starter-web`依赖项后,当启动项目时,默认情况下,就会自动将当前项目编译、打包并部署到内置的Tomcat中,并启动Tomcat,默认占用`8080`端口。

关于控制器的基本使用

  • 仅当添加了@Controller注解后,此类才算是”控制器类“(才可以接收请求、响应结果)
  • 在方法上使用@RequestMapping可以配置某个路径,后续,客户端可以向此路径发起请求,则此方法将自动执行,所以,此方法可称之为”处理请求的方法“
  • 在默认情况下,处理请求的方法的返回值是String时,返回的结果表示某个视图的名称
  • 在方法上添加@ResponseBody注解,将表示此方法是”响应正文“的,方法的返回结果将直接响应到客户端
  • @ResponseBody注解还可以添加在控制器类上,将表示此控制器类中所有处理请求的方法都是响应正文的
  • @RestController将同时具有@Controller和@ResponseBody的效果,这一点,可以从@RestController中看到

关于处理请求的方法:

  • 访问权限:应该是public
  • 返回值类型:JsonResult
  • 方法名称:自定义
  • 参数列表:按需设计,可以直接将所需的请求参数声明为方法的参数,或者,将多个请求参数封装到自定义类型中,并使用自定义类型作为处理请求的方法的参数,各参数可以按照期望的数据类型进行设计,如果有多个参数,不区分先后顺序

关于接收请求参数:

  • 如果客户端正确的按照名称提交了请求参数,则服务器端可以正常接收到,如果不是字符串类型,会尝试自动的转换数据类型,如果转换失败,将出现错误,且响应400
    • http://localhost:9080/add-new?name=小米&pinyin=xiaomi
  • 如果客户端提交了对应的请求参数名称,却没有提交值,则服务器端默认视为空字符串,如果请求参数是其它类型(例如Integer),框架会放弃转换类型,仍保持为null
    • http://localhost:9080/add-new?name=&pinyin=
  • 如果客户端没有提交对应名称的请求参数,则服务器端接收到的为null
    • http://localhost:9080/add-new

关于@RequestMapping注解

在Spring MVC框架中,@RequestMapping注解的主要作用是:绑定“请求路径”与“处理请求的方法”的映射关系。

在Spring MVC框架中,还定义了相关注解,以简化限制请求方式的配置:

  • @GetMapping
  • @PostMapping
  • PutMapping
  • DeleteMapping
  • PatchMapping

小结:

  • 推荐在每个控制器类上使用@RequestMapping配置请求路径前缀
  • 推荐在每个处理请求的方法上使用@GetMapping / @PostMapping配置请求路径
  • 增删改使用@PostMapping/查用@GetMapping

开发前的准备:

返回值类型:JsonResult

控制器处理完请求后,向客户端进行响应时,推荐使用JSON格式的响应数据,并且,此JSON格式的数据中至少应该包括:

  • 业务状态码
  • 提示信息

在Spring MVC框架中,当需要向客户端响应JSON格式的结果时,需要:

  • 当前处理请求的方法必须是“响应正文”的
    • 在处理请求的方法或控制器类上使用@ResponseBody,或控制器类上使用了@RestController,就是“响应正文”的
  • 在项目中添加jackson-databind的依赖
    • 包含在spring-boot-starter-web依赖项中
  • 开启注解驱动
    • 使用注解模式的Spring MVC项目(包括Spring Boot)均默认开启
  • 使用自定义的类型作为处理请求的方法的返回值类型,并且,此类中应该包含响应的JSON中的各属性

则在项目的根包下创建web.JsonResult类:

package com.example.csdndemo.web;

import com.example.csdndemo.ex.ServiceCode;
import com.example.csdndemo.ex.ServiceException;
import lombok.Data;

import java.io.Serializable;

@Data
public class JsonResult<T> implements Serializable {

    /**
     * 业务状态码
     */
    private Integer state;
    /**
     * 错误时的提示消息
     */
    private String message;
    /**
     * 成功时响应的数据
     */
    private T data;

    public JsonResult() {
    }

    private JsonResult(Integer state, String message, T data) {
        this.state = state;
        this.message = message;
        this.data = data;
    }

    public static JsonResult<Void> ok() {
        return ok(null);
    }

    public static <T> JsonResult<T> ok(T data) {
        return new JsonResult(ServiceCode.OK.getValue(), null, data);
    }

    public static JsonResult<Void> fail(ServiceException e) {
        return fail(e.getServiceCode().getValue(), e.getMessage());
    }

    public static JsonResult<Void> fail(Integer state, String message) {
        return new JsonResult(state, message, null);
    }

}

当前项目结构:

统一异常处理类:

在使用Spring MVC框架时,控制器(Controller)可以不处理异常(如果执行过程中出现异常,则自动抛出),框架提供了统一处理异常的机制。

关于统一处理异常:

统一处理异常的代码应该编写在专门的类中,并且,在此类上添加@ControllerAdvice / @RestControllerAdvice注解

    • @ControllerAdvice / @RestControllerAdvice注解的类中的特定方法将作用于每一次处理请求的过程中
    • 其实,统一处理异常的代码可以写在某个控制器中,但是,将只能作用于此控制器中各处理请求的方法,无法作用于其它控制器中处理请求的方法

在类中自定义方法来处理异常

    • 注解:@ExceptionHandler
    • 访问权限:应该public
    • 返回值类型:设计原则可参考控制器中处理请求的方法
    • 方法名称:自定义
    • 参数列表:必须包含异常类型的参数,且此参数就是Spring MVC框架调用控制器方法时捕获的异常,另外,可按需添加HttpServerRequest、HttpServletResponse等少量限定类型的参数

例如:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler
    public JsonResult<Void> handleServiceException(ServiceException e) {
        log.debug("处理ServiceException,serviceCode={},message={}", 
                  e.getServiceCode(), e.getMessage());
        return JsonResult.fail(e);
    }

}

在以上方法中,方法的参数是ServiceException,则表示此方法就是用于处理ServiceException及其子孙类异常的,不可以处理其它种类的异常。

在同一个项目中,可以有多个以上处理异常的类,或同一个处理异常的类中可以有多个处理异常的方法,只要这些方法处理的异常不冲突即可!并且,这些方法处理的异常允许存在父子级继承关系,例如某个方法处理ServiceException,另一个方法处理RuntimeException,当出现ServiceException,仍会按照处理ServiceException的方法进行处理!

强烈建议在每个项目中都添加一个处理Throwable的方法,避免项目出现500错误!例如:

在项目根包下的ex包下在创一个handler包中创建GlobalExceptionHandler类

/**
 * 全局异常处理器
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    public GlobalExceptionHandler() {
        log.debug("创建全局异常处理器:GlobalExceptionHandler");
    }
    @ExceptionHandler
    public JsonResult<Void> handleServiceException(ServiceException e) {
        log.debug("处理ServiceException,serviceCode={},message={}", e.getServiceCode(), e.getMessage());
        return JsonResult.fail(e);
    }
    @ExceptionHandler
    public JsonResult<Void> handleThrowable(Throwable e) {
        log.debug("处理Throwable");
        e.printStackTrace();

        Integer serviceCode = 99999;
        String message = "程序运行过程中出现未知错误,请联系系统管理员!";
        return JsonResult.fail(serviceCode, message);
    }
}

当前项目结构:

注意:以上处理Throwable的方法,并不是真正意义的“处理”了异常,在此方法中,应该通过日志输出异常的详情信息,并且,在后续出现相关异常时,在此类中补充针对这些异常的精准处理!

另外,在@ExceptionHandler中,可以配置异常类型的参数,此参数是异常类型的数组,用于指定需要处理哪些种类的异常,但是,通常并不需要进行此项配置,因为方法的参数就可以直接表示处理哪种异常!此注解参数大多应用于“多种不相关的异常使用同一种处理方式”的情景!

Knife4j框架:

Knife4j是一款基于Swagger 2的在线API文档框架。

在Spring Boot中,使用此框架时,需要:

  • 添加依赖
  • 在配置文件(application.properties)中开启增强模式
  • 编写配置类(代码相对固定,建议CV)

关于依赖的代码:(搭建项目时已添加可以忽略)

<!-- Knife4j Spring Boot:在线API -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.9</version>
</dependency>

关于开启增强模式,在application.properties中添加:

# 开启Knife4j的增强模式
knife4j.enable=true

当前项目application.properties文件结构:

关于配置类,在项目的根包下创建config.Knife4jConfiguration,代码如下:

注意:请检查basePackage属性的值!

必须是:指定项目的Controller包路径

/**
 * Knife4j配置类
 *
 * @author zhangxiansheng
 * @version 0.0.1
 */
@Slf4j
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
    /**
     * 【重要】指定Controller包路径
     */
    private String basePackage = "com.example.csdndemo.controller";
    /**
     * 分组名称
     */
    private String groupName = "product";
    /**
     * 主机名
     */
    private String host = "http://java.tedu.cn";
    /**
     * 标题
     */
    private String title = "酷鲨商城在线API文档--商品管理";
    /**
     * 简介
     */
    private String description = "酷鲨商城在线API文档--商品管理";
    /**
     * 服务条款URL
     */
    private String termsOfServiceUrl = "http://www.apache.org/licenses/LICENSE-2.0";
    /**
     * 联系人
     */
    private String contactName = "Java教学研发部";
    /**
     * 联系网址
     */
    private String contactUrl = "http://java.tedu.cn";
    /**
     * 联系邮箱
     */
    private String contactEmail = "java@tedu.cn";
    /**
     * 版本号
     */
    private String version = "1.0.0";
    @Autowired
    private OpenApiExtensionResolver openApiExtensionResolver;

    public Knife4jConfiguration() {
        log.debug("加载配置类:Knife4jConfiguration");
    }
    @Bean
    public Docket docket() {
        String groupName = "1.0.0";
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .host(host)
                .apiInfo(apiInfo())
                .groupName(groupName)
                .select()
                .apis(RequestHandlerSelectors.basePackage(basePackage))
                .paths(PathSelectors.any())
                .build()
                .extensions(openApiExtensionResolver.buildExtensions(groupName));
        return docket;
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(title)
                .description(description)
                .termsOfServiceUrl(termsOfServiceUrl)
                .contact(new Contact(contactName, contactUrl, contactEmail))
                .version(version)
                .build();
    }
}

当前新建的配置类结构:

注意:以上代码适用于Spring Boot 2.6以下(不含2.6)版本!

完成后,重启项目,打开浏览器,通过http://localhost:11573/doc.html 即可访问Knife4j的API文档。

关于Knife4j框架,还提供了一系列的注解,便于实现API文档的显示,包括:

  • @Api:添加在控制器类上,配置其tags属性,用于指定模块名称,在指定的模块名称,可以使用数字编号作为名称的前缀,则多个管理模块将按照编号顺序来显示,例如:
//
@Api(tags = "03. 品牌管理模块")
public class AlbumController {

    @GetMapping("/test")
    public void test() {}

}
  • @ApiOperation:添加在控制器类中处理请求的方法上,配置其value属性,用于指定业务接口名称,例如
@ApiOperation("删除品牌")
public String delete(Long id) {
}

Controller层代码开发:

在项目的根包下,创建`controller.BrandController`类,

  • 在类上添加@RestController和@RequestMapping
  • 在方法上添加@PostMapping
@RestController
@RequestMapping("/brands")
@Api(tags = "品牌模块")
@Slf4j
public class BrandController {

    @Autowired
    private IBrandService brandService;

    @PostMapping("/add-new")
    @ApiOperation("新增品牌")
    public JsonResult addNew(BrandAddNewDTO brandAddDTO){
        log.debug("传输的参数:{}",brandAddDTO);
        brandService.addNew(brandAddDTO);
        return JsonResult.ok("新增商品成功!!!!!");
    }

}

当前项目结构:

dto类中的必须传的属性可以添加:

 /**
     * 品牌名称
     */
//新增api注解
    @ApiModelProperty(value = "品牌名称", required = true,example = "测试123")
    private String name;

    /**
     * 品牌名称的拼音
     */
//新增api注解
    @ApiModelProperty(value = "品牌名称的拼音", required = true,example = "ceshi123")
    private String pinyin;

以上Controller层就已经开发完毕了!!!

测试:

项目启动

打开浏览器输入http://localhost:11573/doc.html

提交数据

业务逻辑层的判断:

记录

关注博主订阅专栏学习Node不迷路!

如果本篇文章对你有所帮助,还请客官一件四连!❤️

启动项目时报错代码:

org.springframework.context.ApplicationContextException:
 Failed to start bean 'documentationPluginsBootstrapper';
 nested exception is java.lang.NullPointerException

报错截图:


上csdn搜得出结果为:版本冲突问题 最新搭建的SpringBoot版本为2.7.4

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.7.4</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>


Knife4j使用注意:以上代码适用于Spring Boot 2.6以下(不含2.6)版本!


解决办法:
将SpringBoot版本改为2.5.9即可:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.7.4</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程源三zhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值