elasticSearch结合SSM框架实现数据的增删改查

前言:

     当数据量过大几十万或者上百万条数据时,单纯的mysql oracle 以及sql查询已经无法满足我们在效率上的需求,elasticSearch 是当下一款热门的实时搜索引擎基于lucense的搜索服务器,使用它可以完成近乎实时的数据查询。

目录

一、准备开发环境

二、常用操作

三、查询结果高亮显示

四、效果展示​

五、后记:



一、准备开发环境

  • 该项目是基于Srping + SpringMVC+Mybatis
  • 1.JDK 1.8
  • 2.elasticSearch5.5.1
  • 需要用到的相关依赖pom文件如下
      <dependency>  
                <groupId>org.elasticsearch</groupId>  
                <artifactId>elasticsearch</artifactId>  
                <version>5.5.3</version>  
      </dependency>  
       
       <!-- transport客户端 -->  
    <dependency>  
            <groupId>org.elasticsearch.client</groupId>  
            <artifactId>transport</artifactId>  
            <version>5.5.3</version>  
   </dependency>  
         <!--log4j-->    
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.7</version>
        </dependency>

   二、常用操作

  由于代码是从我的项目中拿出来的,没有用到的代码直接删除就可以了

  1.工具类ESutils,用于client客户端的关闭,创建

package com.rupeng.utlis;

import java.net.InetAddress;
import java.net.UnknownHostException;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import com.rupeng.service.config.ConfigInfo;

/**
 * 用来创建关闭es客户端
 * @author 2016wlw2 徐塬峰
 * 创建时间:2018年7月24日 上午10:32:28
 */
public class EsUtils {
	/**
	 * 獲取ElasticSearch
	 */
	public synchronized static  Client getClient() {
		Settings settings = Settings.builder().
				put("client.transport.sniff", true)
				.build();//自动嗅探其他集群的ip 如果有则加入
		InetSocketTransportAddress master = null;
		try {
			master = new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"),9300);
			TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(master);
			return client;
		} 
		catch (UnknownHostException e) {
			e.printStackTrace();
			throw new RuntimeException("elasticSearch Client init error 连接创建失败"+e);
		}
		}
	/**
	 * 用于关闭elasticSearch
	 */
	public static void closeClient(Client client){
		if(null != client){
			try {
				client.close();
			} catch (Exception e) {
				throw new RuntimeException("连接关闭失败");
			}
		}
	}
	
	

1.SSM注入client客户端

代码以及其引用的方式

/**
 * 实现自动注入elasticserach
 * @author Ray
 */
@Configuration
public class ElasticSerachConfig {
     //自动注入client 
	@Bean(name="client")
	public TransportClient esClint() throws UnknownHostException
	{
		Settings settings = Settings.builder().build();
		InetSocketTransportAddress master=new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"),9300);	
		TransportClient client =new PreBuiltTransportClient(settings).addTransportAddress(master);
		return client;	
	}
}
        @Resource(name = "client")
	private TransportClient client;

2.基本操作(查询,增加,删除,修改)

2.1基本查询

public List<String> search (String query,int number)
    {
    	long start =System.currentTimeMillis();
        if(client==null)
        {
        	client =ESUtils.getTransportClient();
        }
//      定义查询用到的类型,单独定义
 	    String Type[]=new String[]{INDEXKEYTEMPLATE.TYPE_COURSES
 				,INDEXKEYTEMPLATE.TYPE_NEWS,INDEXKEYTEMPLATE.TYPE_SEGMENTS};
 	  
 	SearchRequestBuilder responsebuilder = client.prepareSearch(INDEX_NAME).setTypes(Type);
    	List<String> hitsList=new ArrayList<String>();  
 	String Key[]=new String[]{CoursesIndexKey.NAME,
                 NewsIndexKey.TITLE,
                 SegmentsIndexKey.NAME,
                 BbsIndexKey.TITLE   
 	    };//查询用到的关键字,单独定义
 	    SearchResponse myresponse = responsebuilder
    			.setQuery(QueryBuilders.multiMatchQuery(query, Key))
    			.setFrom(0)//from 从哪条开始 ,可用于分页操作
    			.setSize(number).setExplain(true).execute().actionGet();//number为查询的条数
 	   
    	SearchHits hits = myresponse.getHits();
    	for (int i = 0; i < hits.getTotalHits(); i++) // getHits()当前查询页的结果
    	{
    		SearchHit hit = hits.getHits()[i];			
    		String jsonStr=hit.getSourceAsString();
            logger.info("索引库的数据:" +jsonStr );  
            hitsList.add(jsonStr);
    		
    	}	  	
    	long end=System.currentTimeMillis();
    	logger.debug("为您找到相关结果约"+hits.getTotalHits()+"个----"+"耗时"+(end-start)/1000+"秒");
	return hitsList;
    }

2.2根据id来获取数据

 
    public String getOneById(String INDEX_TYPE,Long id)
    {  

	    if(client==null)
            {
        	client =ESUtils.getTransportClient();
            }
		  GetResponse getResponse = client.prepareGet(INDEX_NAME,INDEX_TYPE,String.valueOf(id))  
	                                     .execute()  
	                                     .actionGet();  
	    if(getResponse!=null)
            {
          	 logger.debug(getResponse.getSourceAsString());
          	 String jsonStr=getResponse.getSourceAsString();
          	 return jsonStr;
            }
            else
            {
          	logger.error("找不到id对应的数据");
 			return null;  
            }
           
	 }  
	    

2.3精确匹配 方法和1类似 不过替换为MatchPhraseQuery

2.4创建操作 分批次提交数据

基本思路,从数据库获取数据,并将数据库里的数据插入到elasticSearch搜索服务器,数据量由于比较大,所以

采用了分批次提交

	public boolean createAll(List<T>  pojoList,String INDEX_TYPE) throws InterruptedException, ExecutionException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException 
	{			
		long start =System.currentTimeMillis();
		Gson gson = new Gson();	
                if(client==null)
                {
         	client =ESUtils.getTransportClient();
                }
		BulkRequestBuilder bulkRequest = client.prepareBulk(); 	
		for(T pojo:pojoList)
		{
//			通过反射获得方法getID 设置主键
//			pojo.getClass().getMethod("getId", Long.class);
			Field FieldId=pojo.getClass().getDeclaredField("id");
		        //设置可读写
			FieldId.setAccessible(true);
			Object id = FieldId.get(pojo);   				
			IndexRequest request=client.prepareIndex(INDEX_NAME,INDEX_TYPE).setId(String.valueOf(id))
					.setSource(gson.toJson(pojo),XContentType.JSON).request();
			bulkRequest.add(request);
			if(bulkRequest.numberOfActions()==1000)
			{
				   bulkRequest.execute().get();//get的实现就是调用executeget	
				   bulkRequest=client.prepareBulk();//创建一个对象	
			           logger.debug("已提交1000条数据");

			}				
		}		
		if(bulkRequest.numberOfActions()>0)//最后一批不足一千的再提交一次
	         {	
	       bulkRequest.execute().get();//
	       long end=System.currentTimeMillis();
	       logger.debug("最后一批已提交,"+"耗时"+(end-start)/1000+"秒");

	        }
		
		ESUtils.close(client);
    	        return true;		
   
    }

2.6删除操作

2.6.1删除整个索引库

            /** 注意 :该操作会删除整个索引库
	     * 
	     * @param INDEX_NAME
	     */
	    public void deleteIndex(String INDEX_NAME)  
	    {  
	    	
	    	 if(client==null)
	            {	              
	            	client =ESUtils.getTransportClient();
	            }
	    TransportResponse resp=client.admin().indices().prepareDelete(INDEX_NAME).get();  
            if(resp!=null)
            {
	  	    logger.debug("索引库删除成功");
	  	    ESUtils.close(client);

            }
            else
            {
            logger.error("索引删除失败");
	  	    ESUtils.close(client);
            }
 	    ESUtils.close(client);
	    
	    }  

2.6.2 删除指定id

如果需要删除单个类型里的多个文档,则可以使用for循环删除 或者其他 只要思路正确就ok

/**
		 * 根据id的值删除文档
		 * @param id
		 * @param INDEX_TYPE
		 * @throws UnknownHostException
		 */
		 public void removeOneById(Long id,String INDEX_TYPE)
		 {	   
			if(client==null)
	                {	                
	            	client =ESUtils.getTransportClient();
	                 }
		        DeleteResponse response = client.prepareDelete(INDEX_NAME, INDEX_TYPE, String.valueOf(id)).get();
		        String index = response.getIndex();
		        String type = response.getType();
		        String typeId = response.getId();
		        long version = response.getVersion();
		        logger.debug("删除一条文档"+index + " : " + type + ": " + typeId + ": " + version);
		        ESUtils.close(client);
		     
		 }

2.6.3 删除指定分类

public boolean deleteType(String indexName, String type) {
		client = EsUtils.getClient();
		QueryBuilder builder = QueryBuilders.typeQuery(type);
		DeleteByQueryAction.INSTANCE.newRequestBuilder(client).source(indexName).filter(builder).execute().actionGet();
		EsUtils.closeClient(client);
		return true;
	}

2.7插入一条数据

                  /**
		  * 创建一条或者更新索引
		  * @param esMixedDataDto
		  * @return
		  */
		 public boolean upsertDocument(T pojo,String INDEX_TYPE) {
		     client =ESUtils.getTransportClient();
	             Gson gson = new Gson();				 
		     Field FieldId;
			try {
				
		         FieldId = pojo.getClass().getDeclaredField("id");		 
			 FieldId.setAccessible(true);
			 Object id = FieldId.get(pojo);  
			 IndexResponse indexRes=client.prepareIndex(INDEX_NAME,INDEX_TYPE).setId(String.valueOf(id))
					.setSource(gson.toJson(pojo),XContentType.JSON).get();
		 	String index = indexRes.getIndex();
	        String type = indexRes.getType();
	        String typeId = indexRes.getId();
	        logger.debug("插入成功"+index + " : " + type + ": " + typeId + ": " );
	        ESUtils.close(client);
	        return true;
			
			}
    	 	 catch (NoSuchFieldException | SecurityException e) {
				// TODO Auto-generated catch block
		 		logger.warn("插入失败");
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
		 		logger.warn("插入失败");
				e.printStackTrace();
			} catch (IllegalAccessException e) {
		 		logger.warn("插入失败");
				e.printStackTrace();
			}
			return false;
 	 
		    }

三、查询结果高亮显示

java代码实现高亮显示

 public Map<String,Object> hlSearch (String query,int number)
    {
    	long start =System.currentTimeMillis();
        if(client==null)
        {
        	client =ESUtils.getTransportClient();//创建连接
        }
 	    String Type[]=new String[]{INDEXKEYTEMPLATE.TYPE_COURSES
 				,INDEXKEYTEMPLATE.TYPE_NEWS,INDEXKEYTEMPLATE.TYPE_SEGMENTS};
 	  
 	    SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX_NAME).setTypes(Type);
 	    String Key[]=new String[]{
 	    		 CoursesIndexKey.NAME,
                 NewsIndexKey.TITLE,
                 SegmentsIndexKey.NAME,
                 BbsIndexKey.TITLE,
 	    };
 	   SearchRequestBuilder  searchRequestBuilder = requestBuilder
    			.setQuery(QueryBuilders.multiMatchQuery(query, Key))
    			.setFrom(20). //从那行开始
    			setSize(30).setExplain(true);//.actionGet()==get()
 	   
        Map<String,Object> msgMap = new HashMap<String,Object>();  
        HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);  
        highlightBuilder.preTags("<span style=\"color:red\">");  
        highlightBuilder.postTags("</span>");  
        searchRequestBuilder.highlighter(highlightBuilder);  
        SearchResponse response = searchRequestBuilder.get();  
        List<Map<String,Object>> result = new ArrayList<>();  
        
        
        
    	for (SearchHit hit:response.getHits()) // getHits()当前查询页的结果
    	{
            Map<String, Object> source = hit.getSource();  
    		//创建map 存放高亮字段 并返回到对象中
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();  
             //处理高亮字段
            HighlightField nameField = highlightFields.get("title");  
            if(nameField!=null){  
                
            	Text[] fragments = nameField.fragments();  
                String nameTmp ="";  
                for(Text text:fragments){  
                    nameTmp+=text;  
                    source.put("title",nameTmp);
                    source.put("content",nameTmp);
                }  
                source.put("title",nameTmp);

            }
            result.add(source);  
    }
    	//封装数据返回  
        msgMap.put("itemsList",result);     //搜索结果  
        //msgMap.put("page","page");          //分页  
        msgMap.put("took",response.getTook().getSecondsFrac()); //获取响应需要的时间  
        return msgMap;  
    }

Controller层

   @RequestMapping(value="/hlsearch.do",method=RequestMethod.POST)
   public @ResponseBody AjaxResult hlsearch(String text) 
   {
	   Map<String,Object> msg=searchService.hlSearch(text, 10);
	   for(Map.Entry<String, Object> entry :msg.entrySet())
	   {
		   System.out.println("Key:"+entry.getKey()+"Value:"+entry.getValue());		   
	   }
	   return new AjaxResult("Success",msg);	   
   }

前端代码(vue.js)

search:function()
                    {
                        var that=this;
                        that.results=[];                                            
                        axios.post('./hlsearch.do?text='+this.text)
                                .then(function(resp){
                                   
                                	var results=resp.data.data.itemsList;
                                    if(results.length==0)
                                    {
                                    	alert("未找到匹配结果");                                  
                                    	that.message="<font size='2' color='CCCCCC'>找到约0 条结果!</font>";
                                       
                                    }                                  
                                    else
                                    {
                                    that.message="<font size='2' color='CCCCCC'>找到约 "+results.length+" 条结果!</font>";
                                    for(var i=0;i<results.length;i++)
                                    {
                                        that.results.push(resp.data.data.itemsList[i]);
                                        
                                    }
                                    }
                                })
                                .catch(function(error){alert("ajax错误"+error);})
                    }}

四.效果展示

五、后记:

1.前端不要用ajax请求来获取数据,异步获取数据在并发环境下 会导致前端展示结果 错乱的情况,所以

建议大家使用返回视图的方式来展示数据,这样效果更好

2.生产环境下 client必须关闭,否则会导致资源泄露,内存溢出等问题。但是client 获取连接的时间较慢依然是一个大问题

获取一个client平均需要2秒左右 ,这样查询快的意义就没有了。所以应该尽量采用es连接池的方式来创建client 客户端。

连接池代码(参考)

package com.rupeng.elasticpools;

import java.net.InetAddress;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;

import com.rupeng.service.config.ConfigInfo;

/**
 * 每一次创建es的连接都太耗费时间 我们应该自己去封装一个es的连接池
 * @author 2016wlw2 徐塬峰 创建时间:2018年7月31日 上午10:00:32
 */
public class ElasticSearchFactory implements PooledObjectFactory<Client>{

	private  int elasticPool_maxTotal = Integer.parseInt(ConfigInfo.elasticPool_maxTotal.trim());
	private  int elasticPool_maxIdle =  Integer.parseInt(ConfigInfo.elasticPool_maxIdle.trim());
	private  String elasticSearch_addr ="127.0.0.1";// ConfigInfo.elasticSearch_addr;
	private  int elasticSearch_port = Integer.parseInt(ConfigInfo.elasticSearch_port.trim());
	private  int elasticPool_minIdle =  Integer.parseInt(ConfigInfo.elasticPool_minIdle.trim());
	private  int maxWaitMilis = Integer.parseInt(ConfigInfo.elasticPool_maxWaitMilis.trim());
	
	private  static GenericObjectPool<Client> pool;// 连接池
	static
	{
		ElasticSearchFactory fa=new ElasticSearchFactory();
		fa.createElasticSearchPool();
		
	}
	/**
	 * 创建连接池
	 */
	public void createElasticSearchPool()
	{
		ElasticSearchFactory fac = new ElasticSearchFactory();// 创建工厂
		GenericObjectPoolConfig conf = new GenericObjectPoolConfig();// 配置文件
		conf.setMaxTotal(elasticPool_maxTotal);// 设置线程池中最大的数量
		conf.setMaxIdle(elasticPool_maxIdle);// 设置最大的空闲时间
		conf.setMinIdle(elasticPool_minIdle);// 设置最小空闲连接
		conf.setMaxWaitMillis(maxWaitMilis);// 设置最大等待时间
		
		try {
			//先创建三个连接
			fac.makeObject();
			fac.makeObject();
			fac.makeObject();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		pool = new GenericObjectPool<Client>(fac, conf);// 创建连接池
		
	}
	// 模拟对象创建的过程
	@Override // 通过ElastcSearchConnections 来创建连接
	    public PooledObject<Client> makeObject() throws Exception {
		Settings settings = Settings.builder().put("client.transport.sniff", true).build();// 自动嗅探其他集群的ip
		InetSocketTransportAddress master = null;
		master = new InetSocketTransportAddress(InetAddress.getByName(elasticSearch_addr), elasticSearch_port);
		TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(master);
		System.out.println("太棒了,对象创建成功了!");
		return new DefaultPooledObject<Client>(client);
	}
	
	public static GenericObjectPool<Client> getPool() {
		return pool;
	}
	
	//销毁
	@Override
	public void destroyObject(PooledObject<Client> p) throws Exception {
             p.getObject().close();
	}
	@Override
	public boolean validateObject(PooledObject<Client> p) {
		// TODO Auto-generated method stub
		return false;
	}
	@Override
	public void activateObject(PooledObject<Client> p) throws Exception {
		
	}
	@Override
	public void passivateObject(PooledObject<Client> p) throws Exception {
	    System.out.println("passivate Object"+p.toString());

	}
	
	


}

3.elasticSearch 已不再维护transport 客户端 尽量使用rest client客户端来进行开发。。。。。。。

 

以上就是我归纳的对elasticsearch5.5.3版本的java实现,如果有哪些地方有问题,欢迎大家留言指正,感激不尽!

猜你喜欢

转载自blog.csdn.net/RAVEEE/article/details/80063320