• 知识点8--SSM项目整合redis、kafka、es以及整合es高亮


    本篇将使用Linux集群,如果没有的可以看我的集群安装文档,见博客。

    首先是Redis,我们用它二次提升首页的效率,将栏目这个基本不发生变化的数据放在Redis中。第一步我们要配置Redis的Spring文件

    
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang"
    	xmlns:jms="http://www.springframework.org/schema/jms" xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:cache="http://www.springframework.org/schema/cache"
    	xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:oxm="http://www.springframework.org/schema/oxm"
    	xmlns:task="http://www.springframework.org/schema/task" xmlns:tool="http://www.springframework.org/schema/tool"
    
    	xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
            http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
            http://www.springframework.org/schema/jms
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
            http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm.xsd
            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
            http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd
            http://www.springframework.org/schema/websocket">
            
              
              <bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer">bean>
              
              <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer">bean>
              
              <bean id="jackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
              	<constructor-arg value="java.lang.Object">constructor-arg>
              bean>
              
              
            
            <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
            	
            	
            	<property name="connectionFactory" ref="connectionFactory">property>
            	
            	<property name="keySerializer" ref="stringRedisSerializer">property>
            	<property name="valueSerializer" ref="jdkSerializationRedisSerializer">property>
            	
            	<property name="hashKeySerializer" ref="stringRedisSerializer">property>
            	<property name="hashValueSerializer" ref="jdkSerializationRedisSerializer">property>
            bean>
            
            
            
            <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            	
            	<property name="hostName" value="192.168.88.188">property>
            	
            	<property name="port" value="6379">property>
            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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    随后在spring配置文件中加载它

    <!-- redis配置文件 -->
    <import resource="redis.xml"/>
    
    • 1
    • 2

    最后我们更改首页Controller修改修改它的业务逻辑,使得所以栏目和最新五条文章的查询先从Redis中查询

    @Autowired
    private RedisTemplate redisTemplate;
    
    /**
     *
     * @Title: index
     * @Description: 进入首页
     * @return
     * @return: String
     */
    @RequestMapping("index.do")
    public String index(Model model,Article article,@RequestParam(defaultValue="1")Integer pageNum) {
    	//封装查询条件
    	model.addAttribute("article", article);
    
    	//使用线程
    	Thread t1;
    	Thread t2;
    	Thread t3;
    	Thread t4;
    	//查询所有的栏目,该线程为必须品
    	t1=new Thread(new Runnable() {
    		@Override
    		public void run() {
    			//查询所有的栏目:使用redis优化
    			List<Channel> channels = (List<Channel>) redisTemplate.opsForValue().get("channels");
    			if(channels==null){
    				//如果redis数据库中没有那么在重mysql中查询并放入redis
    				channels = channelService.selects();
    				redisTemplate.opsForValue().set("channels", channels);
    
    				//你也可以这样写  意为五分钟后数据失效
    				//redisTemplate.opsForValue().set("channels", channels ,5 , TimeUnit.MINUTES);
    			}
    			model.addAttribute("channels", channels);
    		}
    	});
    
    	// 判断栏目ID 不为空 也就是说当前不是查询热点那么就要查询其下分类
    	t2=new Thread(new Runnable() {
    
    		@Override
    		public void run() {
    			//线程2这里也可以用redis优化,减去不停切换栏目对后台的压力
    			if(article.getChannelId()!=null){
    				List<Category> categorys = channelService.selectsByChannelId(article.getChannelId());
    				model.addAttribute("categorys", categorys);
    			}else{
    				//如果栏目id是空的那么就代表这查询的是热点,并为为热点查询广告
    				List<Slide> slides = slideService.getAll();
    				model.addAttribute("slides", slides);
    				//限制查询热点文章
    				article.setHot(1);
    			}
    		}
    	});
    
    	//前两个线程决定查什么文章,第三个线程正式查文章
    	t3=new Thread(new Runnable() {
    
    		@Override
    		public void run() {
    			try {
    				t2.join();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			//无论是什么情况控制查询文章不是被逻辑删除的
    			article.setDeleted(0);
    			//不能查询非审核之后的文章
    			article.setStatus(1);
    			//查询符合条件的所有文章
    			List<Article> selectArticle = articleService.selectArticle(article, pageNum, 6);
    			PageInfo info=new PageInfo<>(selectArticle);
    			model.addAttribute("info", info);
    		}
    
    	});
    
    	//为首页查询五条最新的文章
    	t4=new Thread(new Runnable() {
    
    		@Override
    		public void run() {
    			//	封装该查询条件
    			List<Article> newArticles=(List<Article>) redisTemplate.opsForValue().get("newArticles");
    			if(newArticles==null){
    				Article latest = new Article();
    				latest.setDeleted(0);
    				//不能查询非审核之后的文章
    				latest.setStatus(1);
    				//如果redis数据库中没有那么在重mysql中查询并放入redis
    				newArticles = articleService.selectArticle(latest, 1, 5);
    				redisTemplate.opsForValue().set("newArticles", newArticles);
    			}
    			PageInfo lastArticles=new PageInfo<>(newArticles);
    			model.addAttribute("lastArticles", lastArticles);
    		}
    	});
    
    	//启动线程并保证线程顺序
    	t1.start();
    	t2.start();
    	t3.start();
    	t4.start();
    	try {
    		t1.join();
    		t3.join();
    		t4.join();
    	} catch (InterruptedException e) {
    		e.printStackTrace();
    	}
    
    	return "index/index";
    }
    
    • 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

    运行项目看效果,首先第一次查询用了2秒多,这很正常后台首次查询需要对redis做数据存储
    在这里插入图片描述
    第二次恢复毫秒
    在这里插入图片描述
    但是这里又出现一个bug,最新文章没了,通过排查发现是子栏目Bean没有实现系列化接口,导致封装五条最新文章的时候子类目异常,改过来后页面展示正常
    在这里插入图片描述
    去服务器后台查看redis,你会发现有运行是推送的key
    在这里插入图片描述
    至此Redis如何进行SSM整合就说完了,为了进行下一步开发你需要把redis相关暂时注释掉


    下面我们整合kafka,使用它统计文章的被点击量。首先我们需要整合kafka的ssm配置。我们先说kafka的生产者producer

    
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:context="http://www.springframework.org/schema/context"
    	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.xsd
    	    http://www.springframework.org/schema/context
    	    http://www.springframework.org/schema/context/spring-context.xsd">
    	    
    	
    	<bean id="producerProperties" class="java.util.HashMap">
    		<constructor-arg>
    			<map>
    				
    				<entry key="bootstrap.servers" value="192.168.88.186:9092,192.168.88.187:9092,192.168.88.188:9092" />
    				
    				
    				<entry key="retries" value="0" />
    				
    				<entry key="batch.size" value="1638" />
    				
    				<entry key="linger.ms" value="1" />
    				
    				
    				<entry key="buffer.memory" value="33554432 " />
    				
    				<entry key="key.serializer"
    					value="org.apache.kafka.common.serialization.StringSerializer" />
    					
    				<entry key="value.serializer"
    					value="org.apache.kafka.common.serialization.StringSerializer" />
    			map>
    		constructor-arg>
    	bean>
    
    	
    	<bean id="producerFactory" class="org.springframework.kafka.core.DefaultKafkaProducerFactory">
    		
    		<constructor-arg>
    			<ref bean="producerProperties" />
    		constructor-arg>
    	bean>
    
    	
    	<bean id="kafkaTemplate" class="org.springframework.kafka.core.KafkaTemplate">
    	
    		<constructor-arg ref="producerFactory" />
    		
    		<property name="defaultTopic" value="wy" />
    	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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    随后在spring中加载这个配置

    <!-- kafka:生产者 -->
    <import resource="producer.xml"/>
    
    • 1
    • 2

    在首页Controller中注入生产者的Bean,且在详情中编写代码,使得每次被点击都向kafka集群发送一条记录,当然我们这里不做计算,通常点击量也不是后端人员干的,是大数据开发人员用Spark等手段处理好结果会提供回数据库

    @Autowired
    private KafkaTemplate kafkaTemplate;
    
    /**
     *
     * @Title: detail
     * @Description: 文章详情
     * @param id
     * @return
     * @return: String
     */
    @RequestMapping("detail.do")
    public String detail(Model model, Integer id, HttpSession session, @RequestParam(defaultValue="1")Integer page) {
    	//查询文章
    	Article article = articleService.select(id);
    	model.addAttribute("article", article);
    
    	//查询文章是否被当前用户收藏
    	// 前提:如果用户已经登录则查询是否收藏
    	User user=(User) session.getAttribute("user");
    	if (null != user) {
    		int isCollect = collectService.selectCount(article.getTitle(), user.getId());
    		model.addAttribute("isCollect", isCollect);
    	}
    
    	//查询评论
    	PageInfo<Comment> info = commentService.selects(id, page, 5);
    	model.addAttribute("info", info);
    
    	//发出信息 :文章id,1
    	kafkaTemplate.send("wy",id+","+1);
    
    	return "index/article";
    }
    
    • 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

    集群后台准备好一个消费者,然后运行项目点击首页的文章,看结果
    在这里插入图片描述


    随后返回头我们说消费者,SSM里使用消费者一般很少,而且用也不会和web业务模块在同一项目下,总之SSM整合kafka消费者的需求很少但我们要知道怎么用。首先准备消费者的SSM配置文件

    
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:context="http://www.springframework.org/schema/context"
    	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.xsd
    	    http://www.springframework.org/schema/context
    	    http://www.springframework.org/schema/context/spring-context.xsd">
    
    	<bean id="consumerProperties" class="java.util.HashMap">
    		<constructor-arg>
    			<map>
    				
    				<entry key="bootstrap.servers" value="192.168.88.186:9092,192.168.88.187:9092,192.168.88.188:9092" />
    				
    				<entry key="group.id" value="wy" />
    				
    				<entry key="enable.auto.commit" value="true" />
    				
    				<entry key="session.timeout.ms" value="15000 " />
    
    				<entry key="key.deserializer"
    					value="org.apache.kafka.common.serialization.StringDeserializer" />
    
    				<entry key="value.deserializer"
    					value="org.apache.kafka.common.serialization.StringDeserializer" />
    			map>
    		constructor-arg>
    	bean>
    
    
    	
    	<bean id="consumerFactory"
    		class="org.springframework.kafka.core.DefaultKafkaConsumerFactory">
    		<constructor-arg>
    			<ref bean="consumerProperties" />
    		constructor-arg>
    	bean>
    
    	
    	<bean id="messageListenerContainer"
    		class="org.springframework.kafka.listener.KafkaMessageListenerContainer"
    		init-method="doStart">
    		
    		<constructor-arg ref="consumerFactory" />
    		
    		<constructor-arg ref="containerProperties" />
    	bean>
    
    	
    	<bean id="containerProperties" class="org.springframework.kafka.listener.ContainerProperties">
    		
    		
    		<constructor-arg value="wy" />
    		
    		<property name="messageListener" ref="messageListernerConsumerService" />
    	bean>
    
    	
    	<bean id="messageListernerConsumerService" class="com.wy.kafka.MesLis" />
    
    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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    随后关键的一点注意,和生产者一样我们需要在spring的配置里加载配置,但是记住!!!如果有生产者一定要把相关代码暂时注释,尽量不要两者共存一个项目中,不然很可能其中一个会失效!!!!

    <!-- kafka:消费者配置文件 -->
    <import resource="consumer.xml"/>
    
    • 1
    • 2

    最后我们要书写消费者类,也就是配置中指向的监听类

    package com.wy.kafka;
    
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.kafka.listener.MessageListener;
    
    import com.alibaba.fastjson.JSON;
    import com.wy.bean.Article;
    import com.wy.dao.ArticleMapper;
    
    /**
     *@描述
     * SSM整合消费者用的类 实现MessageListener接口 泛型一般都是String
     *
     *@参数
     *@返回值
     *@创建人 wangyang
     *@创建时间 2022/9/17
     *@修改人和其它信息
     */
    public class MesLis implements MessageListener<String, String>{
    
    	@Override
    	public void onMessage(ConsumerRecord<String, String> data) {
    		String d = data.value();
    		System.out.println("接收到的数据为"+d);
    
    	}
    
    }
    
    • 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

    随后运行项目,在服务器端想topic发送消息,查看结果
    在这里插入图片描述
    不要有中文,SSM官方现在对框架维护很少,中文字符集不对这个问题需要自己解决一下


    最后我们来说SSM如何整合ES,同样的步骤,ES后续步骤有些多,先准备SSM整合ES的配置文件

    
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
                                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        
        
        
        
    	<elasticsearch:repositories base-package="com.wy.es" />
    	
    	 
    	<elasticsearch:transport-client id="client" cluster-nodes="192.168.88.188:9300" /> 
    		
    	
    	<bean id="elasticsearchTemplate"
    		class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
    		<constructor-arg name="client" ref="client">constructor-arg>
    	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

    随后spring中引入该配置,并且创建配置中的ES接口包

    <!-- es数据库 -->
    <import resource="es.xml"/>
    
    • 1
    • 2
    创建<elasticsearch:repositories base-package="com.wy.es" />指向的包
    
    • 1

    在这个包下我们需要准备ES数据接口

    package com.wy.es;
    
    import java.util.List;
    
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
    
    import com.wy.bean.Article;
    
    /**
     * 所有ES仓库接口必须继承ElasticSearchRepository,之后就自动具备了简单的CRUD的方法
     * 第一个泛型是文档操作的实例Bean  第二个是这个Bean的主键类型
     */
    public interface ArticleElasticsearch extends ElasticsearchRepository<Article, Integer>{
    	//按标题或内容查询
    	List<Article> findByTitleOrContent(String title,String content);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    随后我们要使用ES提供的注解对操作ES数据的实体Bean中的字段做注解

    //指定type库名(库名必须用纯小写的名字,不允许有特殊字符,否则就报错),indexName指定表名也叫索引名
    @Document(indexName="test_user",type="user")
    //用来指定ID
    @Id
    //指定字段的索引方式,index是否索引、store是否存储、字段的分词方式、搜索时关键字分词的方式、type指定该字段的值以什么样的数据类型来存储
    @Field(index=true,store=true,analyzer="ik_smart",searchAnalyzer="ik_smart",type=FieldType.text)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    标记结果如下

    package com.wy.bean;
    
    import java.io.Serializable;
    import java.util.Date;
    
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    /**
     * 
     * @ClassName: Article 
     * @Description: 文章内容表
     * @author: charles
     * @date: 2020年3月3日 上午11:25:22
     */
    @Document(indexName="articles",type="article")
    public class Article implements Serializable {
    
    	/**
    	 * @fieldName: serialVersionUID
    	 * @fieldType: long
    	 * @Description: TODO
    	 */
    	private static final long serialVersionUID = 1L;
    	@Id
    	private Integer id;//主键
    	@Field(index=true,store=true,analyzer="ik_max_word",searchAnalyzer="ik_max_word",type=FieldType.text)
    	private String title;//文章标题
    	private String summary;//文章摘要
    	@Field(index=true,store=true,analyzer="ik_max_word",searchAnalyzer="ik_max_word",type=FieldType.text)
    	private String content;//文章内容
    	private String picture;//文章的标题图片
    	
    	private Integer channelId;//所属栏目ID
    	private Integer categoryId;//所属分类ID
    	private Integer userId;//文章发布人ID
    	
    	private Integer hits;//  点击量
    	private Integer hot;//是否热门文章   1:热门     , 0 :一般文章
    	private Integer status;//文章审核状态     0:待审        1:审核通过     -1: 审核未通过
    	private Integer deleted;// 删除状态 0:正常,1:逻辑删除
    	private Date created;// 文章发布时间
    	private  Date  updated;// 文章修改时间
    	
    	private String contentType ;//文章内容类型  0:html  1:json
    	private Channel channel;
    	private Category category;
    	private User user;
    	
    	
    	private String keywords;//文章关键词
    	private String original;//文章来源
    	
    	
    	public String getKeywords() {
    		return keywords;
    	}
    	public void setKeywords(String keywords) {
    		this.keywords = keywords;
    	}
    	public String getOriginal() {
    		return original;
    	}
    	public void setOriginal(String original) {
    		this.original = original;
    	}
    	
    	public Integer getId() {
    		return id;
    	}
    	public void setId(Integer id) {
    		this.id = id;
    	}
    	public String getTitle() {
    		return title;
    	}
    	public void setTitle(String title) {
    		this.title = title;
    	}
    	public String getSummary() {
    		return summary;
    	}
    	public void setSummary(String summary) {
    		this.summary = summary;
    	}
    	public String getContent() {
    		return content;
    	}
    	public void setContent(String content) {
    		this.content = content;
    	}
    	public String getPicture() {
    		return picture;
    	}
    	public void setPicture(String picture) {
    		this.picture = picture;
    	}
    	public Integer getChannelId() {
    		return channelId;
    	}
    	public void setChannelId(Integer channelId) {
    		this.channelId = channelId;
    	}
    	public Integer getCategoryId() {
    		return categoryId;
    	}
    	public void setCategoryId(Integer categoryId) {
    		this.categoryId = categoryId;
    	}
    	public Integer getUserId() {
    		return userId;
    	}
    	public void setUserId(Integer userId) {
    		this.userId = userId;
    	}
    	public Integer getHits() {
    		return hits;
    	}
    	public void setHits(Integer hits) {
    		this.hits = hits;
    	}
    	public Integer getHot() {
    		return hot;
    	}
    	public void setHot(Integer hot) {
    		this.hot = hot;
    	}
    	public Integer getStatus() {
    		return status;
    	}
    	public void setStatus(Integer status) {
    		this.status = status;
    	}
    	public Integer getDeleted() {
    		return deleted;
    	}
    	public void setDeleted(Integer deleted) {
    		this.deleted = deleted;
    	}
    	public Date getCreated() {
    		return created;
    	}
    	public void setCreated(Date created) {
    		this.created = created;
    	}
    	public Date getUpdated() {
    		return updated;
    	}
    	public void setUpdated(Date updated) {
    		this.updated = updated;
    	}
    	public Channel getChannel() {
    		return channel;
    	}
    	public void setChannel(Channel channel) {
    		this.channel = channel;
    	}
    	public Category getCategory() {
    		return category;
    	}
    	public void setCategory(Category category) {
    		this.category = category;
    	}
    	public User getUser() {
    		return user;
    	}
    	public void setUser(User user) {
    		this.user = user;
    	}
    	public String getContentType() {
    		return contentType;
    	}
    	public void setContentType(String contentType) {
    		this.contentType = contentType;
    	}
    	@Override
    	public String toString() {
    		return "Article [id=" + id + ", title=" + title + ", summary=" + summary + ", content=" + content + ", picture="
    				+ picture + ", channelId=" + channelId + ", categoryId=" + categoryId + ", userId=" + userId + ", hits="
    				+ hits + ", hot=" + hot + ", status=" + status + ", deleted=" + deleted + ", created=" + created
    				+ ", updated=" + updated + ", contentType=" + contentType + ", channel=" + channel + ", category="
    				+ category + ", user=" + user + ", keywords=" + keywords + ", original=" + original + "]";
    	}
    
    	
    }
    
    • 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
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188

    此时我们使用测试类和我们自己的ES接口向ES初始化数据

    import com.wy.bean.Article;
    import com.wy.es.ArticleElasticsearch;
    import com.wy.service.ArticleService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.List;
    
    /**
     * 测试类用来推送ES数据
     * @创建人 wangyang
     * @创建时间 2022/9/17
     * @描述
     */
    //这个必须加入
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:spring.xml" })
    public class ESTest {
    
        @Autowired
        private ArticleElasticsearch articleElasticsearch;
    
        @Autowired
        private ArticleService articleService;
    
        @Test
        public void pushESData(){
            List<Article> articles = articleService.selectArticle(new Article(), 1, 30);
            for (Article a : articles){
                articleElasticsearch.save(a);
            }
        }
    
    }
    
    • 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

    测试类运行结束后,才head上就可以看到数据
    在这里插入图片描述
    到此ES整合的配置流程已经完成了

    注意:Spring提供的父接口ElasticsearchRepository,继承后就可以拥有基本的操作方法,如果要进行复杂点的查询,就需要我们自定义方法,自定义方法的方法名必须按照命名规则来进行命名,使用的时候注入接口类型的变量对象后直接调用方法就可以,不需要你额外的准备实现类

    具体示例:更多的大家需要在网上自己找找了

    	List<User> findByName(String name);
    	
    	//根据地址查询 findBy+字段名
    	List<User> findByAddress(String address);
    	
    	//根据地址和姓名查询  findBy+多个字段名之间Or分隔
    	List<User> findByAddressAndName(String address,String name);
    	
    	//查询id小于5的数据  findBy+比大小的字段+LessThan
    	List<User> findByIdLessThan(int id);
    	
    	//查询价格在多少-多少之间的   findBy+结果字段+Between
    	List<User> findByPriceBetween(double money,double money)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    而本篇知识点不去做上面说的自定义方法这种普通的整合,我们玩一个比较高端的ES高亮,既可以学习高亮又能知道ES整合怎么用,美滋滋,想要实现ES高亮,需要自己写一个工具类,这个工具类我已经给大家写完了,直接复制就行,提前说明我写的这个工具类不是公用的,只是用作本次CMS系统,如果有其他用处就需要你自己看着改

    /**  
     * @Title: ESUtils.java 
     * @Description: TODO
     * @author: chj 
     * @date: 2019年7月24日 上午10:14:13 
     */
    package com.wy.utils;
    
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.index.query.QueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.domain.Sort;
    import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
    import org.springframework.data.elasticsearch.core.SearchResultMapper;
    import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
    import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
    import org.springframework.data.elasticsearch.core.query.GetQuery;
    import org.springframework.data.elasticsearch.core.query.IndexQuery;
    import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.data.elasticsearch.core.query.SearchQuery;
    
    import com.github.pagehelper.PageInfo;
    
    
    /**
     * @ClassName: ESUtils
     * @Description: TODO
     * @author: 
     * @date: 2019年7月24日 上午10:14:13
     */
    public class HLUtils {
    
    
    	/**
    	 * 保存及更新方法
    	 * 
    	 * @param elasticsearchTemplate
    	 * @param id
    	 * @param object
    	 */
    	public static void saveObject(ElasticsearchTemplate elasticsearchTemplate, String id, Object object) {
    		// 创建所以对象
    		IndexQuery query = new IndexQueryBuilder().withId(id).withObject(object).build();
    		// 建立索引
    		elasticsearchTemplate.index(query);
    	}
    
    	/**
    	 * 批量删除
    	 * 
    	 * @param elasticsearchTemplate
    	 * @param clazz
    	 * @param ids
    	 */
    	public static void deleteObject(ElasticsearchTemplate elasticsearchTemplate, Class<?> clazz, Integer ids[]) {
    		for (Integer id : ids) {
    			// 建立索引
    			elasticsearchTemplate.delete(clazz, id + "");
    		}
    	}
    
    	/**
    	 * 
    	 * @Title: selectById
    	 * @Description: 根据id在es服务启中查询对象
    	 * @param elasticsearchTemplate
    	 * @param clazz
    	 * @param id
    	 * @return
    	 * @return: Object
    	 */
    	public static Object selectById(ElasticsearchTemplate elasticsearchTemplate, Class<?> clazz, Integer id) {
    		GetQuery query = new GetQuery();
    		query.setId(id + "");
    		return elasticsearchTemplate.queryForObject(query, clazz);
    	}
    
    	// 查询操作
    	public static PageInfo<?> findByHighLight(ElasticsearchTemplate elasticsearchTemplate, Class<?> clazz, Integer page,
    			Integer rows, String fieldNames[],String sortField, String value) {
    		AggregatedPage<?> pageInfo = null;
    		PageInfo<?> pi = new PageInfo<>();
    		// 创建Pageable对象														主键的实体类属性名
    		final Pageable pageable = PageRequest.of(page - 1, rows, Sort.by(Sort.Direction.ASC, sortField));
    		//查询对象
    		SearchQuery query = null;
    		//查询条件高亮的构建对象
    		QueryBuilder queryBuilder = null;
    		
    		if (value != null && !"".equals(value)) {
    			// 高亮拼接的前缀与后缀
    			String preTags = "";
    			String postTags = "";
    
    			// 定义创建高亮的构建集合对象
    			HighlightBuilder.Field highlightFields[] = new HighlightBuilder.Field[fieldNames.length];
    
    			for (int i = 0; i < fieldNames.length; i++) {
    				// 这个代码有问题
    				highlightFields[i] = new HighlightBuilder.Field(fieldNames[i]).preTags(preTags).postTags(postTags);
    			}
    
    			// 创建queryBuilder对象
    			queryBuilder = QueryBuilders.multiMatchQuery(value, fieldNames);
    			query = new NativeSearchQueryBuilder().withQuery(queryBuilder).withHighlightFields(highlightFields)
    					.withPageable(pageable).build();
    			//elasticsearchTemplate.queryForPage(query, clazz,)
    
    			pageInfo = elasticsearchTemplate.queryForPage(query, clazz, new SearchResultMapper() {
    
    				public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable1) {
    
    					List<T> content = new ArrayList<T>();
    					long total = 0l;
    
    					try {
    						// 查询结果
    						SearchHits hits = response.getHits();
    						if (hits != null) {
    							//获取总记录数
    							total = hits.getTotalHits();
    							// 获取结果数组
    							SearchHit[] searchHits = hits.getHits();
    							// 判断结果
    							if (searchHits != null && searchHits.length > 0) {
    								// 遍历结果
    								for (int i = 0; i < searchHits.length; i++) {
    									// 对象值
    									T entity = clazz.newInstance();
    
    									// 获取具体的结果
    									SearchHit searchHit = searchHits[i]; 
    
    									// 获取对象的所有的字段
    									Field[] fields = clazz.getDeclaredFields();
    
    									// 遍历字段对象
    									for (int k = 0; k < fields.length; k++) {
    										// 获取字段对象
    										Field field = fields[k];
    										// 暴力反射
    										field.setAccessible(true);
    										// 字段名称
    										String fieldName = field.getName();
    										if (!fieldName.equals("serialVersionUID")&&!fieldName.equals("user")&&!fieldName.equals("channel")&&!fieldName.equals("category")&&!fieldName.equals("articleType")&&!fieldName.equals("imgList")) {
    											HighlightField highlightField = searchHit.getHighlightFields()
    													.get(fieldName);
    											if (highlightField != null) {
    												// 高亮 处理 拿到 被 结束所包围的内容部分
    												String value = highlightField.getFragments()[0].toString();
    												// 注意一下他是否是 string类型
    												field.set(entity, value);
    											} else {
    												//获取某个字段对应的 value值
    												Object value = searchHit.getSourceAsMap().get(fieldName);
    												// 获取字段的类型
    												Class<?> type = field.getType();
    												if (type == Date.class) {
    													// bug
    													if(value!=null) {
    														field.set(entity, new Date(Long.valueOf(value + "")));
    													}
    												} else {
    													field.set(entity, value);
    												}
    											}
    										}
    									}
    
    									content.add(entity);
    								}
    							}
    						}
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    
    					return new AggregatedPageImpl<T>(content, pageable, total);
    				}
    			});
    
    		} else {
    			// 没有查询条件的的时候,获取es中的全部数据 分页获取
    			query = new NativeSearchQueryBuilder().withPageable(pageable).build();
    			pageInfo = elasticsearchTemplate.queryForPage(query, clazz);
    		}
    		int totalCount = (int) pageInfo.getTotalElements();
    		int pages = totalCount%rows==0?totalCount/rows:totalCount/rows+1;
    		pi.setTotal(pageInfo.getTotalElements());
    		pi.setPageNum(page);
    		pi.setPageSize(rows);
    		pi.setPrePage(page-1);
    		pi.setLastPage(page+1);
    		pi.setPages(pages);
    		List content = pageInfo.getContent();
    		pi.setList(content);
    
    		return pi;
    	}
    
    }
    
    • 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
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213

    下面我们来使用这个工具类,首先在首页上放一个查询框,用来做高亮的搜索,我们放在展示五条最新消息的上面

    <form action="/es.do" method="get">
    	<div class="input-group mb-3">
    		<input type="text" name="key" value="${key}" class="form-control"
    			   placeholder="请输入要搜索的内容" aria-label="Recipient's username"
    			   aria-describedby="button-addon2">
    		<div class="input-group-append">
    			<button class="btn btn-outline-secondary" id="button-addon2">搜索button>
    		div>
    	div>
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    前端首页有了搜索框,那后端就要有它的接收Controller

    @RequestMapping("es.do")
    public String es(Model model,String key,@RequestParam(defaultValue="1")Integer pageNum){
    	//高亮查询
    	//注意该方法查询参数依次为:框架自带的ElasticsearchTemplate对象、文档Bean的class对象、当前页、每页显示多少条数据、数组包裹高亮搜索那些字段注意写在后面的优先高亮倒着来的,这是我工具类的一个小问题,不影响使用、文档bean的主键字段、高亮的具体值
    	PageInfo<?> info = HLUtils.findByHighLight(elasticsearchTemplate, Article.class, pageNum, 5, new String[]{"title"}, "id", key);
    	model.addAttribute("info", info);
    	model.addAttribute("key", key);
    	return "index/index";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    现在我们就可以运行项目看效果
    在这里插入图片描述
    在这里插入图片描述
    通过效果来看高亮成功,但是其他的数据没了,这是因为其他数据没有查询,大家可以自己扩展把高亮的查询和原先的查询融合一下

    自此CMSDemo2.0版本完成

    本项目目前以上传github :https://github.com/wangyang159/cmsdemo

  • 相关阅读:
    语言大模型的分布式训练与高效微调指南
    C/C++内存管理
    希格斯玻色子——物质质量起源的探索
    【Java 进阶篇】JQuery 动画:为页面添彩的魔法
    正在安装最新版本的origin太慢了
    Unity Scene窗口获取鼠标位置
    自学成为一名黑客(自学笔记)
    java-php-python-ssm寿险公司保险业务管理系统计算机毕业设计
    MySQL数据存在更新不存在新增数据
    0-1背包问题的一维数组优化解析
  • 原文地址:https://blog.csdn.net/dudadudadd/article/details/126902736