Spring Data Couchbase: How to rename fields from nested POJOs? - spring

Doc says that the #Field annotation can be used to rename a field in an entity. What about fields from nested POJOs that are technically not entities themselves? Consider the following hypothetical example.
#Document
public class Person {
#Id
private String ssn;
#Field
private String name;
#Field
private Address address;
static class Address {
// how to rename this field to line1?
private String street;
}
}

To answer your question specifically, you can use #Field("line1") for street in Address.
I have something like this in my project and it works fine (see descriptions)
Class 1
#Document
#JsonInclude(JsonInclude.Include.NON_NULL)
public class HotelInfo {
#Field("hotel_type") #JsonProperty("hotel_type")
public String hotelType;
#Field #JsonProperty("images")
public List<Image> images = new ArrayList<Image>();
#Field #JsonProperty("regions")
public List<String> regions = new ArrayList<String>();
#Field #JsonProperty("themes")
public List<String> themes = new ArrayList<String>();
#Field #JsonProperty("facilities")
public List<String> facilities;
#Field #JsonProperty("descriptions")
public Descriptions descriptions;
}
Class 2
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Descriptions {
#Field("hotel_information") #JsonProperty("hotel_information")
public String hotelInformation;
}

Related

I don't know why the double values are displayed in postman. Is the my code correct?

This is my Book class:
#Entity
#Table(name="book")
public class Book {
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#ManyToOne(targetEntity=Category.class,cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinColumn(name="CategoryId")
public Category category;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(length=10)
private int book_id;
#Column(length=128)
private String title;
#Column(length=64)
private String author;
#Column(length=200)
private String description;
#Column(length=10)
private int ISBN;
#Column(length=10)
private float price;
private Date published_Date;
#Lob
#Column
#Basic(fetch = FetchType.LAZY)
private byte[] icon;
//getter and setter
}
This is my Category class:
#Entity
#Table(name="category1")
public class Category {
#Id
#Column(length=12)
#GeneratedValue(strategy=GenerationType.AUTO)
public int CategoryId;
#Column(length=50)
public String CategoryName;
//#JsonBackReference
#OneToMany(mappedBy="category")
private List<Book> books = new ArrayList<Book>();
//getter and setter
}
The relationship between them is one to many.
This is my Category Service class
#Service
#Transactional
public class AdminServiceImpl implements AdminService {
#Autowired
private CategoryDao dao;
#Autowired
private BookDao dao1;
#Override
public List<Category> getAllCategory(){
return dao.findAll();
}
}
My Controller class
#RestController
#RequestMapping("/bookstore")
public class CategoryController {
#Autowired
private AdminService service;
#GetMapping("/GetAllCategory")
private ResponseEntity<List<Category>> getAllCategory() {
List<Category> catlist = service.getAllCategory();
return new ResponseEntity<List<Category>>(catlist, new HttpHeaders(), HttpStatus.OK);
}
}
My category table already has data.When i try to display them it is showing double values.
Displaying values using Postman
The Category table in the Database: Database table
Jackson's ObjectMapper uses the Java bean pattern and it expects the following
public class Foo {
public Object bar;
public Object getBar() {...}
public void setBar(Object bar) {...}
}
The getters and setters start with get and set, respectively, followed by the corresponding field name with its first letter capitalized.
Change
CategoryId to categoryId (first letter lowercase)
and
CategoryName to categoryName

Project embedded document fields after lookup operation

I want to do a join between Timesheet:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Document(collection = TIMESHEET_COLLECTION)
public class Timesheet {
#Id
private ObjectId id;
private ObjectId employeeId;
private LocalDate date;
private String occupationTitle;
private BigDecimal salary;
private List<TimesheetEntry> entries;
}
and Employee (as embedded document):
#Data
#AllArgsConstructor
#NoArgsConstructor
#Document(collection = Employee.EMPLOYEE_COL)
public class Employee {
#Id
private ObjectId id;
private String registry;
private String cpf;
private String firstName;
private String lastName;
private String nickname;
private String phone;
private LocalDate dateOfBirth;
private LocalDate admissionDate;
private EmployeeOccupation occupation;
private EmployeePaymentPreferences paymentPreferences;
private Map<String, String> equipmentPreferences;
private Boolean active;
}
So I have this aggregation query, with match, lookup, unwind and projection operations.
Aggregation aggregation = Aggregation.newAggregation(matchTimesheetFilter(timesheetFilter), lookupEmployee(), unwindEmployee(), projectEmployee());
There are lookup and unwind implementations. I'm unwinding because employee should be a single object, not an array.
private LookupOperation lookupEmployee(){
return LookupOperation.newLookup()
.from("employee")
.localField("employeeId")
.foreignField("_id")
.as("employee");
}
private UnwindOperation unwindEmployee(){
return Aggregation.unwind("employee");
}
It returns successfully a Timesheet document with a embedded Employee document. The point is: I don't want all data from employee. I only want a few fields.
So, I tried to exclude unwanted fields from employee, using my projection operation:
private ProjectionOperation projectEmployee() {
return Aggregation.project().andExclude("employee.nickname", "employee.firstName", "employee.fullName");
}
It didn't work. My embedded employee is still being returned with all fields. However I can successfully exclude fields from Timesheet, if I do something like this:
private ProjectionOperation projectEmployee() {
return Aggregation.project().andExclude("startDate", "endDate");
}
How can I project custom fields from a document embedded through a lookup operation?
i think you need to exclude "employee.nickname", "employee.firstName", "employee.fullName", instead of "nickname", "firstName", "fullName"
Try this:
private ProjectionOperation projectEmployee() {
return Aggregation.project().andExclude("employee.nickname", "employee.firstName", "employee.fullName");
}
i did it this way (not sure if it's right but it works):
private LookupOperation lookupEmployee(){
return LookupOperation.newLookup()
.from("employee")
.localField("employeeId")
.foreignField("_id")
.as("employeeLookup");
}
no unwind used
Aggregation.project().and("employeeLookup.firstName").as("employee.firstName")

Spring boot MongoDb complex query

I have been learning myself MongoDB implementation in Spring Boot.
However, I came into a problem with complex queries.
I cannot find any right solution for how to implement complex queries to MongoDB from Spring boot.
I am querying the database with MongoRepository interface implementation.
Let's say that I have three collections:
Person - 1 Person can have many Pets.
Pet - 1 Pet can have 1 PetToy and 1 Person who owns him.
PetToy - 1 PetToy can belong to 1 Pet.
POJO classes are bellow
What do I want to achieve?
I want to make a query, which would be returned me a Person, whose Pet has a Toy (PetToy) with the name "Teddy".
I could not have found a way how to do it. Furthermore, is it the best practice to even use such complex queries, or is it better to write more of little ones in MongoDB?
POJOs:
#Document
#Data
#ToString
public class Person {
#Id
private String id;
private String firstname;
private String lastname;
private int age;
#DBRef
private Pet pet;
}
#Document
#Data
#ToString
public class Pet {
#Id
private String id;
private String name;
private int age;
#DBRef
private List<PetToy> toys;
}
#Document
#Data
#ToString
public class PetToy {
#Id
private String id;
private String name;
}
I have tried to use MongoRepositories; however, I was not able to make the complex query.
How can one write such a query to a MongoDB from Spring Boot?
Thank you very much in advance.
If you can use embedded attributes, the class model should be:
#Document
#Data
#Builder
public class Person {
#Id
private String id;
private String firstName;
private String lastName;
private int age;
private List<Pet> pets;
}
#Data
#Builder
public class Pet {
private String name;
private int age;
private List<PetToy> toys;
}
#Data
#Builder
public class PetToy {
private String name;
}
The repository with the method that achieves what you want:
public interface PersonRepository extends MongoRepository<Person, String> {
List<Person> getByPetsToysName(String name);
}
The getByPetsToysName method basically navigate between Person's attributes Person->pets->toys->name. More info here.
An example
#Configuration
#EnableMongoRepositories
public class TestMongo implements CommandLineRunner {
private final PersonRepository repository;
public TestMongo(PersonRepository repository) {
this.repository = repository;
}
#Override
public void run(String... args) throws Exception {
repository.save(Person.builder()
.firstName("John")
.lastName("Doe")
.age(20)
.pets(Stream.of(Pet.builder()
.name("Ursa")
.age(1)
.toys(Stream.of(PetToy.builder()
.name("Teddy")
.build())
.collect(Collectors.toList()))
.build())
.collect(Collectors.toList()))
.build());
repository.save(Person.builder()
.firstName("Phillip")
.lastName("Larson")
.age(21)
.pets(Stream.of(Pet.builder()
.name("Bella")
.age(5)
.toys(Stream.of(PetToy.builder()
.name("Lolo")
.build())
.collect(Collectors.toList()))
.build())
.collect(Collectors.toList()))
.build());
List<Person> persons = repository.getByPetsToysName("Teddy");
System.out.println(persons.size());
List<Person> persons1 = repository.getByPetsToysName("Lolo");
System.out.println(persons1.size());
}
}
Logs:
find using query: { "pets.toys.name" : "Teddy" } fields: Document{{}} for class: class Person in collection: person
If you want more complex queries you can to take a look at the Spring Data MongoDB docs.

Ignoring Nested properties in Jackson OnDemand

I am working on a spring boot application with Hibernate as ORM and Jackson as JSON serialiser .
I have three model objects and CRUD operations for all three models.
Class Student{
private Teacher teacher; // Teacher of the student — to be fetched eagerly
+Getter/Setter
}
class Teacher {
private List<Subject> subject; // List of subjects associated to that user— to be fetched eagerly
+Getter/Setter
}
class Subject {
private long subjectId
//Other subject properties
+ Getter/Setter
}
Whenever I trigger a get request for student info I get the teacher info which is correct where as I also receive Subject info as well which is unnecessary for me. In the same time when I request for Teacher info, I need Subject info should be associated to that for sure. If I use #JsonBackReference for subject I am losing it all the time. I am not sure how to achieve this.
Thanks in advance for your help!!
You can also annotate like this
Class Student{
#JsonIgnoreProperties("subject")
private Teacher teacher; // Teacher of the student — to be fetched eagerly
}
You can use JSON Views
From the spring blog:
public class View {
interface Summary {}
}
public class User {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private String firstname;
#JsonView(View.Summary.class)
private String lastname;
private String email;
private String address;
private String postalCode;
private String city;
private String country;
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
private List<User> recipients;
private String body;
}
and in the controller
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#JsonView(View.Summary.class)
#RequestMapping("/")
public List<Message> getAllMessages() {
return messageService.getAll();
}
#RequestMapping("/{id}")
public Message getMessage(#PathVariable Long id) {
return messageService.get(id);
}
}
PS: No link to http://fasterxml.com/ as it's currently down.

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