How to show List in #OneToMany relation - java

I'm having an entity TechnicalStack and an entity Category, which an Category may have many TechnicalStack inside.
Here is my code:
#Entity
#Table(name = "technical_stack")
public class TechnicalStack implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
private String Question;
#Column(columnDefinition = "NVARCHAR(MAX)")
private String Answer;
private int Bookmark;
private int CheatSheet;
}
and Category
#Entity
#Table(name="categories")
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "category", fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#Fetch(FetchMode.SUBSELECT)
private Set<TechnicalStack> techList = new HashSet<>();
private String categoryName;
private String description;
}
I'm only using normal JPA function, findAll, save, etc.
So, when I save an category item, like:
{
"description" : "...",
"categoryName" : "name1update",
"techList": [{
"question" : "ABC",
"answer" : "XYZ"
}]
}
when I already have some records in 2 table TechStack and Category, I want to view category, i'm having this by calling findAll()
`[
{
"id": 1,
"techList": [],
"categoryName": "name1update",
"description": "..."
}
]`
but, when I check technical stack record, i have like this:
`{
"category": {
"id": 1,
"techList": [],
"categoryName": null,
"description": null
},
"id": 3,
"question": "question11",
"answer": "answer22",
"bookmark": 0,
"cheatSheet": 0
}`
Controller:
#PostMapping("/category/viewAll")
public List<Category> viewAllCategory() {
return repo.findAll();
}
TechnicalStack is working fine, but vice versa is not. The techList inside category shouldn't be empty.
How do i implement, so when the jpa call category by findAll(), I also got the techList?
I think i can do it by manually call the techList. That wouldn't be a problem. One query statement to call the category id, and one query to call the technical stack IN the list just called. But I don't think that is fully used JPA/Hibernate.
Thank you

One way you can do it is by using Spring Data JPA Projection. In your spring data JPA Repository, define the following method: 
public interface CategoryRepository extends JpaRepository<Category, Long> {
#Query("select c from Category c")
List<CategoryWithTechList> findAllWithTechList();
}
And here is how the CategoryWithTechList projection will look like:
import java.util.Set;
public interface CategoryWithTechList {
Long getId();
String getCategoryName();
String getDescription();
Set<TechnicalStackInfo> getTechList();
interface TechnicalStackInfo {
Long getID();
String getQuestion();
String getAnswer();
int getBookmark();
int getCheatSheet();
}
}
The get endpoint:
#GetMapping("/category/viewAll")
public List<CategoryWithTechList> viewAllCategory() {
    return categoryRepository.findAllWithTechList();
}
Returned result:
[
  {
    "id": 1,
    "techList": [
      {
        "id": 1,
        "question": "1",
        "bookmark": 1,
        "cheatSheet": 1,
        "answer": "1"
      },
      {
        "id": 2,
        "question": "1",
        "bookmark": 1,
        "cheatSheet": 1,
        "answer": "1"
      }
    ],
    "categoryName": "Cat1",
    "description": "1"
  },
  {
    "id": 2,
    "techList": [
      {
        "id": 4,
        "question": "2",
        "bookmark": 2,
        "cheatSheet": 2,
        "answer": "2"
      },
      {
        "id": 3,
        "question": "2",
        "bookmark": 2,
        "cheatSheet": 2,
        "answer": "2"
      }
    ],
    "categoryName": "Cat2",
    "description": "2"
  },
]
Btw, I also recond you keep all the field names in camelCase in your JPA Entities.

Related

Spring Data Mongodb Aggregation - Group by nested objects and build DTO

I have the following Employee data in MongoDB
{
"_id": {
"$oid": "625f09bb1a96bf42ff4c4006"
},
"employeeId": 1234,
"email": "jason#acme.com",
"firstName": "Jason",
"lastName": "Stuart",
"currentCTC": 1201117.61,
"department": {
"$ref": "department",
"$id": {
"$oid": "625f09bb1a96bf42ff4c4005"
}
}
}
{
"_id": {
"$oid": "625f09bb1a96bf42ff4c4006"
},
"employeeId": 1235,
"email": "jasons#acme.com",
"firstName": "Jasons",
"lastName": "Stuarts",
"currentCTC": 1201117.61,
"department": {
"$ref": "department",
"$id": {
"$oid": "625f09bb1a96bf42ff4c4005"
}
}
}
My Spring #Document looks like this:
// Employee.java
#Data
#Document
public class Employee {
#Id
private String id;
private Long employeeId;
private String email;
private String firstName;
private String middleName;
private String lastName;
private Gender gender;
private double currentCTC;
#DBRef
private Department department;
}
// Department.java
#Document
#Data
public class Department {
#Id
private String id;
private String name;
}
Now, my requirement is to find the sum of salaries Department-wise.. I need the data to be in the following way:
[
{
"department": {
"id": "625f09bb1a96bf42ff4c4006",
"name": "Engineering"
},
"cost": 31894773.01
},
{
"department": {
"id": "625f09bb1a96bf42ff4c4006",
"name": "Marketing"
},
"cost": 4552325.25
}
]
I created an aggregate function like this in Spring Data:
public List<DepartmentCost> getDepartmentCosting() {
GroupOperation groupByDepartment = group("department").sum("currentCTC").as("cost").first("$$ROOT").as("department");
Aggregation aggregation = Aggregation.newAggregation(groupByDepartment);
AggregationResults<DepartmentCost> results = mongoTemplate.aggregate(aggregation, "employee", DepartmentCost.class);
return results.getMappedResults();
}
And my expected DepartmentCost.java
#Data
#Document
public class DepartmentCost {
#DBRef
private Department department;
private double cost;
}
Now when I try this API out, I get the data correctly, but I do not get department name. It comes as null. I get a response like
[
{
"department": {
"id": "625f09bb1a96bf42ff4c4006",
"name": null,
},
"cost": 2241117.6100000003
},
{
"department": {
"id": "625f09bb1a96bf42ff4c400a",
"name": null,
},
"cost": 14774021.43
},
{
"department": {
"id": "625f09bc1a96bf42ff4c4013",
"name": null,
},
"cost": 14879633.97
}
]
How can I get the department details expanded in my model? Please help..
After a couple of attempts, I figured it out. All I had to do was this:
GroupOperation groupByDepartment = group("department").sum("currentCTC").as("cost").first("$department").as("department");
as opposed to:
GroupOperation groupByDepartment = group("department").sum("currentCTC").as("cost").first("$$ROOT").as("department");

Multiple grouping and sorting in Collection entity

I have entities as shown below -
#Entity
public class Certification {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String role; // can have values like "Architect", "Developer", "Business Practioner"
private int score;
private Date expiryDate
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "solution_id")
private Solution solution;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "region_id")
private Region region;
}
#Entity
public class Solution {
private String name; //can have values like "Analytics", "Campaign", "Marketo"
}
#Entity
public class Region{
private String name; //can have values like "EMEA", "APAC", "JAPAN" & "AMERICAS"
}
#Entity
public class Employee {
#OnetoMany(fetch = FetchType.LAZY)
private List<Certification> certifications
}
After pulling the employee certifications, I need to do multiple grouping and sorting and then pull the count of certification under a specific role. First I group by Solution name, then by Region, and then by Role. Each of these groups needs to be sorted alphabetically. For example, under solution group, "Analytics" should come first then "Campaign". Under region "APAC" should come first then "EMEA". And under the roles group, "Architecture" should come first then "Business Prationer".
{
"expertise": [
{
"solutionName": "Analytics",
"regions": [
{
"name": "APAC",
"roles": [
{
"name": "Architect",
"certifiedEmployees": 12
},
{
"name": "Business Practitioner",
"certifiedEmployees": 9
}
]
},
{
"name": "EMEA",
"roles": [
{
"name": "Architect",
"certifiedEmployees": 12
}
]
}
]
},
{
"solutionName": "Campaign",
"regions": [
{
"name": "APAC",
"roles": [
{
"name": "Architect",
"certifiedEmployees": 12
},
{
"name": "Business Practitioner",
"certifiedEmployees": 9
}
]
},
{
"name": "EMEA",
"roles": [
{
"name": "Architect",
"certifiedEmployees": 12
},
{
"name": "Back-end Developer",
"certifiedEmployees": 9
}
]
}
]
}
]
}
I tried using Collectors.groupingBy and Comparator.comparing but unable to achieve the desired results.
Found a better way to group a list using java streams and groupingBy. It's short and helps me avoid a lot of coding
credentials.stream().collect(groupingBy(c -> c.getSolution().getName(), groupingBy(c -> c.getRegion().getName())));

Sorting does not work correctly! Java SORT

I am writing MVC Rest online store. I use Spring Boot, Hibernate and PostgreSQL in my project. I have drinks and want to be able to sort them when I receive them. By name and price, everything is good, but I can't sort by popularity. For this I use Pagination and Sort. In the drink class, I use the ordersCount variable, which I marked with the #Formula annotation and wrote a sql-query. The request itself must take a drink from the cart by ID and return its quantity. Separately, I checked the request in the database, and it works correctly. But when I make a REST request, I get those drinks that are not in the basket at all. What could be the problem?
ENUM for Sorting type:
public enum SortingParams {
PRICE_INCREASE, PRICE_DECREASE, NAME_INCREASE, POP_DECREASE
}
I write method for Sorting:
/*
* #param sortingParams
* #param page
* #param pageSize
* #return PageRequest
*/
public PageRequest sortingWithParams(SortingParams sortingParams, int page, int pageSize) {
switch (sortingParams) {
case PRICE_INCREASE:
return PageRequest.of(page, pageSize, Sort.by("price").ascending());
case PRICE_DECREASE:
return PageRequest.of(page, pageSize, Sort.by("price").descending());
case NAME_INCREASE:
return PageRequest.of(page, pageSize, Sort.by("name").ascending());
case POP_DECREASE:
return PageRequest.of(page, pageSize, Sort.by("ordersCount").descending());
default:
return PageRequest.of(page, pageSize, Sort.unsorted());
}
}
Drink class:
#Data
#Entity
#NoArgsConstructor
#Table(name = "drink")
#Inheritance(strategy = InheritanceType.JOINED)
public class Drink {
#Id
#GeneratedValue
private Long id;
private String name;
private BigDecimal price;
#Formula("(select sum(c.count) from cart_items c where c.drink_id = id)")
private Long ordersCount;
private String about;
private int weight;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JoinColumn(name = "packaging_id")
private Packaging packaging;
#ManyToOne
#JoinColumn(name = "manufacturer_id")
private Manufacturer manufacturer;
#ManyToOne
#JoinColumn(name = "country_id")
private Country country;
This is how I then use this method:
public PageDTO<CoffeeDTO> findAll(int page, int pageSize, SortingParams sortingParams) {
final Page<Coffee> coffees = coffeeRepository
.findAll(drinkService.sortingWithParams(sortingParams, page, pageSize));
return new PageDTO<>(coffeeMapper.coffeeToCoffeesDTO(coffees));
}
RESULT:
http://localhost:8080/coffee/coffees?page=0&page_size=5&sortingParams=POP_DECREASE
{
"totalElements": 9,
"totalPages": 2,
"number": 0,
"size": 5,
"content": [
{
"id": 10,
"name": "Mokka",
"price": 590,
"about": "Mokka - это крепкий и ароматный кофе средней степени обжарки (3 по 5-тибальной шкале Paulig).",
"weight": 800,
"packaging": {
"id": 1,
"name": "Вакуумная упаковка"
},
"manufacturer": {
"id": 4,
"name": "Nescafe"
},
"country": {
"id": 7,
"name": "Франция"
},
"coffeeType": null,
"roasting": null
},
{
"id": 11,
"name": "Бурбон",
"price": 320,
"about": "Бурбон - свое название сорт получил в честь географической родины – французского острова Бурбон (ныне Реюньон). Именно здесь в начале XVIII века прижились первые саженцы кофейных деревьев, вывезенных из Йемена.",
"weight": 400,
"packaging": {
"id": 5,
"name": "Жестяная банка"
},
"manufacturer": {
"id": 5,
"name": "Jacobs"
},
"country": {
"id": 8,
"name": "Страна тотемов"
},
"coffeeType": null,
"roasting": null
},
{
"id": 13,
"name": "Жокей",
"price": 1199,
"about": "Жокей - насыщенный, крепкий, ароматный, с пряными нотками Жокей Для турки создан искусным сочетанием лучших сортов кофе из Центральной и Южной Америки, Африки и Индии. Особо мелкий помол идеален для приготовления кофе в турке.",
"weight": 850,
"packaging": {
"id": 4,
"name": "Стеклаянная банка"
},
"manufacturer": {
"id": 4,
"name": "Nescafe"
},
"country": {
"id": 2,
"name": "Индия"
},
"coffeeType": null,
"roasting": null
},
{
"id": 14,
"name": "Oro",
"price": 790,
"about": "Ароматный напиток Oro, созданный для требовательного кофемана с утонченным вкусом. Тщательно подобранный купаж создан из 100 % высокогорной арабики, выращенной на лучших плантациях Центральной Америки. Средняя обжарка и высокое качество сырья позволяют получить ярко выраженный красочный вкус и запоминающийся аромат, наполненный выразительной цветочной нотой с небольшой горчинкой.",
"weight": 750,
"packaging": {
"id": 1,
"name": "Vacuum"
},
"manufacturer": {
"id": 5,
"name": "Jacobs"
},
"country": {
"id": 3,
"name": "China"
},
"coffeeType": null,
"roasting": null
},
{
"id": 9,
"name": "Prince",
"price": 730,
"about": "srfrfrftring",
"weight": 0,
"packaging": {
"id": 1,
"name": "Vacuum"
},
"manufacturer": {
"id": 5,
"name": "Jacobs"
},
"country": {
"id": 2,
"name": "India"
},
"coffeeType": null,
"roasting": null
}
]
}
I remake #Formula query and it works:
#Formula("coalesce((select sum(c.count) from cart_items c" +
" where c.drink_id = id), 0)")
I add coalesce() from PostgreSQL.

Java Spring Deserializing Nested objects using RestTemplate

I am using Java Spring boot restTemplate and I am trying to deserialize the below JSON into their corresponding objects. However it is returning null.
Am I doing this the right way? Should I return a String response Entity and then convert?
{
"Events": [
{
"Id": 3584588,
"Url": "https://api.wildapricot.org/v2/accounts/257051/Events/3584588",
"EventType": "Regular",
"StartDate": "2019-10-07T07:00:00-05:00",
"EndDate": "2019-10-11T12:00:00-05:00",
"Location": "Renaissance Montgomery Hotel & Spa",
"RegistrationEnabled": false,
"RegistrationsLimit": null,
"PendingRegistrationsCount": 0,
"ConfirmedRegistrationsCount": 0,
"CheckedInAttendeesNumber": 0,
"InviteeStat": {
"NotResponded": 0,
"NotAttended": 0,
"Attended": 0,
"MaybeAttended": 0
},
"Tags": [
"event"
],
"AccessLevel": "AdminOnly",
"StartTimeSpecified": true,
"EndTimeSpecified": true,
"HasEnabledRegistrationTypes": false,
"Name": "2020 Montgomery IT Summit"
},
{
"Id": 3584591,
"Url": "https://api.wildapricot.org/v2/accounts/257051/Events/3584591",
"EventType": "Rsvp",
"StartDate": "2019-10-03T00:00:00-05:00",
"EndDate": "2019-10-31T00:00:00-05:00",
"Location": "Here",
"RegistrationEnabled": true,
"RegistrationsLimit": null,
"PendingRegistrationsCount": 0,
"ConfirmedRegistrationsCount": 0,
"CheckedInAttendeesNumber": 0,
"InviteeStat": {
"NotResponded": 0,
"NotAttended": 0,
"Attended": 0,
"MaybeAttended": 0
},
"Tags": [
"volunteer"
],
"AccessLevel": "Public",
"StartTimeSpecified": false,
"EndTimeSpecified": false,
"HasEnabledRegistrationTypes": true,
"Name": "Volunteer Event"
}
]
}
Here is my call:
ResponseEntity<WaEvents> response = restTemplate.exchange(uri,
HttpMethod.GET,
request,
WaEvents.class
);
return response.getBody().getEvents();
Here is my WaEvents Class:
#Data
public class WaEvents implements Serializable {
#JsonUnwrapped
#JsonProperty("Events")
private List<WaEvent> events;
}
Here is the WaEvent Class
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class WaEvent {
#JsonProperty("Id")
public Integer id;
#JsonProperty("Name")
public String name;
#JsonProperty("Location")
public String location;
#JsonProperty("StartDate")
public LocalDate startDate;
#JsonProperty("EndDate")
public LocalDate endDate;
#JsonProperty("IsEnabled")
public Boolean isEnabled;
#JsonProperty("Description")
public String description;
#JsonProperty("RegistrationLimit")
public Integer RegistrationLimit;
}
As explained here with an example :
public class Parent {
public int age;
public Name name;
}
public class Name {
public String first, last;
}
Without #JsonUnwrapped, the JSON is :
{
"age" : 18,
"name" : {
"first" : "Joey",
"last" : "Sixpack"
}
}
With #JsonUnwrapped, the JSON is :
{
"age" : 18,
"first" : "Joey",
"last" : "Sixpack"
}
So #JsonUnwrapped will flatten the properties and events won't exist anymore :
{
"Id": 3584588,
"Name": "2020 Montgomery IT Summit",
"Location": "Renaissance Montgomery Hotel & Spa",
"StartDate": "2019-10-07T07:00:00-05:00",
"EndDate": "2019-10-11T12:00:00-05:00",
...
}
Try to remove #JsonUnwrapped

Java elasticsearch spring-data set different index field name

I'm using elasticsearch java-api in combination with spring-data and have a problem with indexing a document.
I want a different name for an indexed field. That means not the same as in the java code:
Domainobject:
#Document(indexName = "testindex", type = "message")
public class MessageObject {
#Id
private String unid;
#FieldNameInElasticIndex(value = "javaMessage") // I want anything like that
private String message;
private String secondMessage;
private String thirdMessage;
...
getters & setters
...
}
Interface:
public interface MessageObjectRepository extends ElasticsearchRepository<MessageObject, Long> {
}
Service:
#Component
public class MessageService {
#Autowired
private MessageObjectRepository repository;
public void addRegistrationObject(MessageObject msg) {
repository.save(msg);
}
}
So....
is it possible to change the name so the index would look like:
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "testindex",
"_type": "message",
"_id": "00113B325ED357B7C1257E2D001D5B4B",
"_score": 1,
"_source": {
"unid": "00113B325ED357B7C1257E2D001D5B4B",
"javaMessage": "Hello", // <--- this is what I want (javaMessage)
"secondMessage": null,
"thirdMessage": "Third",
instead of
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "testindex",
"_type": "message",
"_id": "00113B325ED357B7C1257E2D001D5B4B",
"_score": 1,
"_source": {
"unid": "00113B325ED357B7C1257E2D001D5B4B",
"message": "Hello", // <--- this is NOT what I want (java name: message)
"secondMessage": null,
"thirdMessage": "Third",
?
The idea is simply to use the #JsonProperty annotation on the field and give the name you want to use during the JSON serialization:
#JsonProperty("javaMessage")
private String message;

Categories