Redis can be used to cache data that needs to be accessed frequently, data will be cached in memory, and we can manipulate Redis through Jedis.

Install Redis on Windows

Click “start”, input “cmd”, choose “Run as administrator”:

img

Run the following command in command line:

1
choco install redis-64
img

Run Redis Server

Run the following command in command line:

1
redis-server
img

Test Redis Server with Redis Client

Click “start”, input “cmd” and press enter:

1
redis-cli
img

Run the following command, press enter and you should get “PONG” from the redis server:

1
ping
img

We have successfully setup the redis server.

Add Dependency into Service Module

Copy and paste the following content into the pom.xml file under the service module directory:

1
2
3
4
5
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0-m1</version>
        </dependency>

Update Product.java

Copy and paste the following content into the Product.java file under the common/src/main/java/co.dongchen.shop.common/model directory:

1
2
3
4
5
6
7
8
    private List<Long> ids;
    public List<Long> getIds() {
        return ids;
    }

    public void setIds(List<Long> ids) {
        this.ids = ids;
    }

The complete code will be like:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package co.dongchen.shop.common.model;

import com.google.common.collect.Lists;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

public class Product {

    private Long id;
    private Integer createdAt;
    private Integer updatedAt;
    private String sku;
    private String name;
    private Integer stock;
    private Integer price;
    private Boolean isEnabled;
    private String images;
    private String description;
    private Long userId;
    // NEWLY ADDED CODE 1 BEGIN
    private List<Long> ids;
    // NEWLY ADDED CODE 1 END

    private String mainImage;
    private List<String> imageList = Lists.newArrayList();
    private Boolean isFavourite;
    private List<MultipartFile> imageFiles;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Integer createdAt) {
        this.createdAt = createdAt;
    }

    public Integer getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Integer updatedAt) {
        this.updatedAt = updatedAt;
    }

    public String getSku() {
        return sku;
    }

    public void setSku(String sku) {
        this.sku = sku;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Boolean getEnabled() {
        return isEnabled;
    }

    public void setEnabled(Boolean enabled) {
        isEnabled = enabled;
    }

    public String getImages() {
        return images;
    }

    public void setImages(String images) {
        this.images = images;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    // NEWLY ADDED CODE 2 BEGIN
    public List<Long> getIds() {
        return ids;
    }

    public void setIds(List<Long> ids) {
        this.ids = ids;
    }
    // NEWLY ADDED CODE 2 END

    public String getMainImage() {
        return mainImage;
    }

    public void setMainImage(String mainImage) {
        this.mainImage = mainImage;
    }

    public List<String> getImageList() {
        return imageList;
    }

    public void setImageList(List<String> imageList) {
        this.imageList = imageList;
    }

    public Boolean getFavourite() {
        return isFavourite;
    }

    public void setFavourite(Boolean favourite) {
        isFavourite = favourite;
    }

    public List<MultipartFile> getImageFiles() {
        return imageFiles;
    }

    public void setImageFiles(List<MultipartFile> imageFiles) {
        this.imageFiles = imageFiles;
    }
}

Update product.xml

Copy and paste the following content into the product.xml file under the service/src/main/resources/mapper directory:

1
2
3
4
5
6
            <if test="productCondition.ids != null and productCondition.ids.size() > 0 ">
                and p.id in
                <foreach collection="productCondition.ids" item="item" index="index" open="(" separator="," close=")" >
                    #{item}
                </foreach>
            </if>

The complete code will be like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="co.dongchen.shop.mapper.ProductMapper">
    <sql id="productFields">
        p.id, p.created_at, p.updated_at, p.sku, p.name, p.stock, p.price, p.is_enabled, p.images, p.description, p.user_id
    </sql>
    <sql id="whereCondition">
        <where>
            <if test="productCondition.id != null and productCondition.id != 0">
                and p.id = #{productCondition.id}
            </if>
            <if test="productCondition.isEnabled != null and productCondition.isEnabled == true">
                and p.is_enabled = 1
            </if>
            <if test="productCondition.isEnabled != null and productCondition.isEnabled == false">
                and p.is_enabled = 0
            </if>
            <if test="productCondition.name != null and productCondition.name != ''">
                <bind name="likeName" value="'%' + productCondition.name + '%'"/>
                and p.name like #{likeName}
            </if>
            <!-- NEWLY ADDED CODE BEGIN -->
            <if test="productCondition.ids != null and productCondition.ids.size() > 0 ">
                and p.id in
                <foreach collection="productCondition.ids" item="item" index="index" open="(" separator="," close=")" >
                    #{item}
                </foreach>
            </if>
            <!-- NEWLY ADDED CODE END -->
        </where>
    </sql>
    <select id="queryPagedProducts" resultType="product">
        select <include refid="productFields"/>
        from product p
        <include refid="whereCondition"/>
        <if test="pageHelperCondition.offSet != null and pageHelperCondition.limit != null">
            limit #{pageHelperCondition.offSet},#{pageHelperCondition.limit}
        </if>
        <if test="pageHelperCondition.offSet == null and pageHelperCondition.limit != null">
            limit #{pageHelperCondition.limit}
        </if>
    </select>
    <select id="queryPagedCount" resultType="long">
        select count(id)
        from product p
        <include refid="whereCondition"/>
    </select>
</mapper>

Update ProductService.java

Change the following method’s accessibility from private to public in the ProductService.java file under the service/src/main/co.dongchen.shop/service directory:

1
2
3
4
5
6
7
8
    public List<Product> queryWithImgs(Product productCondition, PageHelper pageHelper) {
        List<Product> products = productMapper.queryPagedProducts(productCondition, pageHelper);
        products.forEach(p -> {
            p.setMainImage(imgPath + p.getMainImage());
            p.setImageList(p.getImageList().stream().map(img -> imgPath + img).collect(Collectors.toList()));
        });
        return products;
    }

Create TopProductService

  • Right click service/src/main/java/co.dongchen.shop/service directory: New > Java Class
  • Fill in “TopProductService”
  • Click “OK” button
img

Since I am currently doing this on my MacBook Pro, so this will be slightly different from the previous screenshots.

Copy and paste the following content into TopProductService.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package co.dongchen.shop.service;

import co.dongchen.shop.common.model.Product;
import co.dongchen.shop.common.util.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class TopProductService {

    private static final String TOP_PRODUCT_KEY = "TOP_PRODUCT_KEY";

    @Autowired
    private ProductService productService;

    public void increase(Long id) {
        Jedis jedis = new Jedis("127.0.0.1");
        // Adds the score value of the member named the product id in the TOP_PRODUCT_KEY sorted set to one and returns the new score value of the member.
        jedis.zincrby(TOP_PRODUCT_KEY, 1.0D, String.valueOf(id));
        // Removes members after the fifth of the top five ranked members in the TOP_PRODUCT_KEY sorted set and returns the number of removed members.
        jedis.zremrangeByRank(TOP_PRODUCT_KEY, 5, -1);
        jedis.close();
    }

    private List<Long> getTopIds() {
        Jedis jedis = new Jedis("127.0.0.1");
        // Retrieve all members of the sorted set named TOP_PRODUCT_KEY.
        Set<String> ids = jedis.zrevrange(TOP_PRODUCT_KEY, 0, -1);
        jedis.close();
        return ids.stream().map(Long::parseLong).collect(Collectors.toList());
    }

    public List<Product> getTopProducts(int size) {
        Product productCondition = new Product();
        List<Long> topIds = getTopIds();
        // Retrieve minimum available products from the beginning.
        topIds = topIds.subList(0, Math.min(topIds.size(), size));
        if (topIds.isEmpty()) {
            return Lists.newArrayList();
        }
        productCondition.setIds(getTopIds());
        final List<Long> orderedIds = topIds;
        List<Product> products = productService.queryWithImgs(productCondition, PageHelper.init(size, 1));
        Ordering<Product> productOrdering = Ordering.natural().onResultOf(ps -> {
            return orderedIds.indexOf(ps.getId());
        });
        return productOrdering.sortedCopy(products);
    }

}

Update Product Controller

Add the following code into ProductController.java file under the controller/src/main/java/co.dongchen.shop/controller directory:

1
2
    @Autowired
    private TopProductService topProductService;

Also replace the productInfo() method’s original content with the following one as well:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @RequestMapping("product/info")
    public String productInfo(Long id, ModelMap modelMap) {
        Product product = productService.getProductById(id);
        topProductService.increase(id);
        if (product.getUserId() != null && ! product.getUserId().equals(0L)) {
            modelMap.put("merchant", merchantService.getMerchantById(product.getUserId()));
        }
        List<Product> topProducts = topProductService.getTopProducts(5);
        modelMap.put("topProducts", topProducts);
        modelMap.put("product", product);
        return "/product/info";
    }

Update product/info.ftl in Controller Module

Replace the original content with the following one in product/info.ftl template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html lang="en-NZ">
<@common.header/>
<body>
${errorMsg!}
${successMsg!}

<h1>Product Info</h1>
<p>Product: ${(product.name)!}</p>
<h1>Merchant Info</h1>
<p>Merchant: ${(merchant.firstName)!} ${(merchant.lastName)!}</p>

<!-- NEWLY ADDED CODE BEGIN -->
<h1>Top Products</h1>
<ul>
    <#foreach topProduct in topProducts>
        <li>
            <p>${topProduct.name}</p>
        </li>
    </#foreach>
</ul>
<!-- NEWLY ADDED CODE END-->

</body>
<@common.footer/>
</html>

Verify

Run the App

1
2
// Short Cut for "Run the Program"
Alt + Shift + F10
img

Choose the correspondent option and press enter.

View Product Info Page in the Browser

1
http://localhost:8080/product/info?id=1

Visit products with id between 1-4, and we will get a similar result as follows:

img

Check in the Redis Command Line Terminal:

1
zrange TOP_PRODUCT_KEY 0 -1 withscores
img

References Redis 64-bit 3.0.503, Spring Boot Data Redis Starter, zincrby, OrderingExplained, natural(), onResultOf(Function)

Buy me a coffeeBuy me a coffee