4-8 CS后台项目练习-2
8. 类别管理--添加类别--持久层
8.1. 配置
续前日,无新增
8.2. 规划需要执行的SQL语句
续前日,无新增
8.3. 接口与抽象方法
此前需要执行的SQL语句大致是:
select id from pms_category where name=?;
在csmall-pojo
的根包下创建vo.CategorySimpleVO
类,用于封装以上查询结果:
@Data
public class CategorySimpleVO implements Serializable {
private Long id;
}
在csmall-product-webapi
的CategoryMapper
接口中添加抽象方法:
CategorySimpleVO getByName(String name);
8.4. 配置SQL语句
在csmall-product-webapi
的CategoryMapper.xml
中添加配置:
<!-- CategorySimpleVO getByName(String name); -->
<select id="getByName" resultMap="SimpleResultMap">
select id from pms_category where name=#{name}
</select>
<resultMap id="SimpleResultMap" type="cn.tedu.csmall.pojo.vo.CategorySimpleVO">
<id column="id" property="id" />
</resultMap>
8.5. 测试
在csmall-product-webapi
的src\test\resources
下创建insert_data.sql
文件,用于插入测试数据:
insert into pms_category (name) value ('类别001'), ('类别002');
然后,在CategoryMapperTests
中添加测试方法:
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetByNameSuccessfully() {
// 测试数据
String name = "类别001";
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行查询
CategorySimpleVO category = mapper.getByName(name);
// 断言查询结果不为null
assertNotNull(category);
});
}
@Test
@Sql({"classpath:truncate.sql"})
public void testGetByNameFailBecauseNotFound() {
// 测试数据
String name = "类别999";
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行查询
CategorySimpleVO category = mapper.getByName(name);
// 断言查询结果为null
assertNull(category);
});
}
完成后,执行整个测试类(将执行此类中所有测试方法),应该全部通过测试。
9. 类别管理--添加类别--业务逻辑层
9.1. 接口与抽象方法
在使用Dubbol的微服务架构中,需要将业务逻辑层的接口声明在专门的Module中,便于被其它微服务Module依赖,所以,先在csmall-product
下创建新的Module,名为csmall-product-service
,创建参数:
- Group:
cn.tedu
- Artifact:
csmall-product-service
- Package Name:
cn.tedu.csmall.product.service
首先,应该调用新Module的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>
<!-- 父级项目 -->
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-product</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 当前项目的信息 -->
<groupId>cn.tedu</groupId>
<artifactId>csmall-product-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 当前项目需要使用的依赖项 -->
<dependencies>
<!-- Csmall POJO -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-pojo</artifactId>
</dependency>
</dependencies>
</project>
然后,在csmall-product
的pom.xml
中补充此子级Module:
<!-- 当前Project的各子级Module -->
<modules>
<module>csmall-product-webapi</module>
<module>csmall-product-service</module> <!-- 新增 -->
</modules>
接下来,需要删除不必要的文件:
- 启动类
src\main\resources
及其下的配置文件src\test
接下来,需要创建接口并添加抽象方法,方法的参数应该是封装的对象(因为一个String
或Long
等简单数据不足以完成添加类别的操作),则先在csmall-pojo
的根包下创建dto.CategoryAddNewDTO
类,并在类中添加必要的属性:
package cn.tedu.csmall.pojo.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class CategoryAddNewDTO implements Serializable {
private String name;
private Long parentId;
private String keywords;
private Integer sort;
private String icon;
private Integer isDisplay;
}
然后,在csmall-product-service
中,在cn.tedu.csmall.product.service
下创建ICategoryService
接口:
public interface ICategoryService {
void addNew(CategoryAddNewDTO categoryAddNewDTO);
}
9.2. 实现
在csmall-product-service
中只存放业务逻辑层的接口,而业务逻辑层的实现类仍在csmall-product-webapi
中,所以,需要在csmall-product-webapi
中依赖csmall-product-service
。
先在Project的pom.xml
中添加对csmall-product-service
的依赖管理:
<!-- ===== 原有其它代码 ===== -->
<!-- 依赖管理,主要管理各依赖项的版本,使得子级Module添加依赖时不必指定版本 -->
<dependencyManagement>
<dependencies>
<!-- Csmall Product Service -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-product-service</artifactId>
<version>${csmall.version}</version>
</dependency>
<!-- ===== 原有其它代码 ===== -->
然后,在csmall-product-webapi
中添加依赖:
<!-- ===== 原有其它代码 ===== -->
<!-- 当前项目需要使用的依赖项 -->
<dependencies>
<!-- Csmall Product Service -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-product-service</artifactId>
</dependency>
<!-- ===== 原有其它代码 ===== -->
在cn.tedu.csmall.product.webapi
下创建service.CategoryServiceImpl
类,此类应该实现ICategoryService
接口,此类还应该添加@Service
注解:
package cn.tedu.csmall.product.webapi.service;
import cn.tedu.csmall.pojo.dto.CategoryAddNewDTO;
import cn.tedu.csmall.product.service.ICategoryService;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl implements ICategoryService {
@Override
public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
}
}
关于以上业务的实现分析:
@Autowired
private CategoryMapper categoryMapper;
// 注意:需要创建异常
// 注意:需要在CategoryMapper中补充getById()方法,至少返回:depth
// 注意:需要在CategoryMapper中补充updateIsParentById()方法
public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
// 从参数中取出尝试添加的类别的名称
// 调用categoryMapper.getByName()方法查询
// 判断查询结果是否不为null
// 是:抛出ServiceException
// 从参数中取出父级类别的id:parentId
// 判断parentId是否为0
// 是:此次尝试添加的是一级类别,没有父级类别,则当前depth >>> 1
// 否:此次尝试添加的不是一级类别,则应该存在父级类别,调用categoryMapper.getById()方法查询父级类别的信息
// -- 判断查询结果是否为null
// -- 是:抛出ServiceException
// -- 否:当前depth >>> 父级depth + 1
// 创建Category对象
// 调用BeanUtils.copyProperties()将参数对象中的属性值复制到Category对象中
// 补全Category对象中的属性值:depth >>> 前序运算结果
// 补全Category对象中的属性值:enable >>> 1(默认即启用)
// 补全Category对象中的属性值:isParent >>> 0
// 补全Category对象中的属性值:gmtCreate, gmtModified >>> LocalDateTime.now()
// 调用categoryMapper.insert(Category)插入类别数据,获取返回的受影响的行数
// 判断返回的受影响的行数是否不为1
// 是:抛出ServiceException
// 判断父级类别的isParent是否为0
// 是:调用categoryMapper.updateIsParentById()方法,将父级类别的isParent修改为1,获取返回的受影响的行数
// 判断返回的受影响的行数是否不为1
// 是:抛出ServiceException
}
要实现以上业务,需要先在持久层完成“根据id查询类别信息”的功能,则在CategorySimpleVO
中添加private Integer depth;
属性(原getByName()
方法对应的查询也作对应的修改,虽然不是必须的)。
然后,还需要在CategorySimpleVO
中补充private Integer isParent;
属性,并且,必须在接下的查询中,查出此值。
然后CategeoryMapper
接口中添加:
CategorySimpleVO getById(Long id);
然后在CategoryMapper.xml
中配置以上方法映射的SQL:
<!-- CategorySimpleVO getById(Long id); -->
<select id="getById" resultMap="SimpleResultMap">
select id, depth from pms_category where id=#{id}
</select>
完成后,还需要在CategoryMapperTests
中添加2个测试,以检验以上功能是否正常运行:
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetByIdSuccessfully() {
// 测试数据
Long id = 1L;
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行查询
CategorySimpleVO category = mapper.getById(id);
// 断言查询结果不为null
assertNotNull(category);
});
}
@Test
@Sql({"classpath:truncate.sql"})
public void testGetByIdFailBecauseNotFound() {
// 测试数据
Long id = -1L;
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行查询
CategorySimpleVO category = mapper.getById(id);
// 断言查询结果为null
assertNull(category);
});
}
在CategoryMapper
接口中添加:
int updateIsParentById(@Param("id") Long id, @Param("isParent") Integer isParent);
然后在CategoryMapper.xml
中配置以上方法映射的SQL:
<!-- int updateIsParentById(@Param("id") Long id, @Param("isParent") Integer isParent); -->
<update id="updateIsParentById">
update pms_category set is_parent=#{isParent} where id=#{id}
</update>
完成后,还需要在CategoryMapperTests
中添加2个测试,以检验以上功能是否正常运行:
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testUpdateIsParentByIdSuccessfully() {
// 测试数据
Long id = 1L;
Integer isParent = 1;
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行测试
int rows = mapper.updateIsParentById(id, isParent);
// 断言受影响的行数为1
assertEquals(1, rows);
});
}
@Test
@Sql({"classpath:truncate.sql"})
public void testUpdateIsParentByIdFailBecauseNotFound() {
// 测试数据
Long id = -1L;
Integer isParent = 1;
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行测试
int rows = mapper.updateIsParentById(id, isParent);
// 断言受影响的行数为0
assertEquals(0, rows);
});
}
在实现业务逻辑之前,还需要创建自定义的异常类型,由于后续还有不少需要被多个Module共同使用的类、接口等,所以,此异常类型和后续可能被共用的类、接口都应该放在一个公共的Module中,则在Project下创建csmall-common
这个新的Module,创建成功后,需要:
- 在
csmall-common
中,修改pom.xml
中的父项目 - 在
csmall-common
中,在pom.xml
删除依赖项 - 在
csmall-common
中,在pom.xml
删除<build>
配置 - 在
csmall-common
中,删除src/test
- 在
csmall-common
中,删除src/main/resources
- 在
csmall-common
中,删除启动类 - 在Project的
pom.xml
中,添加<module>
- 在Project的
pom.xml
中,添加对新Module的依赖管理 - 在
csmall-product-webapi
中的pom.xml
中,添加对csmall-common
的依赖
在csmall-common
的根包下创建ex.ServiceException
类:
public class ServiceException extends RuntimeException {
// 暂时不加构造方法
}
然后,在csmall-product-webapi
中的CategoryServiceImpl
中实现业务:
package cn.tedu.csmall.product.webapi.service;
import cn.tedu.csmall.common.ex.ServiceException;
import cn.tedu.csmall.pojo.dto.CategoryAddNewDTO;
import cn.tedu.csmall.pojo.entity.Category;
import cn.tedu.csmall.pojo.vo.CategorySimpleVO;
import cn.tedu.csmall.product.service.ICategoryService;
import cn.tedu.csmall.product.webapi.mapper.CategoryMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class CategoryServiceImpl implements ICategoryService {
@Autowired
CategoryMapper categoryMapper;
@Override
public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
// 从参数中取出尝试添加的类别的名称
String name = categoryAddNewDTO.getName();
// 调用categoryMapper.getByName()方法查询
CategorySimpleVO queryResult = categoryMapper.getByName(name);
// 判断查询结果是否不为null
if (queryResult != null) {
// 是:抛出ServiceException
throw new ServiceException();
}
// 从参数中取出父级类别的id:parentId
Long parentId = categoryAddNewDTO.getParentId();
// 判断parentId是否为0,当前尝试新增的类别的depth默认为1
Integer depth = 1;
CategorySimpleVO parentCategory = null;
if (parentId != 0) {
// 否:此次尝试添加的不是一级类别,则应该存在父级类别,调用categoryMapper.getById()方法查询父级类别的信息
parentCategory = categoryMapper.getById(parentId);
// -- 判断查询结果是否为null
if (parentCategory == null) {
// -- 是:抛出ServiceException
throw new ServiceException();
}
// -- 否:当前depth >>> 父级depth + 1
depth = parentCategory.getDepth() + 1;
}
// 创建Category对象
Category category = new Category();
// 调用BeanUtils.copyProperties()将参数对象中的属性值复制到Category对象中
BeanUtils.copyProperties(categoryAddNewDTO, category);
// 补全Category对象中的属性值:depth >>> 前序运算结果
category.setDepth(depth);
// 补全Category对象中的属性值:enable >>> 1(默认即启用)
category.setEnable(1);
// 补全Category对象中的属性值:isParent >>> 0
category.setIsParent(0);
// 补全Category对象中的属性值:gmtCreate, gmtModified >>> LocalDateTime.now()
LocalDateTime now = LocalDateTime.now();
category.setGmtCreate(now);
category.setGmtModified(now);
// 调用categoryMapper.insert(Category)插入类别数据,获取返回的受影响的行数
int rows = categoryMapper.insert(category);
// 判断返回的受影响的行数是否不为1
if (rows != 1) {
// 是:抛出ServiceException
throw new ServiceException();
}
// 判断父级类别的isParent是否为0
// 以下判断条件有部分多余,但不会报错
if (parentId != 0 && parentCategory != null && parentCategory.getIsParent() == 0) {
// 是:调用categoryMapper.updateIsParentById()方法,将父级类别的isParent修改为1,获取返回的受影响的行数
rows = categoryMapper.updateIsParentById(parentId, 1);
// 判断返回的受影响的行数是否不为1
if (rows != 1) {
// 是:抛出ServiceException
throw new ServiceException();
}
}
}
}
9.3. 测试
在src/test/java
下的根包下创建service.CategoryServiceTests
测试类,编写并执行测试:
package cn.tedu.csmall.product.webapi.service;
import cn.tedu.csmall.common.ex.ServiceException;
import cn.tedu.csmall.pojo.dto.CategoryAddNewDTO;
import cn.tedu.csmall.product.service.ICategoryService;
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;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
public class CategoryServiceTests {
@Autowired
ICategoryService service;
@Test
@Sql("classpath:truncate.sql")
public void testAddNewSuccessfully() {
// 测试数据
CategoryAddNewDTO category = new CategoryAddNewDTO();
category.setName("大屏智能手机");
category.setParentId(0L);
category.setIcon("未上传类别图标");
category.setKeywords("未设置关键字");
category.setSort(88);
category.setIsDisplay(1);
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行测试
service.addNew(category);
});
}
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testAddNewFailBecauseNameDuplicate() {
// 测试数据
CategoryAddNewDTO category = new CategoryAddNewDTO();
category.setName("类别001");
// 断言不会抛出异常
assertThrows(ServiceException.class, () -> {
// 执行测试
service.addNew(category);
});
}
@Test
@Sql({"classpath:truncate.sql"})
public void testAddNewFailBecauseParentNotFound() {
// 测试数据
CategoryAddNewDTO category = new CategoryAddNewDTO();
category.setName("类别001");
category.setParentId(-1L);
// 断言不会抛出异常
assertThrows(ServiceException.class, () -> {
// 执行测试
service.addNew(category);
});
}
}
10. 基于Spring JDBC的事务管理
事务:是一种能够保证同一个业务中多个写(增删改)操作要么全部成功,要么失败的机制!
在业务方法上添加@Transactional
即可保证此方法是业务性(要么全部成功,要么全部失败)的。
在Spring JDBC中,处理事务的机制大致是:
开启事务:Begin
try {
你的业务方法
提交:Commit
} catch (RuntimeException e) {
回滚:Rollback
}
所以,为了保证事务性,所有的写操作在执行之后,必须有某个判定为失败的标准,且判断定为失败后,必须抛出RuntimeException
或其子孙类异常!
- Spring JDBC默认对
RuntimeException
进行回滚处理,有必要的话,也可以配置为其它异常类型
11. 类别管理--添加类别--控制器层
12. 类别管理--添加类别--前端页面
4-8 CS后台项目练习-2的更多相关文章
- 4-11 CS后台项目-4 及 Redis缓存数据
使用Redis缓存数据 使用Redis可以提高查询效率,一定程度上可以减轻数据库服务器的压力,从而保护了数据库. 通常,应用Redis的场景有: 高频查询,例如:热搜列表.秒杀 改变频率低的数据,例如 ...
- 4-7 CS后台项目练习-1
1. 关于此项目 此项目是一个自营性质电商类型的项目. 当前目标是设计后台管理相关功能. 2. 关于项目的开发流程 开发项目的标准流程应该有:需求分析.可行性分析.总体设计.详细设计等. 建议课后学习 ...
- 4-10 CS后台项目练习-3 || Redis
13. 类别管理--根据id查询类别详情--持久层 13.1. 规划SQL语句 本次需要执行的SQL语句大致是: select * from pms_category where id=? 关于字段列 ...
- CS通用项目系统搭建——三层架构第一天
CS通用项目:使用三层架构进行搭建 三层架构: 表现层(UI(User Interface)):展示给用户的层面,包含窗体控件数据等信息. 业务逻辑层(BLL(Business Logic Layer ...
- php大力力 [039节] 修改一下后台项目,同时启用印象笔记,要做的事情todo列表,记录在印象笔记,速度快一些
php大力力 [039节] 修改一下后台项目,同时启用印象笔记,要做的事情todo列表,记录在印象笔记,速度快一些
- winform WebBrowser控件中,cs后台代码执行动态生成的js
很多文章都是好介绍C# 后台cs和js如何交互,cs调用js方法(js方法必须是页面上存在的,已经定义好的),js调用cs方法, 但如果想用cs里面执行动态生成的js代码,如何实现呢? 思路大致是这样 ...
- 运行PHP后台项目:xampp下载,安装,配置,运行PHP的web项目
本来没有想着弄PHP,但是有同学叫我帮忙启动一下一个PHP写的后台.着实需要去学习一下. 想着安装xampp软件,一个集合了多个服务器,多个数据库,多个后台语言的管理软件. 一.xampp下载 二.安 ...
- react_结合 redux - 高阶函数 - 高阶组件 - 前端、后台项目打包运行
Redux 独立的集中式状态管理 js 库 - 参见 My Git 不是 react 库,可以与 angular.vue 配合使用,通常和 react 用 yarn add redux import ...
- 使用Java+MySQL+Apache开发后台项目(一)
做前端开发的人越来越多,后端维护的人才越来越稀缺,这种趋势正在慢慢扩展.像我这种人总喜欢反其道而行之,做后端开发的人虽然减少了,但是工作量和工作资质都要求的更高了,随着人工智能的发展,需要后台处理的数 ...
随机推荐
- 神经网络 CNN 名词解释
隐藏层 不是输入或输出层的所有层都称为隐藏层. 激活和池化都没有权重 使层与操作区分开的原因在于层具有权重.由于池操作和激活功能没有权重,因此我们将它们称为操作,并将其视为已添加到层操作集合中. 例如 ...
- 关于background-*的一些属性
1.盒模型 盒模型从外到内一次为:margin-box.border-box.padding-box.content-box. 2.一些属性设置的相对位置 ⑴background-position的属 ...
- 一个Python中优雅的数据分块方法
背景 看到这个标题你可能想一个分块能有什么难度?还值得细说吗,最近确实遇到一个有意思的分块函数,写法比较巧妙优雅,所以写一个分享. 日前在做需求过程中有一个对大量数据分块处理的场景,具体来说就是几十万 ...
- jQuery操作标签,jQuery事件操作,jQuery动画效果,前端框架
jQuery操作标签 jQuery代码查找标签绑定的变量名推荐使用 $xxxEle 样式类操作 addClass();// 添加指定的CSS类名. removeClass();// 移除指定的CSS类 ...
- 对象、Map、Set、WeakMap、WeakSet
对象.Map.Set.WeakMap.WeakSet 本文写于 2020 年 11 月 24 日 总的来说,Set 和 Map 主要的应用场景分别在于数据重组和数据储存.Set 是一种叫做「集合」的数 ...
- 【动态UAC权限】无盾程序(win32&cmd)
可以看到两种不同的提权方式,注意是动态,用代码提权,而不是用清单文件提前处理. 函数都写好了,这里不多做解释. win32程序: 首先需要这俩头文件,第二个我忘了啥函数要用了,总之出问题加上就对了:( ...
- 关于position的relative和absolute分别是相对于谁进行定位的
position:absolute; 他的意思是绝对定位,他是参照浏览器的左上角,配合TOP.RIGHT.BOTTOM.LEFT(下面简称TRBL)进行定位,在没有设定TRBL,默认依据父级的做标原始 ...
- linux篇-linux数据库mysql的安装
1数据库文件放到opt下面 2赋予权限775 3运行脚本 4运行成功 5数据库操作 密码修改并刷新 权限修改,允许外部设备访问 6工具连接 7附录 1.显示当前数据库服务器中的数据库列表: mysql ...
- Link-Cut-Tree(1)
参考论文 求解范围:(动态树问题) 树上路径查询.修改 动态连边.删边 换根 lca 算法逻辑 概念: 类似树链剖分,把一棵树拆成许多链,每个链用splay维护(链上的为实边,否则为虚边),splay ...
- kruskar重构树
只略略讲一点基本方式与思想了 构建 并查集,边按从小(大)到大(小)加入,建新点,点权为此边权,该点为两点根的父亲. 性质:(此处为最小生成树重构树) 1.lca(u,v)为u到v路径上的最大边权 2 ...