添加商品同步到索引库

之前商品添加这个功能我们已经是完成了的,不过以现在的眼光看来,问题还是有,比如说在商品添加时,并没有同步索引库,它说的意思就是将新增商品数据从数据库中查询出来再导入到索引库中。因此,咱们在添加商品时需要与索引库进行同步,这样每添加一个商品,索引库就会多一个文档,这样做的好处是不用把数据库中的所有数据进行同步,大大提高了性能并且节约了时间。

其实,不仅仅是要在商品添加时需要同步索引库,而且还要在商品修改/删除的时候同步索引库,只不过在淘淘商城这个项目中,我们只实现了商品添加这一个功能,所以就只好在商品添加时同步索引库了。

现在我们来思考第一个问题,在淘淘商城这个项目中,消息的发送方和接收方分别应该对应哪个工程呢?这里我直接给出答案,消息的发送方所对应的应该是taotao-manager-service工程,消息的接收方所对应的应该是taotao-search-service工程。

再来思考第二个问题,在商品添加时应该发送什么类型的消息呢?我们要做的是在添加商品的时候发送ActiveMQ消息,至于发送什么类型的ActiveMQ消息则要根据实际应用场景来定,由于在淘淘商城这个项目中添加商品涉及到同步缓存、同步索引库以及添加静态页面等操作,也就是说一个消息要被多个消费者所消费,所以发送Topic消息是更为合适的。

下面,咱们就在消息的发送方(即taotao-manager-service工程)发送Topic消息。

首先,我们需要在taotao-manager-service工程中添加对ActiveMQ的maven依赖,如下图所示。

然后,我们需要在taotao-manager-service工程下新建一个关于ActiveMQ的配置文件,即applicationContext-activemq.xml,如下图所示。

为了方便大家复制,现将以上applicationContext-activemq.xml配置文件的内容贴出,如下所示。

<?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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsdhttp://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.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"><bean id="targetConnection" class="org.apache.activemq.ActiveMQConnectionFactory"><property name="brokerURL" value="tcp://192.168.81.135:61616"></property></bean><!-- 通用的connectionfacotry 指定真正使用的连接工厂 --><bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"><property name="targetConnectionFactory" ref="targetConnection"></property></bean><!-- 接收和发送消息时使用的类(Spring提供的JmsTemplate模板类)--><bean class="org.springframework.jms.core.JmsTemplate"><property name="connectionFactory" ref="connectionFactory"></property></bean><bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"><constructor-arg name="name" value="item-change-topic"></constructor-arg></bean>
</beans>

根据命名有意义原则,我们给消息起名为item-change-topic

接着,我们找到添加商品的ItemServiceImpl实现类,在该类中注入JmsTemplate和Topic,如下图所示。

紧接着,找到添加商品的方法,在添加完商品后发送消息。这里需要考虑一个问题,那就是消息的内容应该是什么?既然是添加商品,消费者肯定是要知道添加的商品是哪个的,同时本着简单的原则,我们只需要发送新增商品的ID即可,如下图所示。

为了方便大家复制,现将以上ItemServiceImpl实现类的代码贴出,如下所示。

package com.taotao.service.impl;import java.util.Date;
import java.util.List;import javax.annotation.Resource;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.taotao.common.pojo.EasyUIDataGridResult;
import com.taotao.common.pojo.TaotaoResult;
import com.taotao.common.utils.IDUtils;
import com.taotao.mapper.TbItemDescMapper;
import com.taotao.mapper.TbItemMapper;
import com.taotao.pojo.TbItem;
import com.taotao.pojo.TbItemDesc;
import com.taotao.pojo.TbItemExample;
import com.taotao.service.ItemService;@Service
public class ItemServiceImpl implements ItemService {@Autowiredprivate TbItemMapper tbItemMapper;@Autowiredprivate TbItemDescMapper itemDescMapper;@Autowiredprivate JmsTemplate jmsTemplate;@Resource(name="topicDestination")private Destination destination;@Overridepublic TbItem getItemById(Long itemId) {// 注入Mapper// 调用方法TbItem item = tbItemMapper.selectByPrimaryKey(itemId);// 返回TbItem对象return item;}@Overridepublic EasyUIDataGridResult getItemList(Integer page, Integer rows) {// 1. 设置分页的信息,使用PageHelperif (page == null) {page = 1;}if (rows == null) {rows = 30;}PageHelper.startPage(page, rows);// 2. 注入Mapper// 3. 创建一个TbItemExample对象,而且不需要设置查询条件TbItemExample example = new TbItemExample();// 4. 根据Mapper调用查询所有数据的方法List<TbItem> list = tbItemMapper.selectByExample(example);// 5. 获取分页信息PageInfo<TbItem> info = new PageInfo<TbItem>(list);// 6. 封装到EasyUIDataGridResult对象中并返回EasyUIDataGridResult result = new EasyUIDataGridResult();result.setTotal((int)info.getTotal());result.setRows(info.getList());return result;}@Overridepublic TaotaoResult addItem(TbItem item, String desc) {// 生成商品idlong itemId = IDUtils.genItemId();// 补全item对象的属性item.setId(itemId);// 商品状态,1:正常,2:下架,3:删除item.setStatus((byte) 1);item.setCreated(new Date());item.setUpdated(new Date());// 向商品表中插入数据tbItemMapper.insert(item);// 创建一个商品描述表对应的pojoTbItemDesc itemDesc = new TbItemDesc();// 补全pojo的属性itemDesc.setItemId(itemId);itemDesc.setItemDesc(desc);itemDesc.setCreated(new Date());itemDesc.setUpdated(new Date());// 向商品描述表中插入数据itemDescMapper.insert(itemDesc);// 添加一个发送消息的业务逻辑jmsTemplate.send(destination, new MessageCreator() {@Overridepublic Message createMessage(Session session) throws JMSException {// 发送消息,发送的消息的内容就是商品idreturn session.createTextMessage(itemId + "");}});// 返回结果return TaotaoResult.ok();}@Overridepublic TbItemDesc getItemDescById(Long itemId) {return itemDescMapper.selectByPrimaryKey(itemId);}}

发送完Topic消息之后,接下来咱们就要在消息的接收方(即taotao-search-service工程)接收Topic消息了。

首先,同样需要在taotao-search-service工程中添加对ActiveMQ的maven依赖,如下图所示。

然后,就是修改SearchItemMapper.xml映射文件,添加一个根据商品ID来查询商品详情数据的方法,如下图所示。

为了方便大家复制,现将以上SearchItemMapper.xml映射文件的内容贴出,如下图所示。

<?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="com.taotao.search.mapper.SearchItemMapper" ><select id="getSerarchItemList" resultType="com.taotao.common.pojo.SearchItem">SELECTa.id,a.title,a.image,a.price,a.sell_point,b.`name` as category_name,c.item_descFROMtb_item a,tb_item_cat b,tb_item_desc c WHEREa.cid = b.id AND a.id = c.item_id</select>    <select id="getSearchItemById" parameterType="long" resultType="com.taotao.common.pojo.SearchItem">SELECTa.id,a.title,a.image,a.price,a.sell_point,b.`name` as category_name,c.item_descFROMtb_item a,tb_item_cat b,tb_item_desc c WHEREa.cid = b.id AND a.id = c.item_idAND a.id = #{itemId}</select>
</mapper>

接着,在SearchItemMapper接口中声明一个根据商品ID查询商品详情数据的方法,如下图所示。

紧接着,在SearchDao类中添加一个根据商品ID查询商品详情数据,并更新到索引库中去的方法。

为了方便大家复制,现将以上SearchDao类的代码贴出,如下图所示。

package com.taotao.search.dao;import java.util.ArrayList;
import java.util.List;
import java.util.Map;import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import com.taotao.common.pojo.SearchItem;
import com.taotao.common.pojo.SearchResult;
import com.taotao.common.pojo.TaotaoResult;
import com.taotao.search.mapper.SearchItemMapper;/** 从索引库中搜索商品的dao*/
@Repository
public class SearchDao {@Autowiredprivate SolrClient solrClient;@Autowiredprivate SearchItemMapper mapper;/*** 根据查询的条件查询商品的结果集* @param query* @return* @throws Exception*/public SearchResult search(SolrQuery query) throws Exception {SearchResult searchResult = new SearchResult();// 1. 创建SolrClient对象,它是由Spring容器进行管理的// 2. 直接执行查询QueryResponse response = solrClient.query(query);// 3. 获取结果集SolrDocumentList results = response.getResults();// 设置SearchResult对象中的recordCount属性,即总记录数searchResult.setRecordCount(results.getNumFound());// 4. 遍历结果集// 定义一个集合List<SearchItem> itemList = new ArrayList<SearchItem>();// 取高亮Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();for (SolrDocument solrDocument : results) {// 将SolrDocument对象中的属性一个个地设置到SearchItem对象中SearchItem item = new SearchItem();item.setCategory_name(solrDocument.get("item_category_name").toString());item.setId(Long.parseLong(solrDocument.get("id").toString()));item.setImage(solrDocument.get("item_image").toString());// item.setItem_desc(item_desc);item.setPrice((Long) solrDocument.get("item_price"));item.setSell_point(solrDocument.get("item_sell_point").toString());// 一般标题要高亮显示List<String> list = highlighting.get(solrDocument.get("id").toString()).get("item_title");// 判断list是否为空String gaoliangstr = "";if (list != null && list.size() > 0) {// 有高亮(标题高亮)gaoliangstr = list.get(0);} else {gaoliangstr = solrDocument.get("item_title").toString();}item.setTitle(gaoliangstr);// 然后再将SearchItem对象封装到SearchResult对象中的itemList属性中itemList.add(item);}// 5. 设置SearchResult对象的属性searchResult.setItemList(itemList);return searchResult;}/*** 更新索引库* @param itemId* @return* @throws Exception*/public TaotaoResult updateSearchItemById(Long itemId) throws Exception {// 注入Mapper// 查询到记录SearchItem item = mapper.getSearchItemById(itemId);// 把记录更新到索引库// 1)创建一个SolrClient对象,这个对象是注入进来的// 2)创建SolrInputDocument对象SolrInputDocument document = new SolrInputDocument();// 3)向文档对象中添加域document.setField("id", item.getId().toString()); // 这里是字符串,所以需要转换document.setField("item_title", item.getTitle());document.setField("item_sell_point", item.getSell_point());document.setField("item_price", item.getPrice());document.setField("item_image", item.getImage());document.setField("item_category_name", item.getCategory_name());document.setField("item_desc", item.getItem_desc());// 4)向索引库中添加文档solrClient.add(document);// 5)提交solrClient.commit();return TaotaoResult.ok();}}

这就相当于咱们开发好了dao层中的代码。注意,待会我要启动的是单机版的Solr服务器,因此要保证applicationContext-solr.xml配置文件当前切换到Solr单机版,如下图所示。

开发完dao层,理所当然地,咱们还要接着开发service层,先在SearchService接口中声明一个根据商品ID查询商品详情数据,并更新到索引库中去的方法。

再在该接口的实现类(即SearchServiceImpl类)中去实现以上方法。

接下来,我们需要创建一个接收消息的监听器类,并在该类中处理同步索引库的逻辑,如下图所示。

为了方便大家复制,现将以上ItemChangeMessageListener监听器类的代码贴出,如下所示。

package com.taotao.search.listener;import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;import org.springframework.beans.factory.annotation.Autowired;
import com.taotao.search.service.SearchService;/*** 接收消息的监听器* @author liayun**/
public class ItemChangeMessageListener implements MessageListener {// 注入service,直接调用方法更新索引库即可@Autowiredprivate SearchService searchService;@Overridepublic void onMessage(Message message) {try {TextMessage textMessage = null;Long itemId = null;// 判断消息的类型是否为TextMessageif (message instanceof TextMessage) {// 如果是,那么获取商品的idtextMessage = (TextMessage) message;itemId = Long.parseLong(textMessage.getText());}// 通过商品的id查询数据,需要开发Mapper,通过商品的id查询商品的数据时,返回的是搜索时的商品数据,即SearchItem// 更新索引库searchService.updateSearchItemById(itemId);} catch (Exception e) {e.printStackTrace();}}  }

编写完监听器之后,我们还要再配置下接收的Topic(要与taotao-manager-service当中配置的Topic一致)以及刚刚编写好的监听器,如下图所示。

为了方便大家复制,现将以上applicationContext-activemq.xml配置文件的内容贴出,如下所示。

<?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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsdhttp://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.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"><bean id="targetConnection" class="org.apache.activemq.ActiveMQConnectionFactory"><property name="brokerURL" value="tcp://192.168.81.135:61616"></property></bean><!-- 通用的connectionfacotry 指定真正使用的连接工厂 --><bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"><property name="targetConnectionFactory" ref="targetConnection"></property></bean><!-- 这个是主题目的地,一对多的 --><bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"><constructor-arg name="name" value="item-change-topic"></constructor-arg></bean><!-- 配置消息监听器 --><bean id="itemChangeMessageListener" class="com.taotao.search.listener.ItemChangeMessageListener" /><bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"><property name="connectionFactory" ref="connectionFactory" /><property name="destination" ref="topicDestination" /><property name="messageListener" ref="itemChangeMessageListener" /></bean></beans>

下面咱们就要开始尝试着添加一个商品进行测试了。不过在开始测试之前,咱还得做一些准备工作。首先,我们需要把学习全局异常处理时人为添加的异常给注释掉,如下图所示。

然后,启动如下服务器,也就是启动那些虚拟机。

接着,按照如下顺序依次启动这些maven工程,最好是先启动服务层的工程,再启动表现层的工程。

启动成功之后,我们在浏览器地址栏中输入http://localhost:8081来访问淘淘商城后台管理系统,添加一款商品,这里我添加的是苹果手机iPhone6s,如下图所示。

添加完商品后,我们到淘淘商城首页进行搜索,看能不能搜索到我们刚才添加的手机,如下图所示,发现可以正常搜索到刚才添加的苹果手机!!说明我们的消息机制没问题。

这样就实现商品数据的索引库和数据库的同步了。

你有可能遇到的一个空指针异常

问题描述

如果你是第一次启动各个工程,并且第一次尝试着添加一个商品并同步到索引库中去时,那么有很大可能会遇到一个空指针异常,就像下面这样。

为什么会这样呢?

报错原因

看一下我们在消息的发送方(即taotao-manager-service工程)是如何发送Topic消息的,如下图所示。

当我们在这里面来发送消息的时候,一旦商品插入成功,我们是把商品id发送过去了,那假设有这样一个问题,消息是在addItem方法执行之前发送的,会出现什么情况呢?内容为商品id的消息是发送过去了,但是此时数据库里面是没有新增商品数据的,因为这是在service层,addItem方法是一个事务,而事务在没有完成之前,是不会提交的,不会提交,如果你这个时候发送了一条内容为商品id的消息过去,消息的接收方能查询得到这个商品吗?显然查询不到,查不到就会报一个空指针异常。

解决方案

既然在事务还没有提交之前消息就发送了,那么我们能不能想个法子,就是在事务提交之后再发送消息呢?对不对!法子是有,而且还有两个,下面我一一道来。

解决方案一

在service层中另外定义一个方法,该方法是没有事务的,然后在该方法中先调用新增商品的方法(就是addItem方法啦!),等执行成功之后,再发送消息,如此一来便可解决,而且推荐使用这种方式。

解决方案二

在controller中先调用服务,等到服务调用成功后,再发送消息,如此一来便可解决。

可以看到我在尝试着添加一个商品进行测试时,并没有遇到我所说的空指针异常,这有可能是新增商品时的逻辑执行的比较快,而消息发送执行的比较慢。

淘淘商城第75讲——添加商品同步到索引库以及消息机制测试相关推荐

  1. 淘淘商城第50讲——导入商品数据到索引库时,报错:org.apache.solr.client.solrj.impl.HttpSolrClient$RemoteSolrException

    问题描述 昨天碰到了一个问题,真的是把快我搞死了,导致我代码写下去的勇气都没有了,最后大爷我干脆不写了,我躺着睡觉还不行吗

  2. (转)淘淘商城系列——导入商品数据到索引库

    http://blog.csdn.net/yerenyuan_pku/article/details/72902073 上文我们把商品数据导入到索引库中的Service层代码编写完了,本文我们将再来把 ...

  3. (转)淘淘商城系列——导入商品数据到索引库——Service层

    http://blog.csdn.net/yerenyuan_pku/article/details/72894187 通过上文的学习,我相信大家已经学会了如何使用Solrj来操作索引库.本文我们将把 ...

  4. 淘淘商城第106讲——改造商品详情页面中的加入购物车板块

    在上一讲中,我就说过,关于购物车模块,之前的京东和淘宝并不一样,之前的京东允许用户在没有登录的情况下就添加商品进购物车,而且加到购物车里面的商品可以一直保存着.其实这是将购物车信息写入到了Cookie ...

  5. 淘淘商城第77讲——实现商品详情页面展示

    我相信大家通过上文的学习已经搭建好了商品详情页面展示工程,本文我将带领大家一起实现商品详情页面的展示. 首先我们来看一下商品详情页面的内容,可以看到商品一般属性在TbItem实体类中都是存在的,只是图 ...

  6. 淘淘商城第86讲——实现商品详情页面静态化方案时,你没遇到过java.lang.IllegalArgumentException或者java.lang.NullPointerException这种异常

    问题描述 今儿个,我在实现商品详情页面静态化方案时,遇到了一个蛮奇怪的异常,为什么说蛮奇怪呢?因为它只在第一次测试的时候出现过,后面就再也没出现过了. 我先描述一下这个异常是怎么出现的,我在淘淘商城后 ...

  7. (转)淘淘商城系列——导入商品数据到索引库——dao层

    http://blog.csdn.net/yerenyuan_pku/article/details/72889058 我们先来看看我们要导入数据的sql语句并且查看查询结果.  从上图可知我们需要从 ...

  8. 淘淘商城第78讲——查询商品详情添加缓存的分析

    通过上文的学习,我相信大家一定实现了商品详情页面的展示,接下来我们将学习如何在商品详情页面展示时添加缓存. 因为查询商品详情涉及到查询数据库,当商品详情页面的访问的并发量比较高时,查询商品详情都去查询 ...

  9. 淘淘商城第24讲——实现商品类目的选择

    教员讲过:"会当水击三千里,自信人生两百年".当我们实现商品列表查询这个功能之后,我相信大家都信心倍增,但这只是走完万里长征的第一步,后面还有嵩山峻岭等着我们去攀登,所以千万不可懈 ...

  10. 一个淘淘商城项目送给你,愿你有一个灿烂的前程!

    写在前面 今天是2020年6月23日,星期二,天气晴.2020年已过一半,回想一下,这真是一个多灾多难的年份啊!可生活依然要继续,活着的人依然要继续前行.这是没道理的事情. 好了,回到主题,说说写这篇 ...

最新文章

  1. phpnow升级mysql版本_PHPnow 升级后 PHP不支持GD、MySQL 枫
  2. Qt Mac 桌面版本编译出错
  3. linux的笔画动态加载,关于Android中GestureOverlayView多笔画的问题
  4. redis和kafka读取代码
  5. Tomcat启动报错整理
  6. CodeBlocks 导航栏/输出栏/菜单栏消失
  7. 在idea 中添加和删除模块Module
  8. 梯度的直观理解_BP反向传播算法的思考和直观理解 -卷积小白的随机世界
  9. [07-01]http网页提示含义
  10. vb.net 画多个矩形_电气原理图和接线图识图方法,电气接线图怎么画?你会画吗?...
  11. 运行VS2008提示找不到一个或多个组件,请重新安装该应用程序错误的解决方法V
  12. 分享一个MentoHUST for Windows 锐捷认证使用方法,实现不用猎豹wifi第三方流氓软件破解校园网wifi共享限制。
  13. 关于复数i本质的探讨
  14. SAP 金额等负号提前问题
  15. 蓝桥杯-第六届省赛第一题
  16. 千万级并发实现的秘密:内核不是解决方案而是问题所在!
  17. java工具类 - word内容文本替换
  18. PCB----阻抗计算
  19. Android 镜像
  20. xunsearch开发流程(三)

热门文章

  1. 如何提高测试用例评审效率?
  2. cents7 mysql数据库安装和配置
  3. 什么是RST包,什么是三次握手,什么是四次握手 ---请进
  4. 华为ENSP进行evn实验,尚不完整,但已经有RT1、RT2、RT3、RT4了
  5. Kafka 客户端 org.apache.kafka:kafka-clients:2.4.1
  6. 转载:BGA封装芯片手工焊接攻略
  7. 笔记——跟熊浩学沟通
  8. vue 使用箭头函数会报错
  9. 自从看了<<麦肯锡的领导力法则>>之后......
  10. 对校招生培养工作的建议_贵单位对我校学生培养工作有何建议