Get Aggregate Information from Elasticsearch using Spring-data-elasticsearch, ElasticsearchRepository - elasticsearch

I would like to get aggregate results from ES like avgSize (avg of a field with name 'size'), totalhits for documents that match a term, and some other aggregates in future, for which I don't think ElasticsearchRepository has any methods to call. I built Query and Aggregate Builders as below. I want to use my Repository interface but I am not sure of what should the return ObjectType be ? Should it be a document type in my DTOs ? Also I have seen examples where the searchQueryis passed directly to ElasticsearchTemplate but then what is the point of having Repository interface that extends ElasticsearchRepository
Repository Interface
public interface CCFilesSummaryRepository extends ElasticsearchRepository<DataReferenceSummary, UUID> {
}
Elastic configuration
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.xxx.repository.es")
public class ElasticConfiguration {
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws UnknownHostException {
return new ElasticsearchTemplate(elasticsearchClient());
}
#Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getLocalHost(), 9200));
return client;
}
}
Service Method
public DataReferenceSummary createSummary(final DataSet dataSet) {
try {
QueryBuilder queryBuilder = QueryBuilders.matchQuery("type" , dataSet.getDataSetCreateRequest().getContentType());
AvgAggregationBuilder avgAggregationBuilder = AggregationBuilders.avg("avg_size").field("size");
ValueCountAggregationBuilder valueCountAggregationBuilder = AggregationBuilders.count("total_references")
.field("asset_id");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
.addAggregation(avgAggregationBuilder)
.addAggregation(valueCountAggregationBuilder)
.build();
return ccFilesSummaryRepository.search(searchQuery).iterator().next();
} catch (Exception e){
e.printStackTrace();
}
return null;
}
DataReferernceSummary is just a POJO for now and for which I am getting an error during my build that says Unable to build Bean CCFilesSummaryRepository, illegalArgumentException DataReferernceSummary. is not a amanged Object

First DataReferenceSummary must be a class annotated with #Document.
In Spring Data Elasticsearch 3.2.0 (the current version) you need to define the repository return type as AggregatedPage<DataReferenceSummary>, the returned object will contain the aggregations.
From the upcoming version 4.0 on, you will have to define the return type as SearchHits<DataReferenceSummary> and find the aggregations in this returned object.

Related

ListenerExecutionFailedException Nullpointer when trying to index kafka payload through new ElasticSearch Java API Client

I'm migrating from the HLRC to the new client, things were smooth but for some reason I cannot index a specific class/document. Here is my client implementation and index request:
#Configuration
public class ClientConfiguration{
#Autowired
private InternalProperties conf;
public ElasticsearchClient sslClient(){
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(conf.getElasticsearchUser(), conf.getElasticsearchPassword()));
HttpHost httpHost = new HttpHost(conf.getElasticsearchAddress(), conf.getElasticsearchPort(), "https");
RestClientBuilder restClientBuilder = RestClient.builder(httpHost);
try {
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, (x509Certificates, s) -> true).build();
restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
#Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext)
.setDefaultCredentialsProvider(credentialsProvider);
}
});
} catch (Exception e) {
e.printStackTrace();
}
RestClient restClient=restClientBuilder.build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);
return client;
}
}
#Service
public class ThisDtoIndexClass extends ConfigAndProperties{
public ThisDtoIndexClass() {
}
//client is declared in the class it's extending from
public ThisDtoIndexClass(#Autowired ClientConfiguration esClient) {
this.client = esClient.sslClient();
}
#KafkaListener(topics = "esTopic")
public void in(#Payload(required = false) customDto doc)
throws ThisDtoIndexClassException, ElasticsearchException, IOException {
if(doc!= null && doc.getId() != null) {
IndexRequest.Builder<customDto > indexReqBuilder = new IndexRequest.Builder<>();
indexReqBuilder.index("index-for-this-Dto");
indexReqBuilder.id(doc.getId());
indexReqBuilder.document(doc);
IndexResponse response = client.index(indexReqBuilder.build());
} else {
throw new ThisDtoIndexClassException("document is null");
}
}
}
This is all done in spring boot (v2.6.8) with ES 7.17.3. According to the debug, the payload is NOT null! It even fetches the id correctly while stepping through. For some reason, it throws me a org.springframework.kafka.listener.ListenerExecutionFailedException: in the last line (during the .build?). Nothing gets indexed, but the response comes back 200. I'm lost on where I should be looking. I have a different class that also writes to a different index, also getting a payload from kafka directly (all seperate consumers). That one functions just fine.
I suspect it has something to do with the way my client is set up and/or the kafka. Please point me in the right direction.
I solved it by deleting the default constructor. If I put it back it overwrites the extended constructor (or straight up doesn't acknowledge the extended constructor), so my client was always null. The error message it gave me was extremely misleading since it actually wasn't the Kafka's fault!
Removing the default constructor completely initializes the correct constructor and I was able to index again. I assume this was a spring boot loading related "issue".

Dozer seeks xml configs instead of Java confiigs

I am working on a Spring Boot project with Spring Data Rest, Gradle and Oracle Express DB in which I use DozerBeanMapper to map entities to DTOs and vice versa, I use no xml configurations for Dozer, just Java ones:
#Slf4j
#Configuration
public class DozerConfig {
#Bean
public DozerBeanMapper getDozerMapper() {
log.info("Initializing DozerBeanMapper bean");
return new DozerBeanMapper();
}
}
Also for clarity I have explicitly configured all the fields that has to be mapped although they are all with the same names. For example my Client mapper:
#Slf4j
#Component
public class ClientMapper extends BaseMapper {
private BeanMappingBuilder builder = new BeanMappingBuilder() {
#Override
protected void configure() {
mapping(Client.class, ClientDTO.class)
.fields("id", "id")
.fields("name", "name")
.fields("midName", "midName")
.fields("surname", "surname")
.exclude("password")
.fields("phone", "phone")
.fields("email", "email")
.fields("address", "address")
.fields("idCardNumber", "idCardNumber")
.fields("idCardIssueDate", "idCardIssueDate")
.fields("idCardExpirationDate", "idCardExpirationDate")
.fields("bankAccounts", "bankAccounts")
.fields("accountManager", "accountManager")
.fields("debitCardNumber", "debitCardNumber")
.fields("creditCardNumber", "creditCardNumber")
.fields("dateCreated", "dateCreated")
.fields("dateUpdated", "dateUpdated");
}
};
#Autowired
public ClientMapper(DozerBeanMapper mapper) {
super(mapper);
mapper.addMapping(builder);
}
public ClientDTO toDto(Client entity) {
log.info("Mapping Client entity to DTO");
return mapper.map(entity, ClientDTO.class);
}
public Client toEntity(ClientDTO dto) {
log.info("Mapping Client DTO to entity");
return mapper.map(dto, Client.class);
}
public List<ClientDTO> toDtos(List<Client> entities) {
log.info("Mapping Client entities to DTOs");
return entities.stream()
.map(entity -> toDto(entity))
.collect(Collectors.toList());
}
public List<Client> toEntities(List<ClientDTO> dtos) {
log.info("Mapping Client DTOs to entities");
return dtos.stream()
.map(dto -> toEntity(dto))
.collect(Collectors.toList());
}
public EmployeeDTO toEmployeeDto(Employee entity) {
log.info("Mapping Employee entity to DTO");
return mapper.map(entity, EmployeeDTO.class);
}
public Employee toEmployeeEntity(EmployeeDTO dto) {
log.info("Mapping Employee DTO to entity");
return mapper.map(dto, Employee.class);
}
public List<EmployeeDTO> toEmployeeDtos(List<Employee> entities) {
log.info("Mapping Employee entities to DTOs");
return entities.stream()
.map(entity -> toEmployeeDto(entity))
.collect(Collectors.toList());
}
public List<Employee> toEmployeeEntities(List<EmployeeDTO> dtos) {
log.info("Mapping Employee DTOs to entities");
return dtos.stream()
.map(dto -> toEmployeeEntity(dto))
.collect(Collectors.toList());
}
}
Despite this I get the following exception:
"httpStatus": "500 Internal Server Error",
"exception": "java.lang.IllegalArgumentException",
"message": "setAttribute(name, value):\n name: "http://apache.org/xml/features/validation/schema\"\n value: \"true\"",
"stackTrace": [
"oracle.xml.jaxp.JXDocumentBuilderFactory.setAttribute(JXDocumentBuilderFactory.java:289)",
"org.dozer.loader.xml.XMLParserFactory.createDocumentBuilderFactory(XMLParserFactory.java:71)",
"org.dozer.loader.xml.XMLParserFactory.createParser(XMLParserFactory.java:50)",
"org.dozer.loader.xml.MappingStreamReader.<init>(MappingStreamReader.java:43)",
"org.dozer.loader.xml.MappingFileReader.<init>(MappingFileReader.java:44)",
"org.dozer.DozerBeanMapper.loadFromFiles(DozerBeanMapper.java:219)",
"org.dozer.DozerBeanMapper.loadCustomMappings(DozerBeanMapper.java:209)",
"org.dozer.DozerBeanMapper.initMappings(DozerBeanMapper.java:315)",
"org.dozer.DozerBeanMapper.getMappingProcessor(DozerBeanMapper.java:192)",
"org.dozer.DozerBeanMapper.map(DozerBeanMapper.java:120)",
"com.rosenhristov.bank.exception.mapper.ClientMapper.toDto(ClientMapper.java:52)",
"com.rosenhristov.bank.service.ClientService.lambda$getClientById$0(ClientService.java:27)",
"java.base/java.util.Optional.map(Optional.java:265)",
"com.rosenhristov.bank.service.ClientService.getClientById(ClientService.java:27)",
"com.rosenhristov.bank.controller.ClientController.getClientById(ClientController.java:57)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)",
"java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
"java.base/java.lang.reflect.Method.invoke(Method.java:566)",
"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)",
"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)",
"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)",
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893)",
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:807)",
"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)",
"org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061)",
"org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961)",
"org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)",
"org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)",
"javax.servlet.http.HttpServlet.service(HttpServlet.java:626)",
"org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)",
"javax.servlet.http.HttpServlet.service(HttpServlet.java:733)",
"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)",
"org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)",
"org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)",
"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)",
"org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)",
"org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)",
"org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)",
"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)",
"org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)",
"org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)",
"org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)",
"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)",
"org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)",
"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)",
"org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)",
"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)",
"org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)",
"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)",
"org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)",
"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)",
"org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)",
"org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)",
"org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)",
"org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)",
"org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)",
"org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)",
"org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)",
"org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)",
"org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)",
"org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)",
"org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)",
"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)",
"org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)",
"java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)",
"java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)",
"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)",
"java.base/java.lang.Thread.run(Thread.java:834)"
]
It seems Dozer is trying to find some xml conffig file taking into account this:
"oracle.xml.jaxp.JXDocumentBuilderFactory.setAttribute(JXDocumentBuilderFactory.java:289)"
It seems it is searching for a xml validation schema (take a look at var1 on the picture below):
When I start the application I just saw the following in the IntelliJ console:
Trying to find Dozer configuration file: dozer.properties
2020-12-23 11:46:09.855 WARN 17488 --- [ restartedMain] org.dozer.config.GlobalSettings: Dozer configuration file not found: dozer.properties. Using defaults for all Dozer global properties.
Probably I have to search for dozer.properties and find out how to make it look for Java configurations?
Can someone help me, please? I searched for some solution in internet but I still haven't found a suitable one. I am new to Dozer, I have used Mapstruct before?
You can try my beanknife to generate the dto file automatically. It will has a read method to convert the entity to dto. Although no converter from dto to entity, I think you don't need it in most situation.
#ViewOf(value = Client.class, genName = "ClientDto", includePattern = ".*")
class ClientDtoConfiguration {}
Then it will generate a dto class named "ClientDto" with all the properties of Client.
Client client = ...
ClientDto clientDto = ClientDto.read(client);
List<Client> clients = ...
List<ClientDto> clientDtos = ClientDto.read(clients);
Then serialize the dtos instead of entities.

JAXBElement: providing codec (/converter?) for class java.lang.Class

I have been evaluating to adopt spring-data-mongodb for a project. In summary, my aim is:
Using existing XML schema files to generate Java classes.
This is achieved using JAXB xjc
The root class is TSDProductDataType and is further modeled as below:
The thing to note here is that ExtensionType contains protected List<Object> any; allowing it to store Objects of any class. In my case, it is amongst the classes named TSDModule_Name_HereModuleType and can be browsed here
Use spring-data-mongodb as persistence store
This is achieved using a simple ProductDataRepository
#RepositoryRestResource(collectionResourceRel = "product", path = "product")
public interface ProductDataRepository extends MongoRepository<TSDProductDataType, String> {
TSDProductDataType queryByGtin(#Param("gtin") String gtin);
}
The unmarshalled TSDProductDataType, however, contains JAXBElement which spring-data-mongodb doesn't seem to handle by itself and throws a CodecConfigurationException org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.lang.Class.
Here is the faulty statement:
TSDProductDataType tsdProductDataType = jaxbElement.getValue();
repository.save(tsdProductDataType);
I tried playing around with Converters for spring-data-mongodb as explained here, however, it seems I am missing something since the exception is about "Codecs" and not "Converters".
Any help is appreciated.
EDIT:
Adding converters for JAXBElement
Note: Works with version 1.5.6.RELEASE of org.springframework.boot::spring-boot-starter-parent. With version 2.0.0.M3, hell breaks loose
It seems that I missed something while trying to add converter earlier. So, I added it like below for testing:
#Component
#ReadingConverter
public class JAXBElementReadConverter implements Converter<DBObject, JAXBElement> {
//#Autowired
//MongoConverter converter;
#Override
public JAXBElement convert(DBObject dbObject) {
Class declaredType, scope;
QName name = qNameFromString((String)dbObject.get("name"));
Object rawValue = dbObject.get("value");
try {
declaredType = Class.forName((String)dbObject.get("declaredType"));
} catch (ClassNotFoundException e) {
if (rawValue.getClass().isArray()) declaredType = List.class;
else declaredType = LinkedHashMap.class;
}
try {
scope = Class.forName((String) dbObject.get("scope"));
} catch (ClassNotFoundException e) {
scope = JAXBElement.GlobalScope.class;
}
//Object value = rawValue instanceof DBObject ? converter.read(declaredType, (DBObject) rawValue) : rawValue;
Object value = "TODO";
return new JAXBElement(name, declaredType, scope, value);
}
QName qNameFromString(String s) {
String[] parts = s.split("[{}]");
if (parts.length > 2) return new QName(parts[1], parts[2], parts[0]);
if (parts.length == 1) return new QName(parts[0]);
return new QName("undef");
}
}
#Component
#WritingConverter
public class JAXBElementWriteConverter implements Converter<JAXBElement, DBObject> {
//#Autowired
//MongoConverter converter;
#Override
public DBObject convert(JAXBElement jaxbElement) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", qNameToString(jaxbElement.getName()));
dbObject.put("declaredType", jaxbElement.getDeclaredType().getName());
dbObject.put("scope", jaxbElement.getScope().getCanonicalName());
//dbObject.put("value", converter.convertToMongoType(jaxbElement.getValue()));
dbObject.put("value", "TODO");
dbObject.put("_class", JAXBElement.class.getName());
return dbObject;
}
public String qNameToString(QName name) {
if (name.getNamespaceURI() == XMLConstants.NULL_NS_URI) return name.getLocalPart();
return name.getPrefix() + '{' + name.getNamespaceURI() + '}' + name.getLocalPart();
}
}
#SpringBootApplication
public class TsdApplication {
public static void main(String[] args) {
SpringApplication.run(TsdApplication.class, args);
}
#Bean
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(
new JAXBElementReadConverter(),
new JAXBElementWriteConverter()
));
}
}
So far so good. However, how do I instantiate MongoConverter converter;?
MongoConverter is an interface so I guess I need an instantiable class adhering to this interface. Any suggestions?
I understand the desire for convenience in being able to just map an existing domain object to the database layer with no boilerplate, but even if you weren't having the JAXB class structure issue, I would still be recommending away from using it verbatim. Unless this is a simple one-off project, you almost definitely will hit a point where your domain models will need to change but your persisted data need to remain in an existing state. If you are just straight persisting the data, you have no mechanism to convert between a newer domain schema and an older persisted data scheme. Versioning of the persisted data scheme would be wise too.
The link you posted for writing the customer converters is one way to achieve this and fits in nicely with the Spring ecosystem. That method should also solve the issue you are experiencing (about the underlying messy JAXB data structure not converting cleanly).
Are you unable to get that method working? Ensure you are loading them into the Spring context with #Component plus auto-class scanning or manually via some Configuration class.
EDIT to address your EDIT:
Add the following to each of your converters:
private final MongoConverter converter;
public JAXBElement____Converter(MongoConverter converter) {
this.converter = converter;
}
Try changing your bean definition to:
#Bean
public CustomConversions customConversions(#Lazy MongoConverter converter) {
return new CustomConversions(Arrays.asList(
new JAXBElementReadConverter(converter),
new JAXBElementWriteConverter(converter)
));
}

How to store Java 8 (JSR-310) dates in elasticsearch

I know elasticsearch can only save Date types internally. But can i make it aware to store/convert Java 8 ZonedDateTime, as i use this type in my entity?
I'm using spring-boot:1.3.1 + spring-data-elasticsearch with jackson-datatype-jsr310 on the classpath. No conversions seem to apply neither when i try to save a ZonedDateTime nor Instant or something else.
One way of doing this is to create custom converter like this:
import com.google.gson.*;
import java.lang.reflect.Type;
import java.time.ZonedDateTime;
import static java.time.format.DateTimeFormatter.*;
public class ZonedDateTimeConverter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
#Override
public ZonedDateTime deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return ZonedDateTime.parse(jsonElement.getAsString(), ISO_DATE_TIME);
}
#Override
public JsonElement serialize(ZonedDateTime zonedDateTime, Type type, JsonSerializationContext jsonSerializationContext) {
return new JsonPrimitive(zonedDateTime.format(ISO_DATE_TIME));
}
}
and then configure JestClientFactory to use this converter:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeConverter()).create();
JestClientFactory factory = new JestClientFactory();
factory.setHttpClientConfig(new HttpClientConfig
.Builder("elastic search URL")
.multiThreaded(true)
.gson(gson)
.build());
client = factory.getObject();
Hope it'll help.

Spring Boot Actuator Health Indicator

We have been using Spring Boot for several projects now, We are using the latest version 1.2.3. We are incorporating Actuator. So far things are working well except we are finding that the /health indicator [default] is showing that the service is down. This is not true. These services are that implement with datasources. It may call other SOAP or Rest services. What is the health service looking at to measure whether a service is down?
As #derFuerst said the DataSourceHealthIndicator has the default query to check whether the DB is up or not.
If you want to use this the proper vendor specific query you should write your own health indicator in your configuration class, like this in case of Oracle data source:
#Autowired(required = false)
private DataSource dataSource;
#Bean
#Primary
public DataSourceHealthIndicator dataSourceHealthIndicator() {
return new DataSourceHealthIndicator(dataSource, "SELECT 1 FROM DUAL");
}
The DataSourceHealthIndicator is used to check availablity. The default query is SELECT 1, but there are some product specific queries, too.
You can write your own HealthIndicator. Either you implement the interface or extend the AbstractHealthIndicator.
To disable the default db-health-check put this line into your application properties management.health.db.enabled=false.
Hope that helps
The above comment helpedme to init my research but for me it was not enough :
#Bean
#Primary
public DataSourceHealthIndicator dataSourceHealthIndicator() {
return new DataSourceHealthIndicator(dataSource, "SELECT 1 FROM DUAL");
}
Here is the configuration that helped me to make it run: Define HealthIndicator #Bean like the follow and provide the required query :
#Bean
#Primary
public HealthIndicator dbHealthIndicator() {
return new DataSourceHealthIndicator(dataSource, "SELECT 1 FROM DUMMY");
}
If no Query is providen the SELECT 1 will be used . As #derFuerst said will be used , Here is the defailt implementation of DataSourceHealthIndicator :
public DataSourceHealthIndicator(DataSource dataSource, String query) {
super("DataSource health check failed");
this.dataSource = dataSource;
this.query = query;
this.jdbcTemplate = dataSource != null ? new JdbcTemplate(dataSource) : null;
}
...
protected String getValidationQuery(String product) {
String query = this.query;
if (!StringUtils.hasText(query)) {
DatabaseDriver specific = DatabaseDriver.fromProductName(product);
query = specific.getValidationQuery();
}
if (!StringUtils.hasText(query)) {
query = "SELECT 1";
}
return query;
}
Hi everyone I'm a beginner to health check using actuator. I used the below solution and it worked,
#Autowired(required = false)
private DataSource dataSource;
#Bean
#Primary
public DataSourceHealthIndicator dataSourceHealthIndicator() {
return new DataSourceHealthIndicator(dataSource, "SELECT 1 FROM DUAL");
}
But can anyone please let me know how validation query is working even though there isn't a table named Dual. Also as per my understanding when we request "/actuator/health", all implementations of HealthIndicator are called automatically and health check methods are executed. Kindly correct me if I'm wrong

Resources