Fork me on GitHub

九.宜立方商城——Activemq同步商品索引和freemark

一.课程计划

1
2
3
4
1、Activemq整合springMQ的应用场景
2、添加商品同步索引库
3、商品详情页面动态展示
4、展示详情页面使用缓存

二.Activemq整合spring

2.1 使用方法

第一步:引用相关的jar包。

image

image

1
2
3
4
5
6
7
8
      <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>

第二步:配置Activemq整合spring。配置ConnectionFactory

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">


<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.168:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>
</beans>
第三步:配置生产者。
使用JMSTemplate对象。发送消息。
第四步:在spring容器中配置Destination。

image

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd">

<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>

<!-- 配置生产者 -->
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!--这个是队列目的地,点对点的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>spring-queue</value>
</constructor-arg>
</bean>
<!--这个是主题目的地,一对多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic" />
</bean>
</beans>
第五步:代码测试
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
package yp.e3mall.activemq;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;

/**
* @author RickYinPeng
* @ClassName ActiveMqString
* @Description
* @date 2018/11/29/20:46
*/
public class ActiveMqString {

@Test
public void sendMessage() throws Exception{
//初始化spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml");
//从容器中获得JmsTemplate对象
JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class);
//从容器中获得一个Destination对象
Destination queueDestination = (Destination) applicationContext.getBean("queueDestination");
//发送消息
jmsTemplate.send(queueDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("send activemq messgae");
}
});
}
}

image

image

image

image

2.2 代码测试

2.2.1 发送消息


第一步:初始化一个spring容器

第二步:从容器中获得JMSTemplate对象。

第三步:从容器中获得一个Destination对象

第四步:使用JMSTemplate对象发送消息,需要知道Destination

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
/**
* @author RickYinPeng
* @ClassName ActiveMqString
* @Description
* @date 2018/11/29/20:46
*/
public class ActiveMqString {

@Test
public void sendMessage() throws Exception{
//初始化spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml");
//从容器中获得JmsTemplate对象
JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class);
//从容器中获得一个Destination对象
Destination queueDestination = (Destination) applicationContext.getBean("queueDestination");
//发送消息
jmsTemplate.send(queueDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("send activemq messgae");
}
});
}
}

2.2.2 接收消息

e3-search-Service中接收消息。

第一步:把Activemq相关的jar包添加到工程中


image

第二步:创建一个MessageListener的实现类。

image

image

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author RickYinPeng
* @ClassName MyMessageListener
* @Description
* @date 2018/11/29/21:46
*/
public class MyMessageListener implements MessageListener{
@Override
public void onMessage(Message message) {
//取消息内容
TextMessage textMessage = (TextMessage) message;
try {
String text = ((TextMessage) message).getText();
System.out.println(text);
} catch (JMSException e) {
e.printStackTrace();
}

}
}

image

第三步:配置spring和Activemq整合。

image

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd">

<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>

<!--这里我们配置消费者,所以生产者我们就注释掉-->
<!--&lt;!&ndash; 配置生产者 &ndash;&gt;
&lt;!&ndash; Spring提供的JMS工具类,它可以进行消息发送、接收等 &ndash;&gt;
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
&lt;!&ndash; 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 &ndash;&gt;
<property name="connectionFactory" ref="connectionFactory" />
</bean>-->

<!--这个是队列目的地,点对点的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>spring-queue</value>
</constructor-arg>
</bean>
<!--这个是主题目的地,一对多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic" />
</bean>
<!-- 接收消息 -->
<!-- 配置监听器 -->
<bean id="myMessageListener" class="yp.e3mall.search.message.MyMessageListener" />

<!-- 消息监听容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="myMessageListener" />
</bean>
</beans>

image

第四步:测试代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author RickYinPeng
* @ClassName MessageConsumer
* @Description
* @date 2018/11/29/21:58
*/
public class MessageConsumer {

@Test
public void msgConsumer() throws Exception{
//初始化spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml");

//等待
System.in.read();

}
}

三.添加商品同步索引库

image

image

image

3.1 Consumer(我们先来写接受者代码)

3.1.1 功能分析

  1. 接收消息。需要创建MessageListener接口的实现类。
  2. 取消息,取商品id。
  3. 根据商品id查询数据库。
  4. 创建一SolrInputDocument对象。
  5. 使用SolrServer对象写入索引库。返回成功,返回e3Result。

3.1.2 Dao层

image

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getItemById" parameterType="long" resultType="yp.e3mall.common.pojo.SearchItem">
select
a.id,
a.title,
a.sell_point,
a.price,
a.image,
b.name category_name
from tb_item a
LEFT JOIN tb_item_cat b
on a.cid = b.id
where a.status = 1
AND a.id=#{itemid};
</select>

3.1.3 监听器(Service)

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
/**
* @author RickYinPeng
* @ClassName ItemAddMessageListener
* @Description 监听商品添加事件,接受消息后,将对应的商品信息同步到索引库
* @date 2018/11/30/12:14
*/
public class ItemAddMessageListener implements MessageListener {

@Autowired
private ItemMapper itemMapperp;

@Autowired
private SolrServer solrServer;

@Override
public void onMessage(Message message) {
try {
System.out.println("接受到消息,并同步索引库!!!");
//从消息中取商品id
TextMessage textMessage = (TextMessage) message;

String text = textMessage.getText();
long id = Long.valueOf(text);

//根据商品id查询商品信息
SearchItem searchItem = itemMapperp.getItemById(id);

//创建文档对象
SolrInputDocument document = new SolrInputDocument();
//向文档对象中添加域
document.addField("id", searchItem.getId());
System.out.println(searchItem.getId());
document.addField("item_title", searchItem.getTitle());
System.out.println(searchItem.getTitle());
document.addField("item_sell_point", searchItem.getSell_point());
System.out.println(searchItem.getSell_point());
document.addField("item_price", searchItem.getPrice());
System.out.println(searchItem.getPrice());
document.addField("item_image", searchItem.getImage());
System.out.println(searchItem.getImage());
document.addField("item_category_name", searchItem.getCategory_name());

//把文档对象写入索引库
solrServer.add(document);

//提交
solrServer.commit();

} catch (Exception e) {
e.printStackTrace();
}
}
}

image

3.1.4 Spring配置监听

image

image

3.2 Producer


e3-manager-server工程中发送消息。

当商品添加完成后发送一个TextMessage,包含一个商品id。

image

上图中使用topic来作为目的地,itemAddTopic相当于是发送到哪个队列

image

image

image

image

所以我们之前接受者的代码需要加上Thread.sleep(1000)

具体发送消息代码实现:

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
@Override
public E3Result addItem(TbItem tbItem, String desc) {
//拿到的TbItem缺少ID,因为表单中的是cid,是商品的类目id,并不是商品的ID
//所以我们需要先生成商品的ID

//生成商品ID
long itemId = IDUtils.genItemId();

//补全TbItem的属性
tbItem.setId(itemId);
//商品状态,1-正常,2-下架,3-删除
tbItem.setStatus((byte)1);
//创建时间
tbItem.setCreated(new Date());
//更新时间
tbItem.setUpdated(new Date());

//向商品表插入数据
tbItemMapper.insert(tbItem);

//创建商品描述表对应的pojo对象
TbItemDesc itemDesc = new TbItemDesc();
itemDesc.setItemId(itemId);
itemDesc.setItemDesc(desc);
itemDesc.setCreated(new Date());
itemDesc.setUpdated(new Date());

//向商品描述表插入数据
tbItemDescMapper.insert(itemDesc);

//发送商品添加消息,需要注入jmsTemple
jmsTemplate.send(topDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
session.createTextMessage(itemId+"");
return null;
}
});

//返回成功
return E3Result.ok();
}

四.商品详情页面展示

创建一个商品详情页面展示的工程。是一个表现层工程。(参考e3_portal_web)

4.1 工程搭建

e3-item-web。打包方式war。可以参考e3-portal-web

image

image

image

image

image

image

image

image

4.2 功能分析

在搜索结果页面点击商品图片或者商品标题,展示商品详情页面。

image

image


请求的url:/item/{itemId}

参数:商品id

返回值:String 逻辑视图

业务逻辑:

1、从url中取参数,商品id

2、根据商品id查询商品信息(tb_item)得到一个TbItem对象,缺少images属性,可以创建一个pojo继承TbItem,添加一个getImages方法。在e3-item-web工程中。

image

image

image

image

image

image

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
/**
* @author RickYinPeng
* @ClassName Item
* @Description
* @date 2018/12/2/14:08
*/
public class Item extends TbItem{

//因为父类不能强制转换为子类,所以我们得写一个构造方法,将值复制过来
//因为我们查出来的是一个tbItem对象,这个对象不能直接转换为子类
//所以我们在构造子类的时候将这个父类对象传入,然后将其属性复制到该子类中即可
public Item(TbItem tbItem){
this.setId(tbItem.getId());
this.setTitle(tbItem.getTitle());
this.setSellPoint(tbItem.getSellPoint());
this.setPrice(tbItem.getPrice());
this.setNum(tbItem.getNum());
this.setBarcode(tbItem.getBarcode());
this.setImage(tbItem.getImage());
this.setCid(tbItem.getCid());
this.setStatus(tbItem.getStatus());
this.setCreated(tbItem.getCreated());
this.setUpdated(tbItem.getUpdated());
}


public String [] getImages(){
String image = this.getImage();
if(image!=null && !"".equals(image)){
String[] split = image.split(",");
return split;
}
return null;
}

}

image

image


3、根据商品id查询商品描述。

4、展示到页面。

4.3 Dao层

查询tb_item, tb_item_desc两个表,都是单表查询。可以使用逆向工程。

image

image

image

image

image

image

4.4 Service层

思考一下,我们的Service应该在哪实现

我们这个项目是个分布式项目,Service为服务层,我们现在需要商品信息是不是调用商品信息的Service即可,商品信息的Service是Manager工程中的,所以我们去看manager工程

1、根据商品id查询商品信息

参数:商品id

返回值:TbItem

2、根据商品id查询商品描述

参数:商品id

返回值:TbItemDesc

4.5 Controller层


请求的url:/item/{itemId}

参数:商品id

返回值:String 逻辑视图

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
/**
* @author RickYinPeng
* @ClassName ItemController
* @Description 商品详情页面展示Controller
* @date 2018/12/2/14:58
*/
@Controller
public class ItemController {

@Autowired
private ItemService itemService;

@RequestMapping("/item/{itemId}")
public String showItemInfo(@PathVariable long itemId, Model model){
//调用服务取商品基本信息
TbItem tbItem = itemService.getItemById(itemId);
Item item = new Item(tbItem);

//去商品描述信息
TbItemDesc itemDesc = itemService.getItemDescById(itemId);

//把信息传递给页面
model.addAttribute("item",item);
model.addAttribute("itemDesc",itemDesc);

//返回逻辑视图
return "item";
}
}

image

4.6 向业务逻辑中添加缓存

image

image

4.6.1 缓存添加分析


使用redis做缓存。

业务逻辑:

1、根据商品id到缓存中命中

2、查到缓存,直接返回。

3、差不到,查询数据库

4、把数据放到缓存中

5、返回数据


缓存中缓存热点数据,提供缓存的使用率。需要设置缓存的有效期。一般是一天的时间,可以根据实际情况跳转。

需要使用String类型来保存商品数据。

可以加前缀方法对象redis中的key进行归类。

ITEM_INFO:123456:BASE

ITEM_INFO:123456:DESC


image

image


如果把二维表保存到redis中:

1、表名就是第一层

2、主键是第二层

3、字段名第三次

三层使用“:”分隔作为key,value就是字段中的内容。

4.6.2 把redis相关的jar包添加到工程

4.6.3 添加缓存

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
@Override
public TbItem getItemById(long itemId) {
//查询缓存
try {
String json = jedisClient.get(REDIS_ITEM_PRE + ":" + itemId + ":BASE");
if(StringUtils.isNotBlank(json)){
TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class);
return tbItem;
}
}catch (Exception e){
e.printStackTrace();
}
//缓存中没有,查询数据库

TbItem tbItem = tbItemMapper.selectByPrimaryKey(itemId);
if(tbItem!=null){
//将结果添加到缓存
try {
jedisClient.set(REDIS_ITEM_PRE+":"+itemId+":BASE", JsonUtils.objectToJson(tbItem));
//设置过期时间
jedisClient.expire(REDIS_ITEM_PRE+":"+itemId+":BASE",ITEM_CACHE_EXPIRE);
}catch (Exception e){
e.printStackTrace();
}
return tbItem;
}

return null;
}

@Override
public TbItemDesc getItemDescById(long itemId) {
//查询缓存
try {
String json = jedisClient.get(REDIS_ITEM_PRE+":"+itemId+":DESC");
if(StringUtils.isNotBlank(json)){
TbItemDesc itemDesc = JsonUtils.jsonToPojo(json, TbItemDesc.class);
return itemDesc;
}
}catch (Exception e){
e.printStackTrace();
}
//缓存中没有,查询数据库

TbItemDesc itemDesc = tbItemDescMapper.selectByPrimaryKey(itemId);
if(itemDesc!=null){
//将结果添加到缓存
try {
jedisClient.set(REDIS_ITEM_PRE+":"+itemId+":DESC", JsonUtils.objectToJson(itemDesc));
//设置过期时间
jedisClient.expire(REDIS_ITEM_PRE+":"+itemId+":DESC",ITEM_CACHE_EXPIRE);
}catch (Exception e){
e.printStackTrace();
}
return itemDesc;
}
return null;
}

image

image