Elasticsearch with spring boot when using index query return nullPointerExciption - spring-boot

I am using Elasticsearch with spring boot. A post request returns a null pointer exception, because index query is null value doesn't have index name or any things.
Look at my code
Service :
public List<Product> createProducts() {
List<Product>productList = new ArrayList<>();
for (Integer i = 0; i < 200; i++)
{
Product product = new Product();
product.setId(Long.parseLong(i.toString()));
product.setProductName(generateName());
product.setProductPrice(generatePrice());
product.setCategory(generateCategory());
if(!product.validation().equals(""))
{
throw new BadRequestAlertException(product.validation(),"Product","check input");
}
IndexQuery indexQuery=new IndexQueryBuilder().withId(i.toString()).build(); //return null
elasticsearchOperations.index(indexQuery); // here is error becouse index is null
productList.add(product);
}
return productList;
}
And this is the entity:
#JsonInclude(value = JsonInclude.Include.NON_NULL)
#Document(indexName = "product",type = "product")
public class Product implements Serializable {
private static final long serialVersionUID = 6320548148250372657L;
#Id
private Long id;
#Field(type = FieldType.Text)
private String productName;
#Field(type = FieldType.Text)
private String category;
#Field(type = FieldType.Double)
private Double productPrice;
This is the repostory:
public interface ProductSearchRepostory extends ElasticsearchRepository<Product,Long> {
List<Product> findByProductName(String name);
List<Product> findByCategory(String category);
}

You need to let the elasticsearchOperations.index method know which index to save the data in.
As per the documentation present for Spring Data Elasticsearch, in the newer versions, the index method expects an IndexCoordinates object which tells the client which index to put the data in. For the older versions ( < 4.0), this is inferred by the client based on the entity object that is being indexed.
In your code, can you please try to pass the entity while building the IndexQuery. Something like,
new IndexQueryBuilder().withId(i.toString()).withObject(product).build()

Related

Spring Boot Entity model member converter

Here my problem, I have to store serialized website's cookie information in DB, and I tried to write a simple PesistentConverter to it can be convert Map to String and vice-versa. But the IDE show a warning about this:
'Basic' attribute type should not be a map
Here my Entity:
#Entity
#Table(name = "website")
public class Website implements Serializable {
#Id
#Column(name = "name", length = 16, nullable = false)
#NotNull
private String name;
#Column(name = "serialized_cookie_map", nullable = false, length = 2048)
#Convert(converter = CookieMapPersistenceConverter.class)
#NotNull
private Map<String,String> serializedCookieMap;
...
}
CookieMapPersistenceConverter:
public class CookieMapPersistenceConverter implements AttributeConverter<Map<String, String>, String> {
#Override
public String convertToDatabaseColumn(Map<String, String> stringStringMap) {
return stringStringMap.toString();
}
#Override
public Map<String, String> convertToEntityAttribute(String s) {
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, String.class);
HashMap<String,String> convertedMap = null;
try {
convertedMap = mapper.readValue(s, mapType);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return convertedMap;
}
}
I'm using the 2.6.3 version of Spring Boot.
Many thanks your time!
According to this:
Any attributes that have no other annotations and do not reference
other entities will be automatically mapped as basic.
and according to the table from the above link map is not a basic type. so IDE is right about
'Basic' attribute type should not be a map
I suggest try the following approach to see it satisfies your requirements or not.
#Id
#Column(name = "id", length = 16, nullable = false)
#NotNull
private String id;
#ElementCollection
#MapKeyColumn(name="property")
#Column(name="value")
#CollectionTable(name="cookies", joinColumns=#JoinColumn(name="id"))
#NotNull
private Map<String,String> serializedCookieMap;
//setter and getter
and the code for repositry:
#Repository
public interface WebsiteRepository extends CrudRepository<Website, String> {}
code I did for test:
Website w = new Website();
w.setId("id");
Map<String, String> m = new HashMap<>();
m.put("key1", "value1");
m.put("key2", "value2");
m.put("key3", "value3");
w.setSerializedCookieMap(m);
repo.save(w);
which creates two table named WEBSITE and COOKIES and the content of each is like this:
WEBSITES:
ID
id
and:
COOKIES:
ID VALUE PROPERTY
id value1 key1
id value2 key2
id value3 key3
if you insist to store Serialized version of map take a look at this
I have tried same thing in my project (with same version of spring boot). I did not notice any warning.
But here are some suggestions...
The field you have annotated #convert not meant to be persisted in DB. So instead of #column try using #Transient.
You can also try using #Type( type = "json" ) instead of #convert.
Also this can be possibly issue with IDE, so just to ignore it as a
warning you can use #SuppressWarnings("JpaAttributeTypeInspection") annotation.

JPA Hibernate - Entity with #Loader and a function field in select, won't work properly

#Entity
#Table(name="cad_paciente")
#Loader(namedQuery = "selectInicial")
#NamedNativeQuery(
name="selectInicial",
query="select p.*, fu_obter_lista_convenios_pac(p.id) as ds_convenio from cad_paciente p where p.id = ?", resultClass = Paciente.class,
resultSetMapping = "sqlResult")
#SqlResultSetMapping(
name="sqlResult",
entities={
#EntityResult(entityClass = Paciente.class, fields={
#FieldResult(name="ds_convenio",column="ds_convenio")})})
public class Paciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name="id_empresa")
private Integer id_empresa;
...
#Transient
#Column(name="ds_convenio")
private String ds_convenio;
public String getDs_convenio() {
return ds_convenio;
}
public void setDs_convenio(String ds_convenio) {
this.ds_convenio = ds_convenio;
}
My Controller method "pacientes.findAll()" won't return "ds_convenio" field with the correct value, listing "null" always in my JSON return.
What do I have to do?
Try removing the annotation #Transient and provide the column as below :
#Column(name="ds_convenio")
private String ds_convenio;
#org.springframework.data.annotation.Transient specifically states to the spring framework that the Object Mapper you are using should not include this value when converting from Java Object to JSON. Also, it means that the value is not to be persisted into the database, which means you could not query over it.
Or if you want to keep it as transient itself but does not require the value to be serialized then register the object mapper as below :
#Bean
public ObjectMapper includeTransientObjectMapper() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(hibernate5Module);
return mapper;
}
Or in your case since you want the result of the #NamedNativeQuer in which you aliased ds_convenio, using #FieldResult might be required to get the desired result as follows :
#Entity
#Table(name="cad_paciente")
#Loader(namedQuery = "selectInicial")
#NamedNativeQuery(name="selectInicial", query="select p.*, fu_obter_lista_convenios_pac(p.id) as ds_convenio from cad_paciente p where p.id = ?", resultClass = Paciente.class)
#SqlResultSetMapping(name="Results",
entities={
#EntityResult(entityClass=com.acme.Order.class, fields={
#FieldResult(name="id", column="id"),
#FieldResult(name="id_empresa", column="id_empresa"),
........
})
public class Paciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name="id_empresa")
private Integer id_empresa;
...
#Transient
#Column(name="ds_convenio")
private String ds_convenio;
Read doc

MyBatis #Many / Spring-Boot

I'm beginner (sorry for my bad explanation, feel free to correct me) in MyBatis Spring-Boot, I have problem to understand and make it works #Many
I'm using 3 layer logic programming (Presentation Layer, Service Layer, Data Layer)
Thanks for your help :)
I have 3 Tables (it's TB_Products and not TB_Product as on the screenshot):
I would like to get data form table TB_Users and TB_Products to "put" it in DTO
I create 4 java object class SearchEntity, ProductEntity (for Data layer)
I create an interface SearchRepositoryMapper.
I also create a SearchService interface and SearchServiceImpl as well.
Java object class:
SearchEntity
public class SearchEntity implements Serializable{
private static final long serialVersionUID = -9143930742617602050L;
private String id;
private String firstName;
private String lastName;
private List<ProductEntity> products;
// Getters and Setters code .....
}
ProductEntity
public class ProductEntity implements Serializable{
private static final long serialVersionUID = -6525703679290992635L;
private String id;
private String productId;
private String product;
private String number;
private String date;
private String description;
// Getters and Setters code .....
}
SearchRepositoryMapper
public interface SearchRepositoryMapper {
// Get some fields from TB_Users and all fields from TB_Products
#Select("SELECT * FROM TB_Users WHERE id = #{id}")
#Results({
#Result(property = "id", column ="id"),
#Result(property = "firstName", column = "firstName"),
#Result(property = "lastName", column= "lastName"),
#Result(property = "products", javaType = List.class, column="id",
many = #Many(select = "getProductIdByUserId"))})
public SearchEntity findAllInfoByUserId(#Param("id") int id);
#Select("SELECT *, productId FROM TB_Products WHERE productId = #{id}")
public ArrayList<ProductEntity> getProductIdByUserId(#Param("id") int id);
// Find id by uderId and return null if it doesn't exist
#Select("SELECT id FROM TB_Users WHERE userId = #{userId}")
int findIdByUserId(#Param("userId") String userId);
}
SearchServiceImpl
#Service
public class SearchServiceImpl implements SearchService {
#Autowired
SearchRepositoryMapper searchRepository;
#Override
public SearchDto getAllInfoByUserId(String id) {
SearchDto returnValue = new SearchDto(); // Init returnValue as SearchDto
int searchId = searchRepository.findIdByUserId(id); // Init searchId with the TB_Users id
SearchEntity searchEntity = searchRepository.findAllInfoByUserId(searchId);
BeanUtils.copyProperties(searchEntity, returnValue);
return returnValue;
}
}
So when I execute the code and do a GET request I get this error message:
{
"message": "nested exception is org.apache.ibatis.executor.ExecutorException: Statement returned more than one row, where no more than one was expected."
}
I found out that come from the mapper and SearchEntity searchEntity = searchRepository.findAllInfoByUserId(searchId);
But i don't know how to resolve it. The way I wrote the code is wrong
Thanks to correct me
The exception clearly says that the query returns multiple results. Plese verify if the data in the table is correct.

How to cache only when the json is valid

I have a spring rest api application that is using HATEOAS/PagingAndSortingRepository to do most of the heavy lifting.
I have implemented caching using guava but I am having issues where when the user cancels the request midway through an api call, it caches the incomplete json and re-serves it for 60 seconds.
I am trying to use the unless="" parameter of the #Cacheable annotation. Previously, I just used unless="#result == null" but that does not handle incomplete or invalid json.
This does not seem to work either. So now I am trying to use com.google.gson.JsonParser to parse the result and invalidate if applicable.
Repository
#RepositoryRestResource(path = "products", collectionResourceRel = "products")
public interface ProductEntityRepository extends PagingAndSortingRepository<ProductEntity, String> {
JsonParser parser = new JsonParser();
#Cacheable(value = CacheConfig.STORE_CACHE)
ProductEntity findByName(String name);
}
Cache Config
public final static String PRODUCTS_CACHE = "products";
#Bean
public Cache productsCache() {
return new GuavaCache(PRODUCTS_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build());
}
How do I detect invalid json in the unless="" parameter?
I figured out my own issue!
When I interrupted the api request to localhost/products and re-requested, I finally saw an error about not being able to fetch a onetomany mapping. I believe the error was lazy initialization error for a collection.
I solved this issue by adding #LazyCollection(LazyCollectionOption.FALSE) to my models where the #OneToMany and #ManyToOne mappings were decalared.
For example:
#Entity(name = "product")
#Table(name = "products", schema = "${DB_NAME}", catalog = "")
public class ProductEntity {
private Integer id;
private String name;
private List shipments = new ArrayList<>();
#Id
#Column(name = "id", nullable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 10)
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "shipmentID", targetEntity=ShipmentEntity.class)
#LazyCollection(LazyCollectionOption.FALSE)
public Collection<ShipmentEntity> getShipments() { return shipments; }
public void setShipments(Collection<ShipmentEntity> shipments) { this.shipments = shipments; }
}

Parent/Child relationships in spring-data-elastic-search

I'm using Spring-Data-Elastic-Search for searching/caching purposes.
I need to execute a query which uses child(TermCache) and parent(ConceptCache) properties
and return instances of child objects(this means i can't use nested objects).
i have the following structure:
#Document(indexName = "termweb" , type = "term")
public class TermCache {
#Id
private String id;
private String name;
private LanguageDTO language;
private String status;
private String definition;
#Field(type = FieldType.String, store = true)
#Parent(type = "concept")
private Long conceptId;
private String displayId;
private Map<Long, String> fields = new HashMap<>();
//todo think about storing it as a collection of nested objects
}
#Document( indexName = "termweb" , type = "concept")
public class ConceptCache implements ConceptDTO{
#Id
private String id;
private String displayId;
private Long dictionaryId;
private String dictionaryName;
private Map<Long, String> fields = new HashMap<>();
}
I need a hint on how to handle this type of tasks; should i use two separate queries or should i somehow fetch properties of a parent or maybe something else?
Agreed, We are lacking on documentation which we will be improving with upcoming release.
If you have any question about spring data elasticsearch stackoverflow probably is not best way to get answer(as we wont be notified for new thread), we have separate google group for question/queries https://groups.google.com/forum/#!forum/spring-data-elasticsearch-devs
Without having any idea about what exactly you are trying to achieve with above entities, i can give you an example of sample parent child entities as below
#Document(indexName = "parent-child", type = "parent-entity")
public class ParentEntity {
#Id
private String id;
#Field(type = FieldType.String, index = FieldIndex.analyzed, store = true)
private String name;
// setter/getter
public ParentEntity() {
}
public ParentEntity(String id, String name) {
this.id = id;
this.name = name;
}
}
#Document(indexName = "parent-child", type = "child-entity")
public class ChildEntity {
#Id
private String id;
#Field(type = FieldType.String, store = true)
#Parent(type = "parent-entity")
private String parentId;
#Field(type = FieldType.String, index = FieldIndex.analyzed, store = true)
private String name;
public ChildEntity() {
}
public ChildEntity(String id, String parentId, String name) {
this.id = id;
this.parentId = parentId;
this.name = name;
}
}
// indexing parent (you can use many other ways to index that includes using repositories)
ParentEntity parent1 = new ParentEntity("parent1", "First Parent");
IndexQuery parentIndex1 = new IndexQuery();
parentIndex1.setId(parent1.getId());
parentIndex1.setObject(parent1);
elasticsearchTemplate.index(parentIndex1);
ParentEntity parent2 = new ParentEntity("parent2", "Second Parent");
IndexQuery parentIndex2 = new IndexQuery();
parentIndex2.setId(parent2.getId());
parentIndex2.setObject(parent2);
elasticsearchTemplate.index(parentIndex2);
// indexing child
ChildEntity child1 = new ChildEntity("child1", parent1.getId(), "First");
IndexQuery childIndex1 = new IndexQuery();
childIndex1.setId(child1.getId());
childIndex1.setObject(child1);
childIndex1.setParentId(child1.getParentId());
elasticsearchTemplate.index(childIndex1);
ChildEntity child2 = new ChildEntity("child2", parent1.getId(), "Second");
IndexQuery childIndex2 = new IndexQuery();
childIndex2.setId(child2.getId());
childIndex2.setObject(child2);
childIndex2.setParentId(child2.getParentId());
elasticsearchTemplate.index(childIndex2);
// searching
there are several available option while searching on Parent/Child entities, that includes has children, has parent and top children queries.
QueryBuilder query = topChildrenQuery("child-entity", QueryBuilders.termQuery("name", child1name.toLowerCase()));
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(query).build();
List<ParentEntity> parents = elasticsearchTemplate.queryForList(searchQuery, ParentEntity.class);
Hope this small example will give you basic understanding how to use parent child. have a look at ParentChildTests for more.
If you still have more question please feel free to contact us.
You should simply use hasparent query of filter : http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-parent-filter.html#query-dsl-has-parent-filter
This will make a request on parent field and result in children documents of the matching parents documents. You can then use a filter on the returned child document :)

Resources