第四阶段product笔记

文章目录

  • 第一天
    • 关于此项目
    • 项目的开发流程
    • 关于数据库与数据表
    • 具体开发顺序
    • 实现数据访问层的开发
  • 第二天
    • LOMBOK
    • Mybatis框架
      • Mybatis框架的主要作用
      • 使用Mybatis的前期准备
      • 使用Mybatis插入数据
    • 插入数据时获取自动编号的id
      • 使用Mybatis删除数据
    • Spring Boot框架
      • 关于启动类
      • 关于配置文件
    • 课后作业
  • 第三天
    • MyBatis的动态SQL--foreach
    • 使用Mybaits修改数据
    • 使用Mybatis查询--统计
    • 使用Mybatis查询--根据id查询
    • 使用Mybatis查询数据列表
    • 作业
    • 1. 关于utf8mb4
    • 2. Mybatis中的`#{}`占位符
    • 3. 其它问题
  • 第四天
    • 关于Mybatis小结
    • 使用SLF4j日志
    • Spring MVC框架
      • Spring MVC框架的作用
      • 基础配置
      • 使用Spring MVC框架接收请求
      • 响应正文
      • @RequestMapping
      • 关于注解的源代码
      • 接收请求参数
    • RESTful风格
      • 作业
  • 第五天
    • 关于RESTful(续)
    • 关于MVC
    • 开发Service
    • 关于业务异常
    • Spring MVC统一处理异常
    • 使用Spring Validation检查请求参数
    • 周末作业
  • 第六天
    • Knife4j框架
    • 关于响应结果
  • 第七天
    • 前后端分离的跨域访问
    • 关于Spring MVC中的@RequestBody
    • 前端框架:qs
  • 第九天
    • 1. Mybatis的`#{}`与`${}`格式的占位符
    • 2. Mybatis的缓存机制
  • 第十四天
    • 单点登录
    • 在Product项目中实现授权访问
  • 作业
      • 显示类别列表
      • 添加类别
      • 删除类别
      • 启用与禁用类别
      • 创建属性模板
      • 显示属性模板
      • 删除属性模板
      • 关联类别与属性模板
      • 添加属性
      • 根据属性模板id查询属性列表
      • 删除属性
      • 显示品牌列表
      • 关联类别与品牌
      • 删除品牌
  • 第十六天
    • Redis
      • 关于Redis
      • Redis的简单操作
      • 在Spring Boot项目中读写Redis
  • 1. Spring MVC拦截器
      • 1.1. 关于拦截器
      • 1.2. 使用拦截器
      • 1.3. 配置拦截路径
      • 1.4. 拦截器与过滤器的区别
  • 第十八天
    • 在项目中应用Redis
    • 关于缓存预热
    • 关于更新缓存
    • Spring AOP
  • 第十九天

第一天

关于此项目

此项目是《酷鲨商城》的服务器端管理商品相关数据的项目(至于管理员、用户、订单等,并不在此项目中开发)。

此项目是使用Spring Boot作为基础框架的项目,在后续的使用过程中,将使用到主流的SSM(Spring / Spring MVC / Mybatis)、Spring Security、Spring Validation等框架。

早期流行的是SSH:Spring / Struts 2 / Hibernate

创建项目的参数:

  • Group:cn.tedu
  • Artifact:csmall-product
  • Package Name:cn.tedu.csmall.product
  • Java版本:1.8
  • Spring Boot父项目版本:2.5.9
  • 创建过程中勾选的依赖:无

创建项目完成后,pom.xml文件的内容为:


<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.0modelVersion>
    
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.5.9version>
        <relativePath/> 
    parent>

    
    <groupId>cn.tedugroupId>
    <artifactId>csmall-productartifactId>
    <version>0.0.1version>
    <name>jsd2204-csmall-product-teachername>
    <description>这是酷鲨商城的商品管理服务的项目(学习中……)description>

    
    <properties>
        
        <java.version>1.8java.version>
    properties>

    
    
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

project>

项目的开发流程

推荐学习《软件工程》。

大概的指导思想,开发项目的核心流程:需求分析、可行性分析、总体设计、详细设计等。

在开发实践中,每个用户感受到的功能(例如登录、注册等)都是由项目中的多个组件(例如Controller、Mapper等)来共同完成的,通常,在开发时,首先确定需要开发的数据类型有哪些,例如用户、类别、购物车、订单等,并且,从基础类型开始制定开发顺序,例如需要先开发用户类型的数据的相关功能,才能开发订单数据的相关功能,然后,对于互不直接相关的数据类型,一般先开发简单的,再开发难度略大的,接下来,就应该规划每种数据类型需要实现哪些业务(用户能感受到的功能),以用户数据为例,需要开发的业务可能有:登录、注册、修改密码、查看用户列表、禁用某用户、删除用户……并规划这些业务的开发先后顺序,通常,应该大致遵循增、查、删、改的顺序,例如需要先开发注册,再开发登录……然后,在每个业务的开发过程中,应该先开发数据访问功能(增删改查)、业务逻辑层、控制器、页面。

关于数据库与数据表

创建数据库mall_pms(Product Management System):

CREATE DATABASE mall_pms;

然后,在IntelliJ IDEA中配置Database面板,并在Console中执行以下SQL:

-- 数据库:mall_pms

-- 相册表:创建数据表
drop table if exists pms_album;
create table pms_album
(
    id           bigint unsigned auto_increment comment '记录id',
    name         varchar(50)      default null comment '相册名称',
    description  varchar(255)     default null comment '相册简介',
    sort         tinyint unsigned default 0 comment '自定义排序序号',
    gmt_create   datetime         default null comment '数据创建时间',
    gmt_modified datetime         default null comment '数据最后修改时间',
    primary key (id)
) comment '相册' charset utf8mb4;

-- 相册表:为相册名称字段添加索引
create index idx_album_name on pms_album (name);

-- 图片表:创建数据表
drop table if exists pms_picture;
create table pms_picture
(
    id           bigint unsigned auto_increment comment '记录id',
    album_id     bigint unsigned   default null comment '相册id',
    url          varchar(255)      default null comment '图片url',
    description  varchar(255)      default null comment '图片简介',
    width        smallint unsigned default null comment '图片宽度,单位:px',
    height       smallint unsigned default null comment '图片高度,单位:px',
    is_cover     tinyint unsigned  default 0 comment '是否为封面图片,1=是,0=否',
    sort         tinyint unsigned  default 0 comment '自定义排序序号',
    gmt_create   datetime          default null comment '数据创建时间',
    gmt_modified datetime          default null comment '数据最后修改时间',
    primary key (id)
) comment '图片' charset utf8mb4;

-- 品牌表:创建数据表
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);

-- 类别表:创建数据表
drop table if exists pms_category;
create table pms_category
(
    id           bigint unsigned auto_increment comment '记录id',
    name         varchar(50)      default null comment '类别名称',
    parent_id    bigint unsigned  default 0 comment '父级类别id,如果无父级,则为0',
    depth        tinyint unsigned default 1 comment '深度,最顶级类别的深度为1,次级为2,以此类推',
    keywords     varchar(255)     default null comment '关键词列表,各关键词使用英文的逗号分隔',
    sort         tinyint unsigned default 0 comment '自定义排序序号',
    icon         varchar(255)     default null comment '图标图片的URL',
    enable       tinyint unsigned default 0 comment '是否启用,1=启用,0=未启用',
    is_parent    tinyint unsigned default 0 comment '是否为父级(是否包含子级),1=是父级,0=不是父级',
    is_display   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_category_name on pms_category (name);

-- 品牌类别关联表:创建数据表
drop table if exists pms_brand_category;
create table pms_brand_category
(
    id           bigint unsigned auto_increment comment '记录id',
    brand_id     bigint unsigned default null comment '品牌id',
    category_id  bigint unsigned default null comment '类别id',
    gmt_create   datetime        default null comment '数据创建时间',
    gmt_modified datetime        default null comment '数据最后修改时间',
    primary key (id)
) comment '品牌与类别关联' charset utf8mb4;

-- 属性表:创建数据表
drop table if exists pms_attribute;
create table pms_attribute
(
    id                 bigint unsigned auto_increment comment '记录id',
    template_id        bigint unsigned  default null comment '所属属性模版id',
    name               varchar(50)      default null comment '属性名称',
    description        varchar(255)     default null comment '简介(某些属性名称可能相同,通过简介补充描述)',
    type               tinyint unsigned default 0 comment '属性类型,1=销售属性,0=非销售属性',
    input_type         tinyint unsigned default 0 comment '输入类型,0=手动录入,1=单选,2=多选,3=单选(下拉列表),4=多选(下拉列表)',
    value_list         varchar(255)     default null comment '备选值列表',
    unit               varchar(50)      default null comment '计量单位',
    sort               tinyint unsigned default 0 comment '自定义排序序号',
    is_allow_customize 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;

-- 属性模版表:创建数据表
drop table if exists pms_attribute_template;
create table pms_attribute_template
(
    id           bigint unsigned auto_increment comment '记录id',
    name         varchar(50)      default null comment '属性模版名称',
    pinyin       varchar(50)      default null comment '属性模版名称的拼音',
    keywords     varchar(255)     default null comment '关键词列表,各关键词使用英文的逗号分隔',
    sort         tinyint unsigned default 0 comment '自定义排序序号',
    gmt_create   datetime         default null comment '数据创建时间',
    gmt_modified datetime         default null comment '数据最后修改时间',
    primary key (id)
) comment '属性模版' charset utf8mb4;

-- 属性模版表:为属性模版名称字段添加索引
create index idx_attribute_template_name on pms_attribute_template (name);

-- 类别与属性模版关联表:创建数据表
drop table if exists pms_category_attribute_template;
create table pms_category_attribute_template
(
    id                    bigint unsigned auto_increment comment '记录id',
    category_id           bigint unsigned default null comment '类别id',
    attribute_template_id bigint unsigned default null comment '属性模版id',
    gmt_create            datetime        default null comment '数据创建时间',
    gmt_modified          datetime        default null comment '数据最后修改时间',
    primary key (id)
) comment '类别与属性模版关联' charset utf8mb4;

-- SPU(Standard Product Unit)表:创建数据表
drop table if exists pms_spu;
create table pms_spu
(
    id                     bigint unsigned not null comment '记录id',
    name                   varchar(50)      default null comment 'SPU名称',
    type_number            varchar(50)      default null comment 'SPU编号',
    title                  varchar(255)     default null comment '标题',
    description            varchar(255)     default null comment '简介',
    list_price             decimal(10, 2)   default null comment '价格(显示在列表中)',
    stock                  int unsigned     default 0 comment '当前库存(冗余)',
    stock_threshold        int unsigned     default 0 comment '库存预警阈值(冗余)',
    unit                   varchar(50)      default null comment '计件单位',
    brand_id               bigint unsigned  default null comment '品牌id',
    brand_name             varchar(50)      default null comment '品牌名称(冗余)',
    category_id            bigint unsigned  default null comment '类别id',
    category_name          varchar(50)      default null comment '类别名称(冗余)',
    attribute_template_id  bigint unsigned  default null comment '属性模版id',
    album_id               bigint unsigned  default null comment '相册id',
    pictures               varchar(500)     default null comment '组图URLs,使用JSON数组表示',
    keywords               varchar(255)     default null comment '关键词列表,各关键词使用英文的逗号分隔',
    tags                   varchar(255)     default null comment '标签列表,各标签使用英文的逗号分隔,原则上最多3个',
    sales                  int unsigned     default 0 comment '销量(冗余)',
    comment_count          int unsigned     default 0 comment '买家评论数量总和(冗余)',
    positive_comment_count int unsigned     default 0 comment '买家好评数量总和(冗余)',
    sort                   tinyint unsigned default 0 comment '自定义排序序号',
    is_deleted             tinyint unsigned default 0 comment '是否标记为删除,1=已删除,0=未删除',
    is_published           tinyint unsigned default 0 comment '是否上架(发布),1=已上架,0=未上架(下架)',
    is_new_arrival         tinyint unsigned default 0 comment '是否新品,1=新品,0=非新品',
    is_recommend           tinyint unsigned default 0 comment '是否推荐,1=推荐,0=不推荐',
    is_checked             tinyint unsigned default 0 comment '是否已审核,1=已审核,0=未审核',
    check_user             varchar(50)      default null comment '审核人(冗余)',
    gmt_check              datetime         default null comment '审核通过时间(冗余)',
    gmt_create             datetime         default null comment '数据创建时间',
    gmt_modified           datetime         default null comment '数据最后修改时间',
    primary key (id)
) comment 'SPU(Standard Product Unit)' charset utf8mb4;

-- SPU详情表:创建数据表
drop table if exists pms_spu_detail;
create table pms_spu_detail
(
    id           bigint unsigned auto_increment comment '记录id',
    spu_id       bigint unsigned default null comment 'SPU id',
    detail       text            default null comment 'SPU详情,应该使用HTML富文本,通常内容是若干张图片',
    gmt_create   datetime        default null comment '数据创建时间',
    gmt_modified datetime        default null comment '数据最后修改时间',
    primary key (id)
) comment 'SPU详情' charset utf8mb4;

-- SKU(Stock Keeping Unit)表:创建数据表
drop table if exists pms_sku;
create table pms_sku
(
    id                     bigint unsigned not null comment '记录id',
    spu_id                 bigint unsigned  default null comment 'SPU id',
    title                  varchar(255)     default null comment '标题',
    bar_code               varchar(255)     default null comment '条型码',
    attribute_template_id  bigint unsigned  default null comment '属性模版id',
    specifications         varchar(2500)    default null comment '全部属性,使用JSON格式表示(冗余)',
    album_id               bigint unsigned  default null comment '相册id',
    pictures               varchar(500)     default null comment '组图URLs,使用JSON格式表示',
    price                  decimal(10, 2)   default null comment '单价',
    stock                  int unsigned     default 0 comment '当前库存',
    stock_threshold        int unsigned     default 0 comment '库存预警阈值',
    sales                  int unsigned     default 0 comment '销量(冗余)',
    comment_count          int unsigned     default 0 comment '买家评论数量总和(冗余)',
    positive_comment_count int unsigned     default 0 comment '买家好评数量总和(冗余)',
    sort                   tinyint unsigned default 0 comment '自定义排序序号',
    gmt_create             datetime         default null comment '数据创建时间',
    gmt_modified           datetime         default null comment '数据最后修改时间',
    primary key (id)
) comment 'SKU(Stock Keeping Unit)' charset utf8mb4;

-- SKU规格参数表(存储各SKU的属性与值,即规格参数):创建数据表
drop table if exists pms_sku_specification;
create table pms_sku_specification
(
    id              bigint unsigned auto_increment comment '记录id',
    sku_id          bigint unsigned  default null comment 'SKU id',
    attribute_id    bigint unsigned  default null comment '属性id',
    attribute_name  varchar(50)      default null comment '属性名称',
    attribute_value varchar(50)      default null comment '属性值',
    unit            varchar(10)      default null comment '自动补充的计量单位',
    sort            tinyint unsigned default 0 comment '自定义排序序号',
    gmt_create      datetime         default null comment '数据创建时间',
    gmt_modified    datetime         default null comment '数据最后修改时间',
    primary key (id)
) comment 'SKU数据' charset utf8mb4;

-- -------------------------- --
-- 以下是插入测试数据及一些测试访问 --
-- -------------------------- --

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

-- 类别表:插入测试数据
insert into pms_category (name, parent_id, depth, is_parent, keywords, enable, is_display)
values ('手机 / 运营商 / 数码', 0, 1, 1, null, 1, 1),
       ('手机通讯', 1, 2, 1, '手机,电话', 1, 1),
       ('智能手机', 2, 3, 0, null, 1, 1),
       ('非智能手机', 2, 3, 0, null, 1, 1),
       ('电脑 / 办公', 0, 1, 1, null, 1, 1),
       ('电脑整机', 5, 2, 1, '电脑,计算机,微机,服务器,工作站', 1, 1),
       ('电脑配件', 5, 2, 1, '配件,组装,CPU,内存,硬盘', 1, 1),
       ('笔记本', 6, 3, 0, '电脑,笔记本,微机,便携', 1, 1),
       ('台式机 / 一体机', 6, 3, 0, '台式机,一体机', 1, 1);

-- 品牌类别表:插入测试数据
insert into pms_brand_category (brand_id, category_id)
values (1, 3),
       (2, 3),
       (3, 3),
       (1, 8),
       (2, 8),
       (3, 8),
       (1, 9),
       (3, 9);

-- 关联测试查询:各品牌有哪些类别的产品
select pms_brand_category.id, pms_brand.name, pms_category.name
from pms_brand_category
         left join pms_brand
                   on pms_brand_category.brand_id = pms_brand.id
         left join pms_category
                   on pms_brand_category.category_id = pms_category.id
order by pms_brand.pinyin;

-- 属性表:插入测试数据
insert into pms_attribute (name, description, type, input_type, value_list, unit, is_allow_customize)
values ('屏幕尺寸', '智能手机屏幕尺寸', 0, 1, '6.1,6.3', '英寸', 1),
       ('屏幕尺寸', '笔记本电脑屏幕尺寸', 0, 1, '14,15', '英寸', 1),
       ('颜色', '智能手机颜色', 0, 1, '黑色,金色,白色', null, 1),
       ('颜色', '衬衣颜色', 0, 1, '白色,蓝色,灰色,黑色', null, 1),
       ('运行内存', '智能手机运行内存', 0, 1, '4,8,16', 'GB', 1),
       ('CPU型号', '智能手机CPU型号', 0, 1, '骁龙870,骁龙880', null, 1),
       ('机身毛重', '智能手机机身毛重', 0, 0, null, 'g', 0),
       ('机身存储', '智能手机机身存储', 0, 1, '64,128,256,512', 'GB', 0),
       ('操作系统', '智能手机操作系统', 0, 1, 'Android,iOS', null, 0),
       ('操作系统', '电脑操作系统', 0, 1, '无,Windows 7,Windows 10,Ubuntu,Mac OS', null, 0);

-- 属性模版表:插入测试数据
insert into pms_attribute_template (name, pinyin, keywords)
values ('智能手机', 'zhinengshouji', '手机'),
       ('服装-上身', 'fuzhuang', '服装,上衣'),
       ('服装-裤子', 'fuzhuang', '服装,裤'),
       ('笔记本电脑', 'bijibendiannao', '电脑,笔记本'),
       ('台式电脑', 'taishidiannao', '电脑,台式电脑,台式机');

-- 相册表:插入测试数据
insert into pms_album (name, description)
values ('iPhone 13', null),
       ('Mi 11 Ultra', null);

-- 图片表:插入测试数据
insert into pms_picture (album_id, url, description, width, height)
values (1, '模拟数据:iPhone 13图片URL-1', null, 1024, 768),
       (1, '模拟数据:iPhone 13图片URL-2', null, 1024, 768),
       (1, '模拟数据:iPhone 13图片URL-3', null, 1024, 768),
       (1, '模拟数据:iPhone 13图片URL-4', null, 1024, 768),
       (1, '模拟数据:iPhone 13图片URL-5', null, 1024, 768),
       (2, '模拟数据:Mi 11 Ultra图片URL-1', null, 1024, 768),
       (2, '模拟数据:Mi 11 Ultra图片URL-2', null, 1024, 768),
       (2, '模拟数据:Mi 11 Ultra图片URL-3', null, 1024, 768),
       (2, '模拟数据:Mi 11 Ultra图片URL-4', null, 1024, 768),
       (2, '模拟数据:Mi 11 Ultra图片URL-5', null, 1024, 768);

-- SPU表:插入测试数据
insert into pms_spu (id, name, type_number, title, description, list_price, stock, stock_threshold, unit, brand_id,
                     brand_name, category_id, category_name, keywords, tags)
values (202112010000001, 'iPhone 13', 'A2404', '苹果手机iPhone 13(A2404)', '2021年新款,全网首发',
        5199.99, 5000, 20, '部', 3, '苹果', 3, '智能手机', 'ip13,iPhone13,苹果13', '20w快充,NFC,无线充电'),
       (202112010000002, '小米11 Ultra', 'M112021', '小米11 Ultra(M112021)', '2021年最新旗舰机',
        5899.99, 8000, 20, '部', 2, '小米', 3, '智能手机', 'mi11,xiaomi11,ultra', '67w快充,1亿像素,5000毫安电池');

-- SPU详情表:插入测试数据
insert into pms_spu_detail (spu_id, detail)
values (1, '
iPhone 13的详情HTML
'), (2, '
小米11 Ultra的详情HTML
'); -- SKU(Stock Keeping Unit)表:插入测试数据 insert into pms_sku (id, spu_id, title, attribute_template_id, specifications, price, stock, stock_threshold) values (202112010000001, 2, '2021年新款,小米11 Ultra黑色512G,16G超大内存120Hz高刷67w快充', 1, '{"attributes":[{"id":1,"name":"屏幕尺寸","value":"6.1寸"},{"id":3,"name":"颜色","value":"黑色"},{"id":5,"name":"运行内存","value":"16GB"}]}', 6999.99, 3000, 50), (202112010000002, 2, '2021年新款,小米11 Ultra白色512G,8G超大内存120Hz高刷67w快充', 1, '{"attributes":[{"id":1,"name":"屏幕尺寸","value":"6.1寸"},{"id":3,"name":"颜色","value":"白色"},{"id":5,"name":"运行内存","value":"8GB"}]}', 6499.99, 3000, 50); -- SKU规格参数表(存储各SKU的属性与值,即规格参数):插入测试数据 insert into pms_sku_specification (sku_id, attribute_id, attribute_name, attribute_value, unit) values (1, 1, '屏幕尺寸', '6.1', '寸'), (1, 3, '颜色', '黑色', null), (1, 5, '运行内存', '16', 'GB'), (2, 1, '屏幕尺寸', '6.1', '寸'), (2, 3, '颜色', '白色', null), (2, 5, '运行内存', '8', 'GB');

具体开发顺序

目前涉及12张数据表,即12种数据类型,应该先开发基础的、与其它数据不直接相关的数据类型,例如相册、名牌、类别等,其它类型的开发将后置。

对于相册、名牌、类型这些数据,基本的数据操作至少包括:

  • 插入数据
  • 根据id删除数据
  • 根据id查询数据详情
  • 根据id修改数据
  • 查询数据列表
  • 其它……

实现数据访问层的开发

数据访问层指的就是增删改查相关的数据操作,对于数据库中的数据操作,通常使用Mybatis框架来实现。

在实现Mybatis编程之前,首先,应该有各数据表对应的实体类,关于实体类的开发:

  • 实体类的名称应该与数据表名的关键字相对应,例如表名为pms_album,则实体类名为Album,表名为pms_attribute_template,则实体类名为AttributeTemplate

  • 实体类中的属性的类型应该与表设计保持一致,通常对应关系为:

    • 数据表字段类型 实体类属性类型
      bigint Long
      intsmallinttinyint Integer
      charvarchartext String
      date_time LocalDateTime
      decimal BigDecimal
  • 实体类中所有属性都应该是私有的

  • 实体类中所有属性都应该有对应的Setter & Getter方法【自动生成】

  • 实体类必须存在无参数构造方法

  • 实体类必须重写hashCode()equals(),且必须保证:hashCode()返回值相同时,equals()对比结果必须为truehashCode()返回值不同时,equals()对比结果必须为false【自动生成】

    • 提示:不同的开发工具、生成时使用的模板不同时,生成的代码可能不同,但不重要
  • 实体类都应该重写toString()方法,以输出所有字段的值,便于后续观察对象

  • 实体类都必须实现Serializable接口

    • 可以不定义序列化版本ID

建议将实体类放在项目根包下的entity包中(某些编程习惯中可能使用其它的包名,例如domain等)。

第二天

LOMBOK

LOMBOK是一款可以在编译期在类中自动生成某些代码的工具,通常用于自动生成:

  • Setters & Getters
  • hashCode() and equals()
  • toString()
  • 无参数构造方法
  • 全参数构造方法

在使用时,需要添加依赖项:



<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.18.20version>
    <scope>providedscope>
dependency>

在POJO类的声明上,添加@Data注解,此注解可以帮助生成所有属性对应的Setters & Getters、规范的hashCode()equals()toString(),并且,要求此类的父类中存在无参数构造方法。

package cn.tedu.csmall.product.pojo.entity;

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class Album implements Serializable {

    private Long id;
    private String name;
    private String description;
    private Integer sort;
    private LocalDateTime gmtCreate;
    private LocalDateTime gmtModified;

}

注意:Lombok是在**编译期(将Java源代码文件.java编译成目标文件.class)**添加各方法,所以,在IntelliJ IDEA或其它开发工具中,默认情况下,直接调用以上各属性对应的Setter或Getter方法,在开发工具将无法提示这些方法,并且,已经写出来的调用这些方法的代码会报错,为了解决此问题,需要在开发工具中安装Lombok插件。

另外,Lombok还提供了以下注解:

  • @Data

  • @Setter

  • @Getter

  • @EqualsAndHashCode

  • @ToString

  • @NoArgsConstructor

  • @AllArgsConstructor

  • @Accessors

    • 配置为@Accessors(chain = true)时,将支持链式调用方法
  • @Slf4j

    • 用于日志,将在后续补充说明

Mybatis框架

Mybatis框架的主要作用

Mybatis框架主要实现了简化持久层编程的问题。

持久层:实现数据持久化的一系列组件。

数据持久化:通常,在开发领域中,讨论的数据大多是在内存中的,而内存默认特指内存条(RAM:Random Access Memory),RAM的特性包含“一旦断电,数据将全部丢失”,且“正在执行的程序和数据都是在内存中的”,由程序处理的数据最终应该永久的保存下来,则不能将这些数据一直只存储在内存中,通常,会将数据存储到可以永久保存数据的存储介质中,典型的永久存储数据的存储介质有:硬盘、U盘、光盘等,所以,数据持久化就是将内存中的数据存储到硬盘等介质中,而硬盘中的数据是以文件的形式存在的,所以,通常可以将数据存储到文本文件中、XML文件、数据库中,这些存储方案中,只有数据库是便于实现增、删、改、查这4种操作的,所以,一般“数据持久化”默认指的就是将数据存储到数据库中。

在Java语言中,实现数据库编程需要先建立与数据库的连接,然后准备SQL语句,然后执行,然后获取执行结果并处理结果,最后,关闭或归还数据库连接,这是一套非常固定的流程,无论对哪个数据表执行哪种操作,其流程大致是固定的,所以,就产生了一些框架,用于简化这部分的编程。

在使用Mybatis时,只需要关注2点:

  • 在接口中定义抽象方法
  • 配置抽象方法映射的SQL语句

使用Mybatis的前期准备

在Spring Boot项目中,当需要使用Mybatis时,需要添加相关的依赖:


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
dependency>

<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.2.2version>
dependency>

当添加以上依赖项后,如果启动项目,会提示以下错误:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

因为Spring Boot启动时,如果检测到当前已经添加数据库编程的依赖项,会自动读取连接数据库的配置信息,由于目前尚未配置这些信息,所以,启动会报错!

所以,需要在application.properties中添加配置:

# 连接数据库的参数
spring.datasource.url=jdbc:mysql://localhost:3306/mall_pms?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

由于Spring Boot在启动项目时只会读取以上配置并应用,并不会实际的连接数据库,所以,即使以上配置值是错误的,启动项目时并不会报告错误!

可以在src/test/java的根包下的测试类中进行测试连接:

@SpringBootTest
class CsmallProductApplicationTests {

    @Test
    void contextLoads() {
    }

    @Autowired
    DataSource dataSource;

    @Test
    void testConnection() throws Exception {
        dataSource.getConnection();
    }

}

当配置的URL错误(含主机名错误、端口号错误),或MySQL未启动时,将出现以下错误:

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.

如果数据库名称错误,或无此数据库,将出现以下错误:

java.sql.SQLSyntaxErrorException: Unknown database 'mall_pmsxzxxxxx'

如果用户或密码错误,将出现以下错误:

java.sql.SQLException: Access denied for user 'rootx'@'localhost' (using password: YES)
java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: NO)

使用Mybatis插入数据

Mybatis要求抽象方法必须存在于接口中(因为其实现原理是基于接口的代理模式的),所以,在项目的根包下创建mapper.AlbumMapper接口。

提示:可以在接口上添加@Repository注解,避免在自动装配时IntelliJ IDEA误判而提示错误。

关于接口中的抽象方法:

  • 返回值类型:如果需要执行的SQL是增、删、改类型的,使用int作为返回值类型,表示“受影响的行数”,不建议使用void
  • 方法名称 :自定义,不要使用重载
    • 获取单个对象的方法用get做前缀
    • 获取多个对象的方法用list做前缀
    • 获取统计值的方法用count做前缀
    • 插入的方法用save/insert做前缀
    • 删除的方法用remove/delete做前缀
    • 修改的方法用update做前缀
  • 参数列表:如果需要执行的SQL语句有多个参数,应该将这些参数封装到自定义的数据类型中,并使用自定义的数据类型作为抽象方法的参数

则,插入相册时需要执行的SQL语句大致是:

insert into pms_album (name, description, sort, gmt_create, gmt_modified) values (?, ?, ?, ?, ?);

则抽象方法为:

int insert(Album album);

首次使用时,需要让Mybatis知道哪些接口是Mapper接口,可以(二选一):

  • 在每个接口上添加@Mapper注解
  • 配置类上添加@MapperScan并指定Mapper接口所在的包
    • 在根包下的任何类,添加了@Configuration注解,即是配置类
    • 可以在根包下创建config.MybatisConfiguration类,同时添加@Configuration@MapperScan("cn.tedu.csmall.product.mapper")即可

另外,在使用Mybatis时,还需要为每个抽象方法配置其映射的SQL语句,可以使用@Insert等注解来配置SQL语句,但不推荐,因为:

  • 不便于配置较长的SQL语句
  • 不便于做一些复杂的配置,特别是查询时
  • 不便于实现与DBA(Database Administrator)分工合作

建议的做法是使用XML文件来配置SQL语句,可以从 http://doc.canglaoshi.org/config/Mapper.xml.zip 下载得到所需的文件,然后,在项目的src/main/resources下创建mapper文件夹,将得下载、解压得到的XML文件复制到此文件夹中。

关于配置SQL的XML文件:

  • 根节点必须是
  • 上必须配置namespace属性,取值为对应的接口的全限定名
  • 的子级,根据需要执行的SQL语句,选择使用节点,此节点必须配置resultTyperesultMap这2个属性中的某1个,当使用resultType时,此属性的值取决于抽象方法的返回值类型,如果是基本数据类型(例如int等),则resultType属性的值就是类型名,如果是引用数据类型(例如StringAlbum等),则resultType属性的值就是类型的全限定名(在java.lang包下的可以省略包名)。

    
    <select id="count" resultType="int">
        SELECT count(*) FROM pms_album
    select>
    

    如果既没有配置resultType又没有配置resultMap,将会出现以下错误:

    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'cn.tedu.csmall.product.mapper.AlbumMapper.count'.  It's likely that neither a Result Type nor a Result Map was specified.
    

    使用Mybatis查询–根据id查询

    假设需要实现:根据id查询相册详情

    需要执行的SQL语句大致是:

    select id, name, description, sort from pms_album where id=?
    

    关于抽象方法的返回值类型,原则上,只需要能够“放得下”就行,所以,可以使用Album作为此次查询的返回值类型,但是,并不建议这样处理!通常,建议另创建类型,用于封装查询结果!另外创建的类型,通常并不会称之为实体类,并且,这种类型会添加一些后缀,关于后缀的使用,阿里的文档的参考

    • 数据对象:xxxDO,xxx 即为数据表名
    • 数据传输对象:xxxDTO,xxx 为业务领域相关的名称
    • 展示对象:xxxVO,xxx 一般为网页名称
    • POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO

    关于以上后缀:

    • DO:Data Object
    • DTO:Data Transfer Object
    • VO:View Object / Value Object

    对于本次查询,可以使用VO作为类型的后缀,完整的类名可以使用AlbumStandardVO,此类应该放在项目的根包的pojo.vo包下:

    @Data
    public class AlbumStandardVO implements Serializable {
        // 除了gmtCreate和gmtModified以外的所有属性
    }
    

    提示:此前涉及的“实体类编写规范”本质上是POJO的规范!

    接下来,在AlbumMapper接口中添加抽象方法:

    AlbumStandardVO getStandardById(Long id);
    

    AlbumMapper.xml中配置SQL:

    
    <select id="getStandardById" resultType="xx.xx.xx.AlbumStandardVO">
        select id, name, description, sort from pms_album where id=#{id}
    select>
    

    Mybatis在封装查询结果时,会自动的将**列名(Column)属性名(Property)**匹配的结果进行封装,例如查询结果中的name值将封装到返回值对象的name属性中去,对于名称不匹配的,将放弃。

    可以在配置SQL时,为查询的字段自定义列名,使得“查询结果中的列名”与“封装结果的类型中的属性名”是一致的,例如:

    
    <select id="getStandardById" resultType="cn.tedu.csmall.product.pojo.vo.BrandStandardVO">
        SELECT
            id, name, pinyin, logo, description,
            keywords, sort, sales, 
        
        	product_count AS productCount, 
        	comment_count AS commentCount,
            positive_comment_count AS positiveCommentCount, 
        
        	enable
        FROM pms_brand
        WHERE id=#{id}
    select>
    

    提示:在SQL语句中,自定义别名时,AS关键字并不是必须的,只需要有1个空格即可。

    除了以上做法以外,还可以在application.properties中添加配置,使得Mybatis能自动处理“全小写且使用下划线分隔的字段名对应的列名”与“驼峰命名法的属性名”之间的对应关系(例如此做法时,不必在查询时自定义别名):

    mybatis.configuration.map-underscore-to-camel-case=true
    

    或者,还可以选择自定义ResultMap,用于指导Mybatis如何封装查询结果,其基本方式是:

    <resultMap id="自定义的ResultMap名称" type="封装查询结果的类型的全限定名">
        
    resultMap>
    
    <select id="xxx" resultMap="自定义的ResultMap名称">
    select>
    

    内部,使用节点,配置其columnproperty属性,用于指定列名与属性名的对应关系,例如:

    <resultMap id="自定义的ResultMap名称" type="封装查询结果的类型的全限定名">
        <result column="product_count" property="productCount" />
        <result column="comment_count" property="commentCount" />
        <result column="positive_comment_count" property="positiveCommentCount" />
    resultMap>
    

    提示:在普通的单表查询中,列名与属性名本身就对应的部分,并不需要在中配置。

    另外,在开发实践中,建议将查询的字段列表使用节点进行封装,然后,在配置的SQL语句中,使用节点进行调用即可:

    
    <select id="getStandardById" resultMap="StandardResultMap">
        SELECT
            <include refid="StandardQueryFields"/>
        FROM pms_brand
        WHERE id=#{id}
    select>
    
    <sql id="StandardQueryFields">
        id, name, pinyin, logo, description,
        keywords, sort, sales, product_count, comment_count,
        positive_comment_count, enable
    sql>
    
    <resultMap id="StandardResultMap" 
               type="cn.tedu.csmall.product.pojo.vo.BrandStandardVO">
        <result column="product_count" property="productCount" />
        <result column="comment_count" property="commentCount" />
        <result column="positive_comment_count" property="positiveCommentCount" />
    resultMap>
    

    当在上使用了resultMap,取值错误时(例如取值为类型的全限定名),将出现以下错误:

    java.lang.IllegalArgumentException: Result Maps collection does not contain value for cn.tedu.csmall.product.pojo.vo.BrandStandardVO
    

    使用Mybatis查询数据列表

    查询列表与查询某1个数据的开发过程相差不大,主要区别在于:

    • 查询列表时,需要查询的字段通常更少
    • Mybatis会自动使用List集合来封装查询到的多个数据,所以,抽象方法的返回值类型必须是List类型的

    假设需要实现:查询品牌列表(不考虑分页问题)

    需要执行的SQL语句大致是:

    select * from pms_brand order by sort desc, pinyin, id desc
    

    注意:如果执行的查询的结果可能超过1条(即2条或以上),必须显式的指定order by进行排序!

    vo包中创建BrandListItemVO类:

    @Data
    public class BrandListItemVO implements Serializable {
        // id, name, logo
    }
    

    然后,在BrandMapper接口中添加抽象方法:

    List<BrandListItemVO> list();
    

    BrandMapper.xml中配置SQL:

    
    <select id="list" resultMap="ListItemResultMap">
        SELECT
            <include refid="ListItemQueryFields"/>
        FROM pms_brand
        ORDER BY sort DESC, pinyin, id DESC
    select>
    
    <sql id="ListItemQueryFields">
        id, name, logo
    sql>
    
    <resultMap id="ListItemResultMap" type="cn.tedu.csmall.product.pojo.vo.BrandListItemVO">
    resultMap>
    

    注意:即使是查询列表,无论使用resultType,还是配置,关于数据类型,都只需要指定为List中的元素类型即可!

    作业

    完成以下数据表的功能:“根据id查询数据”(已完成的则跳过)、“根据名称统计数据数量”

    • 品牌表
    • 相册表
    • 类别表

    1. 关于utf8mb4

    utf8mb4是MySQL / MariaDB中的一种字符集。

    在当前主流版本的MySQL / MariaDB中,使用utf8作为字符集时,默认表示的是utf8mb3

    关于utf8mb3utf8mb4,其主要区别在于:most bytes 3most bytes 4,即最多使用3 / 4个字节来表示1个字符!所以,当使用utf8mb4时,可以表示更多字符,例如生僻汉字、冷门符号、emoji表情符号等。

    UTF指的是:Unicode Transfer Format,即Unicode传输编码。

    在使用MySQL / MariaDB时,所有SQL语句中涉及的字符集都明确的使用utf8mb4,而不要使用utf8

    2. Mybatis中的#{}占位符

    在Mybatis中配置SQL时,可以使用#{}格式的占位符来表示SQL语句中的参数,在占位符的大括号中,当抽象方法只有1个基本值(基本数据类型对应的值,和String)参数时,占位符名称是完全无所谓的,例如:

    select * from pms_brand where id=#{0}
    
    select * from pms_brand where id=#{id}
    
    select * from pms_brand where id=#{dxmkjsdoifds}
    

    以上写法都是可以正确运行的!

    注意:如果抽象方法的参数只有1个,但不是基本值时,在#{}的大括号里,必须写参数的数据类型的属性名,例如:

    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO pms_album (
            name, description, sort
        ) VALUES (
            #{name}, #{description}, #{sort}
        )
    insert>
    

    如果抽象方法的参数有多个,在非Spring Boot的集成环境下,抽象方法的每个参数必须使用@Param注解来配置参数名称,例如:

    int updatePasswordByUserId(@Param("userId") Long userId, 
                               @Param("password") String password);
    

    然后,在配置SQL时,在#{}格式的占位符的大括号,使用注解中配置的名称,例如:

    <update id="updatePasswordByUserId">
        update user set password=#{password} where id=#{userId}
    update>
    

    之所以需要使用@Param注解来配置名称,是因为编译期会丢失局部的变量的名称(这是Java语言的特点),

    主流的Spring Boot的集成环境下,即使抽象方法有多个参数,也可以不使用@Param注解来指定参数的名称,是因为在这样的集成环境下,Spring框架会对编译过程进行干预,从而保留抽象方法的参数名称,以至于在.class文件中是存在参数的名称的,所以,可以不使用@Param。(事实上,在Spring MVC的控制器中,Spring MVC框架也是做了这样的处理的)

    在开发实践中,无论使用的是Spring Boot集成环境,还是没有Spring Boot的环境,都应该在多参数时使用@Param注解以配置参数的名称!

    3. 其它问题

    • 在Mybatis中#{}格式的占位符与${}格式的占位符的区别
    • Mybatis的缓存机制

    第四天

    关于Mybatis小结

    • 【理解】Mybatis主要解决了:简化数据库编程
    • 【了解】使用Mybatis时需要添加依赖:mysql-connector-javamybatis-spring-boot-startermybatis + mybatis-spring + spring-context + spring-jdbc + 数据库连接池)
    • 【掌握】在配置类上使用@MapperScan配置接口所在的包,并在application.properties中配置XML文件的位置
    • 【了解】在application.properties配置连接数据库的参数
    • 【掌握】POJO的规范:属性私有化,实现Serializable,全属性的Setters & Getters,hashCode()equals()、存在无参数的构造方法
      • toString()并不是规范所要求的
    • 【掌握】抽象方法的设计原则
      • 返回值类型:增删改使用int,查询使用可以装得下结果的类型即可
      • 方法名称:自定义,不要重载,最好参考规范
      • 参数列表:取决于需要执行的SQL语句中的参数,当参数较多时,应该封装
    • 【掌握】在XML中配置SQL
      • 此XML文件不是凭空创建的,应该从别处复制粘贴得到(此XML顶部的声明不易于手动编写)
      • 每个XML文件都必须使用作为根节点,且配置namespace属性,此属性的值是对应的接口的全限定名
      • 区分使用这些节点都必须配置id属性,取值为对应的抽象方法的名称
      • 可以配置useGeneratedKeyskeyProperty属性,用于获取自动编号的id
      • 节点上配置useCache="false"

        结论:无论是一级缓存,还是二级缓存,都会因为发生了写操作而自动清空,这种机制通常并不满足生产环境的需求,所以,一般不会使用Mybatis的缓存机制!

        第十四天

        单点登录

        单点登录,即SSO(Single Sign On),表现在多个服务中,在其中某1个服务登录后,其它的服务均能识别登录的用户的身份。

        单点登录的解决方案:

        • 共享Session

          • 通常,可以选择将所有Session存储在专门的Redis服务器中,其它需要验证、读取用户身份的服务,都将从此Redis服务器中访问用户的Session信息
        • Token

          • 直接记录用户身份的信息,各服务器端只需要使用共同的验证、解析机制,即可识别用户的身份

        在Product项目中实现授权访问

        需要处理:

        • 添加spring-boot-starter-security依赖
        • 添加jjwtfastjson依赖
        • 创建Security配置类,在此配置类中:
          • 将Knife4j相关的URL设计为“白名单”
          • 允许跨域访问
          • 添加JWT过滤器
        • 创建JWT过滤器
        • 创建LoginPrincipal表示登录的当事人

        作业

        显示类别列表

        注意:具体实现的应该是“根据父级类别,查询其子级类别列表”。

        添加类别

        业务规则:

        • 如果选择了父级类别,且父级类别不存在,不允许添加
        • 深度(depth),由服务器端直接决定,值为:当无父级类别时,值为1,当有父级类别时,值为父级类别的深度+1
        • 是否父级(is_parent),新添加的类别,此值固定为0;如果有父级类别,且父级类别的is_parent为0时,需要更新为1

        删除类别

        业务规则:

        • 如果数据不存在,不允许删除
        • 如果仍存在子级类别,不允许删除
        • 如果被删除的类别的父级没有更多子级,则需要将父级的is_parent更新为0
        • 如果此类别关联了品牌,不允许删除
        • 如果此类别关联了属性模板,不允许删除

        启用与禁用类别

        业务规则:

        • 如果数据不存在,不允许启用/禁用
        • 如果数据的当前状态与目标状态相同,不允许设置
          • 例如当前为启用,目标也是启用,则不允许

        创建属性模板

        业务规则:

        • 每个属性模板的名称必须唯一

        显示属性模板

        删除属性模板

        业务规则:

        • 如果数据不存在,则不执行删除
        • 如果有“属性”数据关联到此模板,则不允许删除
        • 如果此属性模板关联了类别,不允许删除

        关联类别与属性模板

        提示:

        • 关联这2个数据,本质上是向pms_category_attribute_template表中插入数据
        • 前端页面设计难度较大,可以暂时使用2个输入框来输入类别的id、属性模板的id

        添加属性

        业务规则:

        • 属性模板必须存在

        根据属性模板id查询属性列表

        删除属性

        业务规则:

        • 如果数据不存在,则不执行删除

        显示品牌列表

        关联类别与品牌

        提示:

        • 关联这2个数据,本质上是向pms_brand_category表中插入数据
        • 前端页面设计难度较大,可以暂时使用2个输入框来输入类别的id、品牌的id

        删除品牌

        业务规则:

        • 如果数据不存在,不允许删除
        • 如果此品牌已经关联了类别,不允许删除

        第十六天

        Redis

        关于Redis

        Redis是一款基于内存使用了类似K-V结构来实现缓存数据的NoSQL非关系型数据库。

        提示:Redis本身也会做数据持久化处理。

        Redis的简单操作

        当已经安装Redis,并确保环境变量可用后,可以在命令提示符窗口(CMD)或终端(IDEA的Terminal,或MacOS/Linux的命令窗口)中执行相关命令。

        在终端下,可以通过redis-cli登录Redis客户端:

        redis-cli
        

        在Redis客户端中,可以通过ping检测Redis是否正常工作,将得到PONG的反馈:

        ping
        

        在Redis客户端中,可以通过set命令向Redis中存入修改简单类型的数据:

        set name jack
        

        在Redis客户端中,可以通过get命令从Redis中取出简单类型的数据:

        get name
        

        如果使用的Key并不存在,使用get命令时,得到的结果将是(nil),等效于Java中的null

        在Redis客户端中,可以通过keys命令检索Key:

        keys *
        
        keys a*
        

        注意:默认情况下,Redis是单线程的,keys命令会执行整个Redis的检索,所以,执行时间可能较长,可能导致阻塞!

        在Spring Boot项目中读写Redis

        首先,需要添加spring-boot-starter-data-redis依赖项:

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        

        以上依赖项默认会连接localhost:6379,并且无用户名、无密码,所以,当你的Redis符合此配置,则不需要在application.properties / application.yml中添加任何配置就可以直接编程。如果需要显式的配置,各配置项的属性名分别为:

        • spring.redis.host
        • spring.redis.port
        • spring.redis.username
        • spring.redis.password

        在使用以上依赖项实现Redis编程时,需要使用到的工具类型为RedisTemplate,调用此类的对象的方法,即可实现读写Redis中的数据。

        在使用之前,应该先在配置类中使用@Bean方法创建RedisTemplate,并实现对RedisTemplate的基础配置,则在项目的根包下创建config.RedisConfiguration类:

        package cn.tedu.csmall.product.config;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.data.redis.connection.RedisConnectionFactory;
        import org.springframework.data.redis.core.RedisTemplate;
        import org.springframework.data.redis.serializer.RedisSerializer;
        
        import java.io.Serializable;
        
        /**
         * Redis的配置类
         *
         * @author [email protected]
         * @version 0.0.1
         */
        @Slf4j
        @Configuration
        public class RedisConfiguration {
        
            @Bean
            public RedisTemplate<String, Serializable> redisTemplate(
                    RedisConnectionFactory redisConnectionFactory) {
                RedisTemplate<String, Serializable> redisTemplate 
                        = new RedisTemplate<>();
                redisTemplate.setConnectionFactory(redisConnectionFactory);
                redisTemplate.setKeySerializer(RedisSerializer.string());
                redisTemplate.setValueSerializer(RedisSerializer.json());
                return redisTemplate;
            }
        
        }
        

        1. Spring MVC拦截器

        1.1. 关于拦截器

        拦截器:Interceptor

        Spring MVC拦截器是Spring MVC框架中的一种组件,它可以执行在若干个请求之前、之后,通常用于解决处理若干个请求都需要执行的任务,例如验证用户是否已经登录等。

        1.2. 使用拦截器

        使用Spring MVC拦截器,首先,需要自定义类,作为拦截器类,这个类必须实现HandlerInterceptor接口,例如:

        @Slf4j
        @Component
        public class DemoInterceptor implements HandlerInterceptor {
        
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                log.debug("DemoInterceptor.preHandle()");
                return false;
            }
        
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                log.debug("DemoInterceptor.postHandle()");
            }
        
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                log.debug("DemoInterceptor.afterCompletion()");
            }
        
        }
        

        每个拦截器都必须注册后才可以生效,在Spring MVC的配置类(实现了WebMvcConfigurer接口的配置类)中重写addInterceptors()方法即可实现注册,例如:

        @Autowired
        private DemoInterceptor demoInterceptor;
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(demoInterceptor)
                .addPathPatterns("/brands", "/categories/list-by-parent");
        }
        

        通过测试运行,可以发现,拦截器的3个方法:

        • preHandle():在控制器(Controller)之前执行,此方法的返回值是boolean类型的,当返回true时表示“放行”,当返回false时表示“阻止”,当阻止时,程序不会向后继续运行,例如控制器将不会执行
        • postHandle():在控制器(Controller)之后执行
        • afterCompletion():在处理完整个浏览,即将向客户端进行响应之前执行

        1.3. 配置拦截路径

        在配置拦截路径时,可以使用星号(*)作为通配符,但是,只能匹配1层路径,例如:使用/brands/*可以匹配/brands/add-new/brands/list,却不可以匹配到/brands/1/delete

        如果要匹配若干层路径,需要使用2个连续的星号(**),例如:使用/brands/**,可以匹配到/brands/add-new/brands/list/brands/1/delete/brands/1/status/disable……

        一旦使用通配符,可能导致匹配的范围过大,例如:配置为/admins/**,将匹配到/admins/change-password/admins/upload-avatar等,还会匹配到/admins/login等,如果此拦截器是用于验证“是否登录”的,将/admins/login也拦截是不合适的!

        在配置拦截路径时,还可以调用excluedePathPatterns()方法,在已有的拦截范围中添加“排除在外”的请求路径,例如:

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(demoInterceptor)
                    .addPathPatterns("/admins/**")
                    .excludePathPatterns("/admins/login");
        }
        

        提示:以上addPathPatterns()excludePathPatterns()这2个方法的参数都可以是可变参数,或List集合。

        1.4. 拦截器与过滤器的区别

        以上讨论的拦截器(Interceptor)是Spring MVC框架中的组件,而过滤器(Filter)是Java EE中的组件。

        过滤器是最早接收到客户端请求的组件,是执行在所有组件之前的,而拦截器是执行在Spring MVC的控制器(Controller)之前和之后的。

        • 基于以上特点,某些问题只能通过过滤器来解决,例如:
          • 设置字符编码
          • Spring Security的相关过滤器

        过滤器只能配置“黑名单”,不可以配置“白名单”,所以,使用时并不是那么方便,而拦截器的配置更加灵活!

        第十八天

        在项目中应用Redis

        Redis是用于处理“缓存”的,当客户端尝试查询某些数据时,服务器端的处理流程大致是:

        • 优先从Redis中获取数据
          • 如果Redis中没有所需的数据,则从数据库中查询,并将查询结果存入到Redis
        • 将Redis中的数据(或:刚刚从数据库中查询出来的数据)响应到客户端

        使用Redis后,可以明显的提高查询效率(当数据表中的数据量大时,效果明显),同时,还能减轻数据库服务器的压力。

        在使用之前,还应该确定需要将哪些数据使用Redis处理查询,通常,应该是查询频率可能较高的、允许数据不够准确的(即使数据有一些不准确,但是对整个项目没有严重后果的),甚至这些数据极少改变的。

        在具体使用时,可以直接使用RedisTemplate去操作Redis,也可以对RedisTemplate的使用进行再次封装。

        关于缓存预热

        缓存预热:启动项目时,就将缓存数据加载到Redis中。

        在Spring Boot项目中,当需要实现“启动项目时直接执行”的效果,需要自定义组件类,实现ApplicationRunner接口,重写其中的run()方法,此run()将在项目启动成功后自动执行。

        提示:缓存预热的操作应该通过ApplicationRunner来实现,这样才可以保证在所有组件都已经正确的创建后再执行缓存预热,如果通过某些组件的构造方法来编写缓存预热的代码,此时某些组件可能还没有创建,则无法正确执行。

        关于缓存预热的具体实现:

        • 删除所有相关的缓存数据
          • 删除列表数据:如果不删除,再次向缓存中写入列表,将是在原列表的基础上追加,则会产生重复的列表项
          • 删除数据项(每一个数据):如果不删除,则会导致原本已经缓存的数据一直存在,某些数据可能在数据库中已经删除,则缓存中的数据也应该被删除
        • 从数据库中查询列表,并写入到缓存
        • 基于查询到的列表,遍历,得到每个数据的id,再从数据库中查出各数据,并写入到缓存

        关于更新缓存

        更新缓存的策略有多种,通常使用的可以是:

        • 手动更新
          • 适用于数据变化频率非常低的应用场景,这些数据的缓存可以是长期存在,偶尔需要更新时,手动更新即可
        • 自动更新
          • 适用于数据频繁的变化,通过手动更新不太现实,将会是每间隔一段时间,或在特定的某个时间(例如每周一凌晨3点)自动更新

        关于自动更新,需要使用到“计划任务”。

        使用计划任务,需要自定义组件类,然后,在类中自定义方法(应该是public权限,返回值类型声明为void,参数列表为空),这个方法将作为计划任务执行的方法,在此方法上需要添加@Scheduled注解,并配置其执行频率或特定的执行时间,最后,还需要在配置类上使用@EnableScheduling注解,以开启当前项目的计划任务。

        Spring AOP

        AOPAspect Oriented Programming,面向切面编程

        注意:AOP并不是Spring框架独有的技术或特点,即使没有使用Spring框架,也可以实现AOP,但是,Spring框架很好的支持了AOP,所以,通常会使用Spring来实现AOP。

        在开发实践中,数据的处理流程大致是:

        注册:客户端 <---(请求)---> Controller <------> Service <------> Mapper
        
        登录:客户端 <---(请求)---> Controller <------> Service <------> Mapper
        
        下单:客户端 <---(请求)---> Controller <------> Service <------> Mapper
        

        假设,现在添加一个需求:统计每个业务(Service中的方法)的执行耗时。

        在没有AOP的情况下,只能编辑每个Service方法,添加几乎相同代码来实现以上需求,并且,当需求发生变化时,每个Service方法可能需要再次调整。

        使用AOP实现以上需求,大致需要:

        • 创建切面类,并交给Spring框架管理
        • 配置切面类中的方法在特定的点执行

        在项目中添加spring-boot-starter-aop依赖。

        在项目的根包下创建aop.TimerAspect类,在类上添加@Component@Aspect注解

        long start = System.currentTimeMillis();
        
        xxx;
        
        long end = System.currentTimeMillis();
        
        long t = end - start;
        

        第十九天

        【技能描述】

        1. 【了解/掌握/熟练掌握】开发工具的使用,包括:Eclipse、IntelliJ IDEA、Git、Maven;
        2. 【了解/掌握/熟练掌握】Java语法,【理解/深刻理解】面向对象编程思想,【了解/掌握/熟练掌握】Java SE API,包括:String、日期、IO、反射、线程、网络编程、集合、异常等;
        3. 【了解/掌握/熟练掌握】HTML、CSS、JavaScript前端技术,并【了解/掌握/熟练掌握】前端相关框架技术及常用工具组件,包括:jQuery、Bootstrap、Vue脚手架、Element UI、axios、qs、富文本编辑器等;
        4. 【了解/掌握/熟练掌握】MySQL的应用,【了解/掌握/熟练掌握】DDL、DML的规范使用;
        5. 【了解/掌握/熟练掌握】数据库编程技术,包括:JDBC、数据库连接池(commons-dbcpcommons-dbcp2Hikaridruid),及相关框架技术,例如:Mybatis Plus等;
        6. 【了解/掌握/熟练掌握】主流框架技术的规范使用,例如:SSM(Spring,Spring MVC, Mybatis)、Spring Boot、Spring Validation、Spring Security等;
        7. 【理解/深刻理解】Java开发规范(参考阿里巴巴的Java开发手册);
        8. 【了解/掌握/熟练掌握】基于RESTful的Web应用程序开发;
        9. 【了解/掌握/熟练掌握】基于Spring Security与JWT实现单点登录;

你可能感兴趣的:(java)