2020-11-8 17:54更新
练习及学习中所遇到的一些bug,记录成册为自己复习所用。
项目源码链接:gitee-SpringBoot+MyBatis+Spring
业务描述
需求描述
任何一个电商系统中都有一个商品子系统,而与商品相关联都会有一个品牌信息管理,在当前业务系统设计中我们就是要对商品品牌信息的管理进行设计和实现.
业务架构分析
在品牌(Brand)信息管理中就是要实现对商品品牌信息的添加,修改,查询,删除等业务,如图所示:
业务基本原型设计
基于品牌业务描述,对品牌模块的业务原型进行分析和设计,如图所示:
- 品牌列表页面
- 品牌编辑页面
项目技术架构分析及设计
在品牌管理模块实现过程,我们采用典型的C/S架构进行实现.客户端我们基于浏览器进行实现,服务端采用tomcat,数据库使用MySQL.具体应用层基于MVC分层架构进行实现.
项目技术栈应用分析及选型
- 客户端技术:html,css,javascript,bootstrap
- 服务端技术:hikaricp,mybatis,spring,springboot,thymeleaf
- 数据库技术:mysql,sql
- 开发工具集:jdk1.8,maven3.6.3,idea2020.2
项目品牌模块核心API分析与设计
基于分层架构设计思想,现对品牌API进行设计,如图所示:
数据库及表的分析与设计
设计并创建数据库
假如数据库已存在,则先删除数据库,代码如下:
drop database if exists dbbrand;
创建新的数据库,代码如下:
create database dbbrand default character set utf8;
设计并创建品牌(Brand)表
打开数据库,语句如下:
use dbbrand;
在dbbrand数据库中创建品牌表.
create table tb_brand(
id bigint primary key auto_increment,
name varchar(100) not null,
remark text,
createdTime datetime not null
)engine=InnoDB;
基于SQL脚本执行数据库初始化
将数据库设计脚本写到brand.sql中,然后按如下步骤执行:
打开mysql自带客户端,登录mysql,指令如下:
mysql -uroot -proot
设置客户端编码,指令如下:
set names utf8;
执行sql脚本(此处直接复制即可)
[source d:/brand.sql]
drop database if exists dbbrand;
create database dbbrand default character set utf8;
use dbbrand;
create table tb_brand(
id bigint primary key auto_increment,
name varchar(100) not null,
remark text,
createdTime datetime not null
)engine=InnoDB;
insert into tb_brand values (null,'联想','very good',now());
insert into tb_brand values (null,'小米','very good',now());
insert into tb_brand values (null,'美的','very good',now());
insert into tb_brand values (null,'九阳','very good',now());
insert into tb_brand values (null,'TCL','very good',now());
insert into tb_brand values (null,'创维','very good',now());
insert into tb_brand values (null,'华为','very good',now());
脚本执行成功以后,在客户端查询数据之前,先执行如下语句:
set names gbk;
项目环境初始化
准备操作
1)JDK 1.8
2)Maven 3.6.3
3)IDEA 2020.2
4)MySQL 5.7+
创建项目Module
打开idea,然后基于设计,创建项目module,如图所示:
添加项目Module依赖
- MySQL 驱动
mysql
mysql-connector-java
runtime
- Srping Jdbc 提供了HikariCP连接池
org.springframework.boot
spring-boot-starter-data-jdbc
- MyBatis 资源
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3
- Spring Web 依赖 (内置一个tomcat服务)
org.springframework.boot
spring-boot-starter-web
- Thymeleaf 依赖 (html模板引擎)
org.springframework.boot
spring-boot-starter-thymeleaf
项目Module基础配置
打开项目Module配置文件application.properties,并添加如下内容:
#spring server
server.port=80
# spring datasource
spring.datasource.url=jdbc:mysql:///dbbrand?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
# spring mybatis
mybatis.mapper-locations=classpath:/mapper/*/*.xml
# spring log
logging.level.com.cy=debug
#spring thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
启动项目进行初步环境测试分析
- 端口号被占用
- 数据库连接错误
品牌数据的查询及呈现
业务描述
将数据库中的品牌信息查询出来,然后在客户端基于html技术进行呈现,如图所示:
服务端品牌查询时序设计
基于查询请求,进行访问时序设计,如图所示:
领域对象(POJO)设计及实现
设置Brand对象,基于此对象封装从数据库查询到的品牌信息,代码如下:
package com.cy.pj.brand.pojo;
import java.util.Date;
public class Brand {
private Integer id;
private String name;
private String remark;
private Date createdTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", name='" + name + ''' +
", remark='" + remark + ''' +
", createdTime=" + createdTime +
'}';
}
}
数据逻辑对象(DAO)查询方法设计及实现
设计用于访问Brand数据的数据访问对象及方法,其关键步骤如下:
第一步:定义BrandDao接口,代码如下:
package com.cy.pj.brand.dao;
@Mapper
public interface BrandDao{
}
第二步:在BrandDao中定义品牌查询方法,代码如下:
List findBrands(String name);
第三步:基于查询方法定义SQL映射.
在resources目录中创建mapper/brand目录,并在目录中添加BrandMapper.xml文件,关键代码如下:
其中:concat为mysql中提供的字符串连接函数.
第四步:对数据层的查询方法进行单元测试,代码如下:
package com.cy.pj.brand.dao;
@SpringBootTests
public class BrandDaoTests{
@Autowired
private BrandDao brandDao;
@Test
void testFindBrands(){
List list=brandDao.findBrands("TCL");
for(Brand b:list){
System.out.println(b);
}
}
}
第五步:测试过程中的BUG分析?
- BindingException,如图所示:
- ExecutorException,如图所示:
业务逻辑对象(Service)查询方法设计及实现
业务逻辑对象负责模块的具体业务处理,例如参数校验,事务控制,权限控制,日志记录等.
第一步:定义业务接口
package com.cy.pj.brand.service;
public interface BrandService{
}
第二步:在BrandService接口中添加品牌查询方法
List findBrands(String name);
第三步:定义BrandService接口实现类BrandServiceImpl.
package com.cy.pj.brand.service.impl;
@Service
public class BrandServiceImpl implements BrandService{
private static final Logger log=
LoggerFactory.getLogger(BrandServiceImpl.class);
@Autowired
private BrandDao brandDao;
public List findBrands(String name){
long t1=System.currentTimeMillis();
List list=brandDao.findBrands(name);
long t2=System.currentTimeMillis();
log.info("findBrands->time->{}",(t2-t1));
return list;
}
}
第三步:定义BrandService接口方法的单元测试类,并业务进行测试分析
package com.cy.pj.brand.service;
@SpringBootTest
public class BrandServiceTests{
@Autowired
private BrandService brandService;
@Test
void testFindBrands(){
List list=brandService.findBrands();
for(Brand b:list){
System.out.println(b);
}
}
}
第四步:测试过程中的Bug分析
- NoSuchBeanDefinition,如图所示:
- NullPointerException,如图所示:
控制逻辑对象(Controller)查询方法设计及实现
在控制逻辑对象中主要是负责请求和响应逻辑控制,例如请求url映射,参数映射,请求方式,结果集的封装,解析,响应的设计等.
第一步:定义Controller类
package com.cy.pj.brand.controller;
@Controller
public class BrandController{
@Autowired
private BrandService brandService;
}
第二步:在Controller添加处理查询请求的方法
@GetMapping(value={"/brand/doFindBrands/{name}","/brand/doFindBrands"})
public String doFindBrands(@PathVariable(required = false) String name, Model model){
List list=brandService.findBrands(name);
model.addAttribute("list", list);
return "brand/brand";//第一个brand为目录,第二brand为view name
}
其中,
1)@GetMapping描述方法时,表示这个方法只能处理Get请求,注解内部的value属性可以指定多个url.
2)@PathVariable 用于描述方法参数,表示方法参数的值可以来自url中{}内部的变量值,required=false表示参数可以不传值.
客户端品牌列表页面设计及实现
在项目的templates目录下创建brand目录并添加brand.html页面,其关键代码如下:
id
name
createdTime
10
AAA
2020/10/11
其中:
1)${}为thymeleaf为中的EL表达式,用于从服务端model中获取数据
2)th:each为thymeleaf定义的自定义标签属性,用于迭代数据.
3)th:text为thymeleaf定义的自定义标签属性,用于设置文本内容.
启动服务进行访问测试并对结果进行分析
启动服务,打开浏览器输入指定url,进行访问,其数据呈现过程,如图所示:
启动及运行过程中的Bug分析及解决方案
- 页面元素解析异常,如图所示:
- 模板页面找不到,如图所示:
品牌模块删除业务分析及实现
业务描述
在品牌列表页面中,点击当前行记录后面的删除按钮,基于当前行的记录id执行品牌删除操作,删除成功以后,重新刷新页面.如图所示:
业务时序分析与设计
客户端向服务端发起删除请求,服务器端的处理时序如下:
数据逻辑对象(DAO)中删除方法设计及实现
基于业务,在BrandDao接口中添加删除方法,代码如下:
int deleteById(Integer id);
基于删除方法,定义SQL映射(本次直接以注解方式进行定义),代码如下:
@Delete("delete from tb_brand where id=#{id}")
int deleteById(Integer id);
在BrandDaoTests单元测试类中添加单元测试方法,对删除操作进行测试,关键代码如下
@Test
void testDeleteById(){
int rows=brandDao.deleteById(10);
System.out.println("rows="+rows);
}
业务逻辑对象(Service)中删除方法设计及实现
在业务逻辑对象方法中,要处理删除操作需要的一些业务逻辑(后续有参数校验,权限控制,....).
第一步:在BrandService接口中添加,品牌删除的业务方法,代码如下:
int deleteById(Integer id);
第二步:在BrandServiceImpl类中添加删除业务的具体实现,代码如下:
public int deleteById(Integer id){
//1.参数校验
//2.执行删除业务
int rows=brandDao.deleteById(id);
//3.校验结果并返回
return rows;
}
第三步:在BrandServiceTests类中添加单元测试方法,对其删除业务做测试?
@Test
void testDeleteById(){
int rows=brandService.deleteById(10);
System.out.println("row="+row);
}
第四步:测试过程中的Bug分析?
控制逻辑对象(Controller)中删除方法设计及实现
在控制层对象中定义处理删除请求的方法,具体代码如下:
@GetMapping("/brand/doDeleteById/{id}")
public String doDeleteById(@PathVariable Integer id,Model model){
brandService.deleteById(id);
List list=brandService.findBrands();
model.addAttribute("list",list);
return "brand/brand";
}
客户端删除按钮事件定义及处理
在tbody的tr中添加一列,代码如下:
定义javascript函数,处理删除事件,代码如下:
function doDeleteById(id){
//给出提示信息
if(!confirm("您确认删除吗"))return;//confirm为浏览器中window对象的函数
//执行删除业务
location.href=`http://localhost/brand/doDeleteById/${id}`;
}
品牌模块添加业务分析及实现
业务描述
在列表页面上,设计添加按钮,当点击添加按钮时,跳转到添加页面,然后在添加页面上数据品牌信息,点击Save按钮就数据提交到服务端进行保存.
添加时序分析及设计
数据逻辑对象(Dao)中方法设计及实现
第一步:在BrandDao中添加用于保存品牌信息的方法,代码如下:
int insertBrand(Brand brand);
第二步:在BrandMapper中添加品牌保存操作对应的sql映射,代码如下:
insert into tb_brand
(name,remark,createdTime)
values
(#{name},#{remark},now())
业务逻辑对象(Service)中方法设计及实现
第一步:在BrandService业务接口中中定义用于保存品牌信息的方法,代码如下:
int saveBrand(Brand brand);
第二步:在BrandServiceImpl业务实现类中添加保存品牌信息的具体实现,代码如下:
public int saveBrand(Brand brand){
int rows=brandDao.insertBrand(brand);
return rows;
}
控制逻辑对象(Controller)中方法设计及实现
第一步:在BrandController中添加用于处理请求添加页面的方法,代码如下:
@GetMapping("/brand/doAddUI")
public String doAddUI(){
return "brand/brand-add";
}
第二步:在BrandController添加用于处理添加品牌信息页面的方法,代码如下:
@PostMapping("/brand/doSaveBrand")
public String doSaveBrand(Brand brand,Model model){
System.out.println("save.brand="+brand);
brandService.saveBrand(brand);
List list=brandService.findBrands(null);
model.addAttribute("list",list);
return "brand/brand";
}
品牌添加操作客户端业务分析、设计及实现.
第一步:设计品牌添加页面brand-add.html,代码如下
Title
The Brand Add Page
第二步:在品牌列表brand.html页面,设计添加按钮,代码如下:
第三步:点击添加按钮时,加载品牌添加页面,事件处理函数如下:
function doAddUI(){
location.href="/brand/doAddUI";
}
启动服务进行访问测试分析
启动及运行过程中的BUG分析
- 客户端提交到服务端的数据拿不到?
品牌模块修改业务分析及实现
业务描述
在品牌列表页面,点击当前行的修改按钮,先基于id查询当前行记录,并将记录呈现在编辑页面,如图所示:
业务时序分析与设计
基于id查询品牌信息并呈现在页面上,其时序分析如图所示:
在品牌编辑页面,编辑数据,点击save按钮保存更新,其时序如图所示:
数据逻辑对象(Dao)中方法设计与实现
在BrandDao中添加基于id查询品牌信息的方法及SQL映射,代码如下:
@Select("select * from tb_brand where id=#{id}")
Brand findById(Integer id);
在BrandDao中添加基于id执行品牌更新的方法及SQL映射,代码如下:
@Update("update tb_brand set name=#{name},remark=#{remark} where id=#{id}")
int updateBrand(Brand Brand);
业务逻辑对象(Service)中方法设计与实现
在BrandService 中添加基于id查询品牌信息和更新品牌信息的方法,代码如下:
Brand findById(Integer id);
int updateBrand(Brand brand);
在BrandServiceImpl中基于id查询品牌信息和更新品牌信息的方法,代码如下:
@Override
public Brand findById(Integer id) {
//.....
return brandDao.findById(id);
}
@Override
public int updateBrand(Brand brand) {
return brandDao.updateBrand(brand);
}
控制逻辑对象(Controller)中方法设计与实现
在BrandController中添加基于id查询品牌信息的方法,代码如下:
@RequestMapping("/brand/doFindById/{id}")
public String doFindById(@PathVariable Integer id,Model model) {
Brand brand=brandService.findById(id);
model.addAttribute("brand",brand);
return "/brand/brand-update";
}
在BrandController中添加更新品牌信息的方法,代码如下:
@RequestMapping("/brand/doUpdateBrand")
public String doUpdateBrand(Brand brand,Model model) {
brandService.updateBrand(brand);
List list=brandService.findBrands(null);
model.addAttribute("list",list);
return "brand/brand";
}
客户端品牌编辑页面设计与实现
第一步:设计品牌修改页面brand-update.html,代码如下
Title
The Brand Update Page
启动Tomcat服务进行访问测试分析
启动服务,先进入品牌列表页面,然后点击修改按钮如图所示:
在品牌编辑页面,编辑数据以后,点击save按钮,执行更新操作。
启动及运行过程中的BUG分析
- 405 异常
- 呈现的数据不正确
- 页面元素解析异常
总结(Summary)
本章节,主要基于springboot,Hikaricp,MyBatis,Spring,Thymeleaf等技术,对商品品牌模块做了具体实现。重点运用其基本设计及实现过程。