SpringBootDataElasticSearch+JPA实现将ORACLE数据批量写入ElasticSearch

一、环境配置

ElasticSearch 5.6.10  windows版  安装head插件   安装教程:elasticsearch5.X安装和elasticsearch head插件安装

SpringBoot 2.0.2.RELEASE 通过MAVEN引入对应版本的spring boot data elasticsearch和JPA

pom.xml



	4.0.0

	com.geostar
	GeoDataElasticSearch
	0.0.1-SNAPSHOT
	jar

	GeoDataElasticSearch
	Demo project for Spring Boot

	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.2.RELEASE
		
	
	
	
		UTF-8
		UTF-8
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		
		
    org.springframework.boot
    spring-boot-starter-data-elasticsearch

		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		  
            net.java.dev.jna
            jna
            3.0.9
        
		
			org.springframework.boot
			spring-boot-starter-data-jpa
		
		
			org.springframework.boot
			spring-boot-starter-actuator
		
		
		
			org.springframework
			spring-jdbc
			4.3.9.RELEASE
		
	
		
		
			com.alibaba
			druid
			1.1.8
		
		
   log4j
   log4j
   1.2.16
   compile
	
		
		
			com.alibaba
			fastjson
			1.2.26
		
		
		
		
		    com.oracle
		    sdoutl
		    11.2.0
		
		
		    com.oracle
		    sdoapi
		    11.2.0
		
		
		    com.oracle
		    ojdbc6
		    11.2.0.4.0-atlassian-hosted
		
		
			org.springframework.boot
			spring-boot-devtools
			true
			
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
				
					true
				
			
		
	
	
	
		
			spring-milestones
			Spring Milestones
			https://repo.spring.io/milestone
			
				false
			
		
		
            spring-snapshots
            Spring Snapshots
            https://repo.spring.io/snapshot
            
                true
            
        
	
	
		
			spring-milestones
			Spring Milestones
			https://repo.spring.io/milestone
			
				false
			
		
		 
            spring-snapshots
            Spring Snapshots
            https://repo.spring.io/snapshot
            
                true
            
        
	

application.properties

server.port=8090

#ES配置
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
#若没有下面的设置会报出elasticsearchTemplate注入Bean失败
spring.data.elasticsearch.repositories.enabled=true  

#ORACLE数据源
spring.orcl.type=com.alibaba.druid.pool.DruidDataSource
spring.orcl.driverClassName=oracle.jdbc.OracleDriver
spring.orcl.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
spring.orcl.username=scott
spring.orcl.password=tiger

# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.orcl.initialSize=5
spring.orcl.minIdle=5
spring.orcl.maxActive=30
# 配置获取连接等待超时的时间
spring.orcl.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 
spring.orcl.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒 
spring.orcl.minEvictableIdleTimeMillis=300000
spring.orcl.validationQuery=SELECT 1 FROM DUAL
spring.orcl.testWhileIdle=true
spring.orcl.testOnBorrow=true
spring.orcl.testOnReturn=false
spring.orcl.keepAlive=true
spring.orcl.validationQueryTimeout=10
# 打开PSCache,并且指定每个连接上PSCache的大小 
spring.orcl.poolPreparedStatements=true
spring.orcl.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 
spring.orcl.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.orcl.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000

二、创建JPA DataSource

DataSourceConfigure类

package com.geostar.datasource;

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class DataSourceConfigure {

    @Bean(name = "orclDataSource")
    @ConfigurationProperties(prefix = "spring.orcl")
    public DataSource orclDataSource() {
        return  DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
    }

}

JpaCrudServcie类(JpaRepository)

package com.geostar.jpa.repository;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;



@Repository
public class JpaCrudServcie {
	
	@PersistenceContext(name = "entityManager")
	private EntityManager entityManager;
	
	@Transactional("transactionManagerJpa")
	public void save(Object object) {
		try {
			entityManager.persist(object);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Transactional("transactionManagerJpa")
	public void saveOrupdate(Object object) {
		try {
			entityManager.merge(object);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@SuppressWarnings("unchecked")
	//jpa使用JPQL语句查询
	public List queryByJpql(String jpql) {
		Query query = null;
		List resultList = null;
		try {
			query = entityManager.createQuery(jpql);
			resultList = query.getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
} 
  

JpaTransactionalConfigure类

package com.geostar.datasource;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactory",
        transactionManagerRef="transactionManagerJpa",
        basePackages= {"com.geostar.jpa.repository"}) //设置Repository所在位置
public class JpaTransactionalConfigure {

    @Autowired
    @Qualifier("orclDataSource")
    private DataSource orclDataSource;

    @Bean(name = "entityManager")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactory(builder).getObject().createEntityManager();
    }

    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory (EntityManagerFactoryBuilder builder) {
		LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(orclDataSource).packages("com.geostar.domain") // 设置实体类所在位置
				.build();
		entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
    	return entityManagerFactory;
    }
    
    @Bean
    public HibernateJpaVendorAdapter jpaVendorAdapter() {
    	HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
    	jpaVendorAdapter.setDatabase(Database.ORACLE);
    	jpaVendorAdapter.setShowSql(true);
    	return jpaVendorAdapter;
    }

    @Bean(name = "transactionManagerJpa")
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactory(builder).getObject());
    }
}

注意:在类上@EnableJpaRepositories注解中设置创建的JpaRepository的位置,在entityManagerFactory()方法中设置实体类所在位置。

三、创建自己的Repository

ArticleSearchRepository类

package com.geostar.elasticsearch.repository;

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.geostar.domain.T_YW_GA_JWRK;

public interface ArticleSearchRepository extends ElasticsearchRepository{
	 
}

继承ElasticsearchRepository,同时指定实体类及其主键的数据类型()。

四、创建实体类

实体类T_YW_GA_JWRK

package com.geostar.domain;

import java.sql.Date;
import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.springframework.data.elasticsearch.annotations.Document;

@Document(indexName="blockdata",type="T_YW_GA_JWRK",indexStoreType="fs",shards=5,replicas=1,refreshInterval="-1")
@Entity
@Table(name = "T_YW_GA_JWRK")
public class T_YW_GA_JWRK implements Serializable{
	@Id
	@org.springframework.data.annotation.Id
	private	String	ID	;//	人员唯一ID
	private	String	RKID	;//	人口ID
	private	String	GJ	;//	国籍(地区)
   // getter/setter....
	
}

实体类映射ORACLE数据源时需添加@Entity和@Table(name = "XXX")注解,@Table中设置对应的ORACLE数据库表名称。实体类的属性字段可以使用@Column(name = "XXX")进行注解并保持数据类型同ORACLE中表字段类型对应(属性字段名称与ORACLE一致时也可以不添加@Column,若想在实体类中添加标签属性不与数据库映射,可添加@Transient注解)。主键字段同时还需使用@Id进行注解。

实体类映射ElasticSearch时,需添加@Document注解。注解中indexName为ElasticSearch中的索引,相当于ORACLE中的DB;type为类型,相当于ORACLE中的TABLE。同时实体类的主键需要加上ElasticSearch的@Id注解(由于加过了JPA的@Id注解,这里使用@org.springframework.data.annotation.Id的方式加入)。

关于@Document注解可以参考源码:

@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {

String indexName();//索引库的名称,个人建议以项目的名称命名

String type() default "";//类型,个人建议以实体的名称命名

short shards() default 5;//默认分区数

short replicas() default 1;//每个分区默认的备份数

String refreshInterval() default "1s";//刷新间隔

String indexStoreType() default "fs";//索引文件存储类型
}
加上@Document注解后,默认情况下这个实体中所有的属性都会被建立索引、并且分词。 
我们通过@Field注解来进行详细的指定,如果没有特殊需求,那么只需要添加@Document即可。

@Field定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Field {

FieldType type() default FieldType.Auto;#自动检测属性的类型

FieldIndex index() default FieldIndex.analyzed;#默认情况下分词

DateFormat format() default DateFormat.none;

String pattern() default "";

boolean store() default false;#默认情况下不存储原文

String searchAnalyzer() default "";#指定字段搜索时使用的分词器

String indexAnalyzer() default "";#指定字段建立索引时指定的分词器

String[] ignoreFields() default {};#如果某个字段需要被忽略

boolean includeInParent() default false;
}
五、测试写入数据

JPA和Repository创建完成后就可以测试将数据写入ElasticSearch了。

测试类test

package com.geostar.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.geostar.domain.T_YW_GA_JWRK;
import com.geostar.elasticsearch.repository.ArticleSearchRepository;
import com.geostar.jpa.repository.JpaCrudServcie;


@RunWith(SpringRunner.class)
@SpringBootTest
public class test {
	@Autowired
	JpaCrudServcie jpaCrudServcie;
	@Autowired
	ArticleSearchRepository repository;
	
	@Test
	public void repositorytest(){
		String jpql = "select t from T_YW_GA_JWRK t ";
		List list = jpaCrudServcie.queryByJpql(jpql);
		T_YW_GA_JWRK t = (T_YW_GA_JWRK) list.get(0);
		repository.save(t);
	}
		
} 
  

写入完成后可以通过elasticsearch的head插件来查看,在浏览器中输入http://127.0.0.1:9100/,可以在概览中查看索引,在数据浏览和基本查询中查看写入的数据。

SpringBootDataElasticSearch+JPA实现将ORACLE数据批量写入ElasticSearch_第1张图片

六、使用elasticsearchTemplate批量写入

使用上面的Repository可以对elasticsearch进行基本的插入、修改、删除、查询等操作,elasticsearchTemplate提供了更多的方法便于我们操作elasticsearch。

下面的例子是通过elasticsearchTemplate实现ORACLE数据批量写入elasticsearch。

IndexerService类

package com.geostar.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.geostar.domain.T_YW_GA_JWRK;
import com.geostar.jpa.repository.JpaCrudServcie;

@Service
public class IndexerService {
	private static final String INDEX_NAME = "blockdata"; //定义索引
	private static final String INDEX_TYPE = "T_YW_GA_JWRK";//定义类型
	@Autowired
	ElasticsearchTemplate elasticsearchTemplate;
	
	@Autowired
	JpaCrudServcie jpaCrudServcie;

	public long bulkIndex() throws Exception {
		int counter = 0;
		try {
			// 判断索引是否存在
			if (!elasticsearchTemplate.indexExists(INDEX_NAME)) {
				elasticsearchTemplate.createIndex(INDEX_NAME);
			}
			elasticsearchTemplate.putMapping(T_TX_ZWYC_YXGT.class);
			List queries = new ArrayList();
			String jpql = "select t from T_YW_GA_JWRK t ";
			List list = jpaCrudServcie.queryByJpql(jpql);
			
			for(Object e : list){
				T_YW_GA_JWRK jwrk = (T_YW_GA_JWRK) e;
				IndexQuery indexQuery = new IndexQuery();
				indexQuery.setId(jwrk.getID().toString());
				indexQuery.setSource(JSON.toJSONString(jwrk));
				indexQuery.setIndexName(INDEX_NAME);
				indexQuery.setType(INDEX_TYPE);
				queries.add(indexQuery);

				// 分批提交索引
				if (counter % 500 == 0) {
					elasticsearchTemplate.bulkIndex(queries);
					queries.clear();
					System.out.println("bulkIndex counter : " + counter);
				}
				counter++;
			}

			// 不足批的索引最后不要忘记提交
			if (queries.size() > 0) {
				elasticsearchTemplate.bulkIndex(queries);
			}
			elasticsearchTemplate.refresh(INDEX_NAME);
			System.out.println("bulkIndex completed.");
		} catch (Exception e) {
			System.out.println("IndexerService.bulkIndex e;" + e.getMessage());
			throw e;
		}
		return -1;
	}
} 
  

七、遇到的问题

在搭建本次示例工程之前,一直使用的SpringBoot 2.0.0M3里程碑版,为了能够跟更高版本的ElasticSearch兼容,本次工程使用了SpringBoot 2.0.2RELEASE版本,中间遇到了很多问题,记录在此。

1、将SpringBoot从2.0.0M3里程碑版本升级至2.0.2RELEASE发行版后,使用Durid连接池创建DataSource时,DataSourceBuilder的引入JAR包失效。

解决方案:SpringBoot2.0.2RELEASE版本DataSourceBuilder迁移至org.springframework.boot.jdbc.DataSourceBuilder,修改JAR包import路径即可。

2、SpringBoot2.0.2RELEASE版本中初始化Druid连接池时报出"druid Unable to set value for property filters"。

解决方案:将applaction.properties配置文件中数据库连接池的filters配置设置为spring.orcl.filters=stat,wall,log4j。且需在POM文件中引入log4j依赖。

3、引入Spring Boot Data ElasticSearch后,项目启动失败,报出"Error creating bean with name 'elasticsearchTemplate' defined in class path resource"。

解决方案:报出此错误是因为ElasticSearch配置缺失,无法注入elasticsearchTemplate,在application.properties中加入spring.data.elasticsearch.repositories.enabled=true即可。

4、实体类中设置了地理类型属性时,使用elasticsearchTemplate映射至ES后是一个嵌套类型,内部是float,并不是我想要的geo_point,经查阅官方文档得知,地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型

解决方案:在使用elasticsearchTemplate时,显的调用putMapping()方法,将mapping设置进去即可,示例如下:

	                Map m = new LinkedHashMap();
			Map m1 = new LinkedHashMap();
			Map m2 = new LinkedHashMap();
			if(ispoint){//点坐标设置geo_point类型
				m.put("type", "geo_point");
				m1.put("geoPoint", m);
			}else{//线、面坐标设置geo_shape类型
				m.put("type", "geo_shape");
				m1.put("geoShape", m);
			}
			m2.put("properties", m1);
			elasticsearchTemplate.putMapping(INDEX_NAME, INDEX_TYPE, m2);

构建map时注意,实体类存储地理坐标的对象字段名称需和map中设置的key保持一致,这样转换成JSONSTRING后,在IndexQuery.setSource()时才能保证字段对应。

5、因为对API不够熟悉,使用API中各种builder创建地理数据对象插入时经常报错(json转换失败导致的栈溢出、类型不对应等等),后来经查阅ES官方文档 geoshape数据类型后发现,只要按照GeoJson的标准将坐标信息组装起来即可(最终IndexQuery.setSource()时地理数据字段的string符合GeoJson标准)。

6、实体类中自定义类型字段映射时报错,这个字段不需要写入ES,听说@Field注解可以排除不需要建立至ES的字段,试了很久没试出来,用了一个笨办法,创建IndexQuery时,不使用setObject的方式,将对象不需要的字段设置为null,然后转换成JSON字符串,使用setSource方法将json字符串传入。

你可能感兴趣的:(ElasticSearch)