4-10 CS后台项目练习-3 || Redis

13. 类别管理--根据id查询类别详情--持久层

13.1. 规划SQL语句

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

select * from pms_category where id=?

关于字段列表,应该包括:

id, name, parent_id, depth, keywords, sort, icon, enable, is_parent, is_display

13.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);

13.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>

13.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);
    });
}

14. 类别管理--根据id查询类别详情--业务逻辑层

14.1. 接口和抽象方法

ICategoryService中添加:

CategoryDetailsVO getDetailsById(Long id);

14.2. 实现

CategoryServiceImpl中执行查询并返回。


14.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);
    });
}

15. 类别管理--根据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()); // 打印日志
}

16. 使用Redis

Redis是一款基于内存的NoSQL数据存储服务,是非关系型的,是使用K-V结构进行存储的

  • 基于内存:读写数据均在内存中直接操作
  • NoSQL:通常把能够存、取数据的服务都称之为数据库,所以,Redis也是数据库,但是,它没有SQL语句

在基于Spring Boot的开发中,当需要在程序中访问Redis中的数据时,需要添加spring-boot-starter-data-redis依赖项。

要操作Redis中的数据,需要使用RedisTemplate对象,则在csmall-product-webapi的根包下的config包中创建RedisConfiguration类,并在其中进行配置:

@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;
    }
    
}

接下来,在测试的根包下创建RedisTests来测试访问Redis中的数据:

@SpringBootTest
public class RedisTests {

    @Autowired
    RedisTemplate<String, Serializable> redisTemplate;

    @Test
    void testSetValue() {
        redisTemplate.opsForValue()
                .set("name", "liuguobin");
    }

    @Test
    void testSetValueTTL() {
        redisTemplate.opsForValue()
                .set("name", "fanchuanqi", 60, TimeUnit.SECONDS);
    }

    @Test
    void testSetObjectValue() {
        CategoryDetailsVO category = new CategoryDetailsVO();
        category.setId(65L);
        category.setIsParent(1);
        category.setDepth(1);
        category.setName("水果");
        redisTemplate.opsForValue()
                .set("category", category);
    }

    @Test
    void testGetValue() {
        // 当key存在时,可获取到有效值
        // 当key不存在时,获取到的结果将是null
        Serializable name = redisTemplate.opsForValue()
                .get("name");
        System.out.println("get value --> " + name);
    }

    @Test
    void testGetObjectValue() {
        // 当key存在时,可获取到有效值
        // 当key不存在时,获取到的结果将是null
        Serializable serializable = redisTemplate.opsForValue()
                .get("category");
        System.out.println("get value --> " + serializable);
        if (serializable != null) {
            CategoryDetailsVO category = (CategoryDetailsVO) serializable;
            System.out.println("get value --> " + category);
        }
    }

    @Test
    void testDeleteKey() {
        // 删除key时,将返回“是否成功删除”
        // 当key存在时,将返回true
        // 当key不存在时,将返回false
        Boolean result = redisTemplate.delete("name");
        System.out.println("result --> " + result);
    }

    @Test
    void testRightPushList() {
        // 存入List时,需要redisTemplate.opsForList()得到针对List的操作器
        // 通过rightPush()可以向Redis中的List追加数据
        // 每次调用rightPush()时使用的key必须是同一个,才能把多个数据放到同一个List中
        List<CategoryDetailsVO> list = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            CategoryDetailsVO category = new CategoryDetailsVO();
            category.setName("类别00" + i);
            list.add(category);
        }

        String key = "categoryList";
        for (CategoryDetailsVO category : list) {
            redisTemplate.opsForList().rightPush(key, category);
        }
    }

    @Test
    void testListSize() {
        // 获取List的长度,即List中的元素数量
        String key = "categoryList";
        Long size = redisTemplate.opsForList().size(key);
        System.out.println("size --> " + size);
    }

    @Test
    void testRange() {
        // 调用opsForList()后再调用range(String key, long start, long end)方法取出List中的若干个数据,将得到List
        // long start:起始下标(结果中将包含)
        // long end:结束下标(结果中将包含),如果需要取至最后一个元素,可使用-1作为此参数值
        String key = "categoryList";
        List<Serializable> range = redisTemplate.opsForList().range(key, 0, -1);
        for (Serializable serializable : range) {
            System.out.println(serializable);
        }
    }

    @Test
    void testKeys() {
        // 调用keys()方法可以找出匹配模式的所有key
        // 在模式中,可以使用星号作为通配符
        Set<String> keys = redisTemplate.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
    }

}

最后,关于Key的使用,通常建议使用冒号区分多层次,类似URL的设计方式,例如:

  • 类别列表的Key:categories:listcategories
  • 某个id(9527)对应的类别的Key:categories:item:9527
posted @ 2022-07-16 11:08  Y_Zoran  阅读(32)  评论(0)    收藏  举报
-->