一、环境配置
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
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定义如下:
@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/,可以在概览中查看索引,在数据浏览和基本查询中查看写入的数据。
六、使用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字符串传入。