类别管理--根据id查询类别详情--持久层
1.1. 规划SQL语句
本次需要执行的SQL语句大致是:
select * from pms_category where id=?
关于字段列表,应该包括:
id, name, parent_id, depth, keywords, sort, icon, enable, is_parent, is_display
1.2. 抽象方法(可能需要创建VO类)
在csmall-pojo
的根包下的vo
包下创建CategoryDetailsVO
类,封装以上设计的字段对应的属性:
package cn.tedu.csmall.pojo.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class CategoryDetailsVO implements Serializable {
private Long id;
private String name;
private Long parentId;
private Integer depth;
private String keywords;
private Integer sort;
private String icon;
private Integer enable;
private Integer isParent;
private Integer isDisplay;
}
在CategoryMapper
接口中添加:
CategoryDetailsVO getDetailsById(Long id);
1.3. 在XML中配置SQL
在CategoryMapper.xml
中添加配置:
<!-- CategoryDetailsVO getDetailsById(Long id); -->
<select id="getDetailsById" resultMap="DetailsResultMap">
select
<include refid="DetailsQueryFields"/>
from
pms_category
where
id=#{id}
</select>
<sql id="DetailsQueryFields">
<if test="true">
id, name, parent_id, depth, keywords,
sort, icon, enable, is_parent, is_display
</if>
</sql>
<resultMap id="DetailsResultMap" type="cn.tedu.csmall.pojo.vo.CategoryDetailsVO">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="parent_id" property="parentId" />
<result column="depth" property="depth" />
<result column="keywords" property="keywords" />
<result column="sort" property="sort" />
<result column="icon" property="icon" />
<result column="enable" property="enable" />
<result column="is_parent" property="isParent" />
<result column="is_display" property="isDisplay" />
</resultMap>
1.4. 测试
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetDetailsByIdSuccessfully() {
// 测试数据
Long id = 1L;
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行查询
Object category = mapper.getDetailsById(id);
// 断言查询结果不为null
assertNotNull(category);
});
}
@Test
@Sql({"classpath:truncate.sql"})
public void testGetDetailsByIdFailBecauseNotFound() {
// 测试数据
Long id = -1L;
// 断言不会抛出异常
assertDoesNotThrow(() -> {
// 执行查询
Object category = mapper.getDetailsById(id);
// 断言查询结果为null
assertNull(category);
});
}
2. 类别管理--根据id查询类别详情--业务逻辑层
2.1. 接口和抽象方法
在ICategoryService
中添加:
CategoryDetailsVO getDetailsById(Long id);
2.2. 实现
在CategoryServiceImpl
中执行查询并返回。
@Override public CategoryDetailsVO getDetailsById(Long id) { // ===== 以下是原有代码,只从数据库中获取数据 ===== // CategoryDetailsVO category = categoryMapper.getDetailsById(id); // if (category == null) { // throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND, // "获取类别详情失败,尝试访问的数据不存在!"); // } // return category; // ===== 以下是新的业务,将从Redis中获取数据 ===== log.debug("根据id({})获取类别详情……", id); // 从repository中调用方法,根据id获取缓存的数据 // 判断缓存中是否存在与此id对应的key boolean exists = categoryRedisRepository.exists(id); if (exists) { // 有:表示明确的存入过某数据,此数据可能是有效数据,也可能是null // -- 判断此key对应的数据是否为null CategoryDetailsVO cacheResult = categoryRedisRepository.getDetailsById(id); if (cacheResult == null) { // -- 是:表示明确的存入了null值,则此id对应的数据确实不存在,则抛出异常 log.warn("在缓存中存在此id({})对应的Key,却是null值,则抛出异常", id); throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND, "获取类别详情失败,尝试访问的数据不存在!"); } else { // -- 否:表示明确的存入了有效数据,则返回此数据即可 return cacheResult; } } // 缓存中没有此id匹配的数据 // 从mapper中调用方法,根据id获取数据库的数据 log.debug("没有命中缓存,则从数据库查询数据……"); CategoryDetailsVO dbResult = categoryMapper.getDetailsById(id); // 判断从数据库中获取的结果是否为null if (dbResult == null) { // 是:数据库也没有此数据,先向缓存中写入错误数据,再抛出异常 log.warn("数据库中也无此数据(id={}),先向缓存中写入错误数据", id); categoryRedisRepository.saveEmptyValue(id); log.warn("抛出异常"); throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND, "获取类别详情失败,尝试访问的数据不存在!"); } // 将从数据库中查询到的结果存入到缓存中 log.debug("已经从数据库查询到匹配的数据,将数据存入缓存……"); categoryRedisRepository.save(dbResult); // 返回查询结果 log.debug("返回查询到数据:{}", dbResult); return dbResult; }
2.3. 测试
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetDetailsByIdSuccessfully() {
// 测试数据
Long id = 1L;
// 断言不抛出异常
assertDoesNotThrow(() -> {
service.getDetailsById(id);
});
}
@Test
@Sql({"classpath:truncate.sql"})
public void testGetDetailsByIdFailBecauseNotFound() {
// 测试数据
Long id = -1L;
// 断言抛出异常
assertThrows(ServiceException.class, () -> {
service.getDetailsById(id);
});
}
3. 类别管理--根据id查询类别详情--控制器层
在CategoryController
中添加:
@GetMapping("/{id}")
public JsonResult<CategoryDetailsVO> getDetailsById(@PathVariable Long id) {
CategoryDetailsVO category = categoryService.getDetailsById(id);
return JsonResult.ok(category);
}
在CategoryControllerTests
中测试:
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetDetailsByIdSuccessfully() throws Exception {
// 准备测试数据,注意:此次没有提交必要的name属性值
String id = "1";
// 请求路径,不需要写协议、服务器主机和端口号
String url = "/categories/" + id;
// 执行测试
// 以下代码相对比较固定
mockMvc.perform( // 执行发出请求
MockMvcRequestBuilders.get(url) // 根据请求方式决定调用的方法
.contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
.accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
.andExpect( // 预判结果,类似断言
MockMvcResultMatchers
.jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
.value(State.OK.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
.andDo( // 需要执行某任务
MockMvcResultHandlers.print()); // 打印日志
}
@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetDetailsByIdFailBecauseNotFound() throws Exception {
// 准备测试数据,注意:此次没有提交必要的name属性值
String id = "9999999999";
// 请求路径,不需要写协议、服务器主机和端口号
String url = "/categories/" + id;
// 执行测试
// 以下代码相对比较固定
mockMvc.perform( // 执行发出请求
MockMvcRequestBuilders.get(url) // 根据请求方式决定调用的方法
.contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
.accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
.andExpect( // 预判结果,类似断言
MockMvcResultMatchers
.jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
.value(State.ERR_CATEGORY_NOT_FOUND.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
.andDo( // 需要执行某任务
MockMvcResultHandlers.print()); // 打印日志
}