How to update embedded objects in spring mongodb - spring

I am working a simple Spring MongoDB implementation.
My Entities look like this:
#Document(collection = "Book")
public class Book {
#Id
private ObjectId id; // Note: This should be BinInt, String or ObjectId
private List<Author> author;
#Indexed // Note: On which fields we write where clause
private String bookName;
private int price;
private int rating;
private Date publishedDate;
private Map<String, String> contentIndex = new HashMap<>();
private List<String> tags;
private Publisher publisher;
#PersistenceConstructor
public Book(ObjectId id, List<Author> author, String bookName, String firstName, String lastName, int price, int rating, Date publishedDate, Map<String, String> contentIndex, List<String> tags, Publisher publisher) {
}
#Data
public class Publisher {
#Id
private ObjectId id;
private String firstName;
private String lastName;
private int bookCount;
private Date dob;
private String email;
#PersistenceConstructor
public Publisher(ObjectId id, String firstName, String lastName, int bookCount, Date dob, String email) {
}
#Data
public class Author {
#Id
private ObjectId id;
private String firstName;
private String lastName;
private String email;
private int bookCount;
private Date dob;
#PersistenceConstructor
public Author(ObjectId id, String firstName, String lastName, int bookCount, Date dob)
}
I am able to do all basic CRUD operations on BooK object by just extending the MongoRepositoy.
Now I am trying to do the same CRUD operations on embedded objects Publisher and array of Authors in the BooK object.
I am trying to do this but have not found a solution. I went through the Spring MongoDB documentation about Query and Update and several Stack Overflow questions. But not able to do it till now.
I am wanting to do the following things:
how to update field values in book.publisher object.
how to update book.authors[1] fields.
how to add new author to book.authors list object.

How to update field values in book.publisher object.
public void updatePublisher(BigInteger bookId, Publisher publisher) {
Query query = new Query();
query.addCriteria(Criteria.where("id").is(bookId));
Book book;
book = mongoTemplate.findOne
(query, Book.class);
Update update = new Update();
if (publisher.getFirstName() != null)
update.set("publisher.firstName", publisher.getFirstName());
if (publisher.getLastName() != null)
update.set("publisher.lastName", publisher.getLastName());
if (publisher.getBookCount() != null)
update.set("publisher.bookCount", publisher.getBookCount());
if (publisher.getEmail() != null)
update.set("publisher.email", publisher.getEmail());
mongoTemplate.findAndModify(query, update, Book.class);
}
How to update book.authors[1] fields.
public void updateAuthor(BigInteger bookId, Author author) {
Query query = new Query();
query.addCriteria(Criteria.where("id").is(bookId));
Book book;
book = mongoTemplate.findOne(query, Book.class);
Author old = null;
if (book != null) {
List<Author> al = book.getAuthors().getAuthors();
int index = -1;
for (int i = 0; i < al.size() && index == -1; i++) {
if (author.getId().equals(al.get(i).getId()))
index = i;
}
if (index > -1) {
old = book.getAuthors().getAuthors().get(index);
book.getAuthors().getAuthors().remove(index);
book.getAuthors().getAuthors().add(author); // Note: If you need merge of new object to old then , copy field by field from new object to old object.
mongoTemplate.save(book);
}
}
}
How to add new author to book.authors list object.
public Author addAuthor(BigInteger bookId, Author author) {
Query query = new Query();
query.addCriteria(Criteria.where("id").is(bookId));
Book book;
book = mongoTemplate.findOne
(query, Book.class);
if (book != null) {
book.getAuthors().getAuthors().add(author);
mongoTemplate.save(book);
}
return author;
}
I implemented and working well. However, I am open to optimising this if possible.

Related

JPA: How can I read particular fields of an Entity?

I use Spring JPA ( Hibernate ) and have bunch of entities which are mapped onto tables.
When I use an entity to write I need many fields in it (see an example below). But when I read, I wanna sometimes read only particular fields like first/last name. How can I perform it using Spring data JPA ? ( because due to CrudRepository nature it returns the whole entity)
#Entity
#Table(name="PERSON")
#AttributeOverride(name = "id", column = #Column(name = "ID_PERSON"))
public class Person extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name="LAST_NAME", length = 100, nullable = false)
private String lastName;
#Column(name="FIRST_NAME", length = 50, nullable = false)
private String firstName;
#Column(name="MIDDLE_NAME", length = 50)
private String middleName;
#Column(name="BIRTHDAY", nullable = false)
#Temporal(value = TemporalType.DATE)
private Date birthday;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "ID_SEX")
private Sex sex;
public Person() {
super();
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
There are various possibilities.
With Spring Data JPA you can use projection (that's the name when you only select certain fields/columns of an entity/table).
You can return List of Object[] or a DTO or an Interface.
For example with interface it looks like this:
interface NamesOnly {
String getFirstname();
String getLastname();
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
As you can see the return value most not be of type Person.
Please check out the documentation:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
I was faced with a similar issue and I resorted to this:
Let's say you have your entity FooEntity related to repository FooRepository
To only get certain fields, let's say firstName and lastName using key I had to create a custom query in the FooRepository
In this manner
#Query("select new FooEntity(f.firstName, f.lastName) from FooEntity f where f.key = :key")
Optional<FooEntity> findCustomByKey(#Param("key") BigInteger key);
You also have to ensure that the FooEntity has the constructor accepting the values that you only want to be set or returned in this manner:
public FooEntity(String firstName, String lastName){
// Ensure the constructor is not called with null values
notNull(firstName, "Method called with null parameter (firstName)");
notNull(lastName, "Method called with null parameter (lastName)");
this.firstName = firstName;
this.lastName = lastName;
}
Please the full code below:
public class FooEntity implements Serializable {
#Id
#Column(name = "key")
private BigInteger key;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "birth_date")
private Date birthDate;
#Column(name = "hash")
private String hash;
public FooEntity(String firstName, String lastName){
// Ensure the constructor is not called with null values
notNull(firstName, "Method called with null parameter (firstName)");
notNull(lastName, "Method called with null parameter (lastName)");
this.firstName = firstName;
this.lastName = lastName;
}
// Getters and Setters
}
public interface FooRepository extends JpaRepository<FooEntity, BigInteger>{
#Query("select new FooEntity(f.firstName, f.lastName) from FooEntity f where f.key = :key")
Optional<FooEntity> findCustomById(#Param("key") BigInteger key); // This one only returns two set fields firstName and LastName and the rest as nulls
Optional<FooEntity> findById(BigInteger key) // This one returns all the fields
}

Elasticsearch with spring boot when using index query return nullPointerExciption

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()

SpringBoot concatenate search parameters browser url

I am starting working with Spring Boot. My aim is to make a limited search retrieving data from a database. I want to add multiple parameters in the query of the url.
So far I was able using the seek: http://localhost:8080/wsr/search/, to get a full search of the data in the database. But what I want is delimit the search under several conditions adding parameters in the url in the browser as for instance:
http://localhost:8080/data/search/person?name=Will&address=Highstreet&country=UK
http://localhost:8080/data/search/person?name=Will&name=Angie
http://localhost:8080/data/search/person?name=Will&name=Angie&country=UK
The problem I found is that I can't find the way to work with more than one condition. The only thing I got to make it work, is:
http://localhost:8080/data/search/person?name=Will
I surfed the web but no results for this exact problem, too much information but impossible to find this.
The code I have is:
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "country")
private String country;
public Value() {
}
public Value(int id, String name, String address, String country) {
this.id = id;
this.name = name;
this.address = address;
this.country = country;
}
//all getters and setters
}
public class Implementation {
#Autowired
private DataBase dataBase;
public List<Value> findById(#PathVariable final int id) {
return dataBase.findById(id);
}
public List<Value> findByName(#PathVariable final String name) {
return dataBase.findByName(name);
}
public List<Value> findByAddress(#PathVariable final String address) {
return dataBase.findByAddress(address);
}
public List<Value> findByCountry(#PathVariable final String country) {
return dataBase.findByCountry(country);
}
}
//#Component
#RepositoryRestResource(collectionResourceRel = "person", path = "data")
public interface DataBase extends JpaRepository<Value, Integer>{
public List<Value> findAll();
#RestResource(path = "ids", rel = "findById")
public List<Value> findById(#Param("id") int id) throws ServiceException;
#RestResource(path = "name", rel = "findByName")
public List<Value> findByName(#Param("name") String name) throws ServiceException;
#RestResource(path = "address", rel = "findByAddress")
public List<Value> findByAddress(#Param("address") String address) throws ServiceException;
#RestResource(path = "country", rel = "findByCountry")
public List<Value> findByCountry(#Param("country") String country) throws ServiceException;
}
Hope you can help me putting me in the correct way of what should do or is wrong. If possible some code will also be highly appreciated.
Best regards
You can use #RequestParam("nameParameter")annotation to map all the parameters you want. Let's say you have url like :
http://localhost:8080/data/search/person?name=Will&country=UK
then you can have an api like:
...
#RequestMapping(value = "/person")
public String api(#RequestParam("name") String name, #RequestParam("country") String country)
...

how to use spring data neo4j search for fulltext

I am learning spring data neo4j and spring .I want to search fulltext,for example I have three Movies (sky,sky1,sky2),when i search "sky",it return sky,sky1,sky2.firt i use repository below
package com.oberon.fm.repository;
#Repository
public interface MovieRepository extends GraphRepository<Movie> {
Movie findById(String id);
Page<Movie> findByTitleLike(String title, Pageable page);
}
My controller below
#RequestMapping(value = "/movies", method = RequestMethod.GET, headers = "Accept=text/html")
public String findMovies(Model model, #RequestParam("q") String query) {
log.debug("1");
if (query != null && !query.isEmpty()) {
Page<Movie> movies =movieRepository.findByTitleLike(query, new PageRequest(0, 20));
model.addAttribute("movies", movies.getContent());
} else {
model.addAttribute("movies", Collections.emptyList());
}
model.addAttribute("query", query);
addUser(model);
return "/movies/list";
}
these does not work well,i think somewhere might be wrong,but i dont know,if you have any idea,tell me thanks! by the way,it throw exception java.lang.NullPointerException.
My Movie entity
#NodeEntity
public class Movie {
#GraphId
Long nodeId;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "id")
String id;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "search")
String title;
String description;
#RelatedTo(type = "DIRECTED", direction = INCOMING)
Set<Director> directors;
#RelatedTo(type = "ACTS_IN", direction = INCOMING)
Set<Actor> actors;
#RelatedToVia(type = "ACTS_IN", direction = INCOMING)
Iterable<Role> roles;
#RelatedToVia(type = "RATED", direction = INCOMING)
#Fetch
Iterable<Rating> ratings;
private String language;
private String imdbId;
private String tagline;
private Date releaseDate;
private Integer runtime;
private String homepage;
private String trailer;
private String genre;
private String studio;
private Integer version;
private Date lastModified;
private String imageUrl;

How to search nested object by using Spring Data Solr?

I have two such Java object:
public class PSubject
{
#Column
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
#org.apache.solr.client.solrj.beans.Field("name")
private String name;
#Column
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
#org.apache.solr.client.solrj.beans.Field("type")
private String type;
#Column
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
#org.apache.solr.client.solrj.beans.Field("uri")
private String uri;
#OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#IndexedEmbedded
#org.apache.solr.client.solrj.beans.Field("attributes")
private Set<PAttribute> attributes = new HashSet<PAttribute>();
.....
}
#Entity
#Indexed
#Table(name="PAttribute")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class PAttribute extends PEntity
{
private static final long serialVersionUID = 1L;
#Column
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.YES)
#org.apache.solr.client.solrj.beans.Field("attr_name")
private String name;
#Column
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.YES)
#org.apache.solr.client.solrj.beans.Field("attr_value")
private String value;
.....
}
And my Spring Data Solr query interface:
public interface DerivedSubjectRepository extends SolrCrudRepository<PSubject, String> {
Page<PSubject> findByName(String name, Pageable page);
List<PSubject> findByNameStartingWith(String name);
Page<PSubject> findBy(Pageable page);
#Query("name:*?0* or description:*?0* or type:*?0* or mac_address:*?0* or uri:*?0* or attributes:*?0*")
Page<PSubject> find(String keyword,Pageable page);
#Query("name:*?0* or description:*?0* or type:*?0* or mac_address:*?0* or uri:*?0* or attributes:*?0*")
List<PSubject> find(String keyword);
}
I can search any by name, description, type and mac_address, but can't search any result by attribute.
Update:
For example,when user search "ipod", it's probably means the type of subject or name of subject, or the name of attribute or the value of attribute. And I want get all the matched subject in one request. I know I can search the attribute object in a separate query. But that makes the code in the backend complex.
So, how can I search this nested object?
Update:
I flattened my data:
#Transient
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
#org.apache.solr.client.solrj.beans.Field("attrs")
private String attrs;
public String getAttrs() {
return attrs;
}
public void setAttrs(Set<PAttribute> attributes) {
StringBuffer attrs = new StringBuffer();
if(attributes==null) {
attributes = this.getAttributes();
}
for(PAttribute attr:attributes){
attrs.append(attr.getName()+" " + attr.getValue()).append(" ");
}
this.attrs =attrs.toString();
}
The issue is resolved.
IIRC it is not possible to store nested data structures in solr - it depends how you flatten your data to fit into an eg. multivalue field - a little hard not knowing your schema.
see: http://lucene.472066.n3.nabble.com/Possible-to-have-Solr-documents-with-deeply-nested-data-structures-i-e-hashes-within-hashes-td4004285.html
How does the data look like in you index, and did you have a look at the http request sent by spring-data-solr?

Resources