spring boot 基本流程

本文详细介绍了如何在Spring Boot中实现类别管理功能,根据ID查询类别详情。从持久层规划SQL,创建VO类,XML配置SQL,到业务逻辑层的接口和实现,再到控制器层的处理,每个步骤都有清晰的解释和测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类别管理--根据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()); // 打印日志
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值