Hibernate n+1 issue with 3 entities - java

I have 3 entities. Category, Subcategory and Product
#Entity
#JsonInclude(value = JsonInclude.Include.NON_EMPTY)
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String picture;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
#JsonIgnore
private List<Subcategory> subcategories;
//getters and setters...
#JsonProperty("productCount")
private int getProductCount() {
//Counting products
int productCount = 0;
//!!!!!
//My problem starts here!
for (final Subcategory subcategory : subcategories) {
productCount += subcategory.getProducts().size();
}
return productCount;
}
}
#Entity
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Subcategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
#ManyToOne
#JoinColumn(name = "parent_category_id", nullable = false)
#JsonIgnore
private Category category;
#OneToMany(mappedBy = "subcategory", cascade = CascadeType.ALL)
private List<Product> products;
//getters and setters
}
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String unit;
private String icon;
#ManyToOne
#JoinColumn(name = "subcategory_id", nullable = false)
#JsonIgnore
private Subcategory subcategory;
//getters and setters
}
They are related to each other, but when I want to count the number of products in each category, about 120 queries are executed (depending on the number of subcategories and products)
I was able to reduce it to around 60 queries by adding #EntityGraph to my category repository:
#EntityGraph(type = EntityGraph.EntityGraphType.FETCH, attributePaths = {"subcategories"})
However 60 query is still too much. I can't add subcategories.products to this entity graph annotation because that causes org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
I can suppress this exception by changing the data type of products to Set from List but that creates a Cartesian product and makes the performance worse (it returns around 18,000 rows).
How can I fix this issue without creating a Cartesian product?

Related

Spring-boot: Join table with #OneToMany annotation return empty list

I recently started learning Spring Boot and I have a problem. I have two tables (Categories, subcategories) with a one to many relationship. I am having trouble creating this relationship. When requesting the output of all categories, the list of subcategories is empty, but in the database tables are created correctly.
Please tell me what can be wrong.
#Entity
#Table(name ="subcategory")
#Data
public class SubCategoryEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name;
#ManyToOne( targetEntity = CategoryEntity.class)
#JoinColumn(name = "category_id")
private CategoryEntity category;
}
#Entity
#Table(name = "category")
#Data
public class CategoryEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private String img;
#OneToMany(mappedBy = "category", fetch = FetchType.EAGER,
cascade = CascadeType.ALL, targetEntity = SubCategoryEntity.class)
private Set<SubCategoryEntity> subcategories;
}
#GetMapping("/categories")
public List<CategoryEntity> getCategories() {
return categoryRepository.findAll();
}
Attempt to get data:
Here I tried to get a list of categories and subcategories, but the list of subcategories is empty, although it is not.

JPA : How to handle mapping with a table that has relationship with two other tables?

I have three tables, table A (product), table B (invoice) and table C (invoices_info) which contains two columns referencing invoice_id and product_id. How can i insert a new entry (a new invoice) while inserting the products to the appropriate table and inserting the invoice info to its table also ?
Here are the entity classes :
Product
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "family_id")
private long familyId;
#Column(name = "product_name")
private String productName;
#Column(name = "product_category")
private String productCategory;
#Column(name = "product_quantity")
private int productQuantity;
//getters and setters
}
Invoice
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id")
private Long id;
#Column(name = "provider_id")
private Long providerId;
#Column(name = "total")
private int invoiceTotal;
#Column(name = "date")
private Date invoiceDate;
//getters and setters
}
InvoiceInfo
#Entity
#Table(name = "invoice_info")
public class InvoiceInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private long id;
#Column(name = "product_id")
private long productId;
#Column(name = "invoice_id")
private long invoiceId;
//getters and setters
}
InvoiceInfo should be join table, Define relationship on entities Product & Invoice using annotations #OneToMany, #ManyToOne based on your requirement.
You have to create relationships between your entities by using a set of annotations like: #ManyToOne, #OneToMany, #ManyToMany or #OneToOne... and other annotations if needed.
In your case I am not really sure you need an InvoiceInfo table, as the Invoice table can (or should) already contains the list of products.
I would suggest you the following relationships:
Product
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "family_id")
private long familyId;
#Column(name = "product_name")
private String productName;
#Column(name = "product_category")
private String productCategory;
#Column(name = "product_quantity")
private int productQuantity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "invoice_id", referencedColumnName = "id")
private Invoice invoice;
//getters and setters
}
Invoice
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id")
private Long id;
#Column(name = "provider_id")
private Long providerId;
#Column(name = "total")
private int invoiceTotal;
#Column(name = "date")
private Date invoiceDate;
#OneToMany(mappedBy = "product")
private List<Product> products;
//getters and setters
}
As your table InvoiceInfo no longer exists, you just have to insert you data in two table like this:
Invoice invoice = invoiceRepository.save(invoice);
Product product = new Product();
// Set the other properties
product.setInvoice(invoice);
productRepository.save(product);

How to retrieve data from parent-child tables using Spring Data JPA?

In my Spring Boot app, I use Hibernate and applied the necessary relations to the following entities properly.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false, length=50)
private String title;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}
#Entity
public class RecipeIngredient {
#EmbeddedId
private RecipeIngredientId recipeIngredientId = new RecipeIngredientId();
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("recipeId")
#JoinColumn(name = "recipe_id", referencedColumnName = "id")
private Recipe recipe;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("ingredientId")
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")
private Ingredient ingredient;
}
#Entity
public class Ingredient
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique=true, nullable=false, length=50)
#EqualsAndHashCode.Include
private String name;
#OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL)
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
}
Now I am trying to retrieve data by merging related entities. For example, when retrieving a Recipe, I also need to retrieve all Ingredients belonging to this Recipe.
As far as I know, I can use Projection and maybe it is better to only use Hibernate features and retrieve related table data via Java Stream. I have no idea how should I retrieve data via Hibernate.
Suppose that I just need an Optional<Recipe> that has List<Ingredient>. Then, I probably need a DTO class something like that:
#Data
public class ResponseDTO {
private Long id;
private String title;
List<RecipeIngredient> ingredients;
// getter, setter, constructor
}
So, how should I populate this DTO with the requested Recipe and corresponding Ingredient data (getting Ingredient names besides id values) using Java Stream?
Or if you suggest Projection way, I tried it but the data is multiplied by the ingredient count belonging to the searched recipe.
Update:
#Getter
#Setter
#NoArgsConstructor
public class ResponseDTO {
private Long id;
private String title;
List<IngredientDTO> ingredientDTOList;
public ResponseDTO(Recipe recipe) {
this.id = recipe.getId();
this.title = recipe.getTitle();
this.ingredientDTOList = recipe.getRecipeIngredients().stream()
.map(ri -> new IngredientDTO(ri.getIngredient().getName()))
.toList();
}
}
#Getter
#Setter
public class IngredientDTO {
private Long id;
private String name;
public IngredientDTO(String name) {
this.name = name;
}
}
First, in the ResponseDTO you will need you change the type of ingredients from List<RecipeIngredient> to List<Ingredient>.
To manually perform the mapping, you should use (to map from a suppose Recipe recipe to a RespondeDTO response):
ResponseDTO recipeToResponseDTO(Recipe recipe) {
ResponseDTO response = new ResponseDTO();
response.setId(recipe.getId());
response.setTitle(recipe.getTitle());
response.setIngredients(recipe.recipeIngredients.stream()
.map(RecipeIngredient::getIngredient()
.collect(Collectors.toList());
return response;
}
On the other hand, to model a n-n relation, I encourage you to use the approach proposed by E-Riz in the comment.

javax.persistence.EntityNotFoundException: Unable to find kg.library.spring.library_spring.entity.Author with id 10000001

I'm new to Spring and I'm probably making the dumbest mistake, but I can't solve this problem for more than 2 hours. According to the video tutorial, I did Pagination, I did it exactly like his, but he did not have relationships between entities. I think the error is in a one-to-one relationship between Author and Book entity. Can you please help?
I wanted to add pagination because I have more than a million records in my table, after adding pagination I got this error.
Book Entity:
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#Column(name = "publishing_year")
private Integer publishingYear;
#Column(name = "sell_cost")
private BigDecimal sellCost;
#Column(name = "rent_cost")
private BigDecimal rentCost;
#Column(name = "amount")
private Integer amount;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "author_id")
private Author author;
//getter and setters
Author Entity:
#Entity
#Table(name = "author")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "fullname")
private String fullname;
#Column(name = "birthday")
private Date birthday;
#OneToOne(mappedBy = "author")
private Book book;
//getters and setters
BookServiceImpl class:
#Service
public class BookServiceImpl implements BookService
{
#Autowired
private BookRepository bookRepository;
#Override
public Page<Book> findPaginated(int pageN, int pageSize,String sortField,String sortDirection) {
Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() :
Sort.by(sortField).descending();
Pageable pageable = PageRequest.of(pageN-1,pageSize,sort);
return bookRepository.findAll(pageable);
}
}
LibrarianController class:
#GetMapping("/books/{pageN}")
public String getAllBooks(#PathVariable (value = "pageN") int pageN,Model model,
#RequestParam("sortField") String sortField,
#RequestParam("sortDir") String sortDir){
int pageSize = 25;
Page<Book> page = bookService.findPaginated(pageN,pageSize,sortField,sortDir);
List<Book> books = page.getContent();
model.addAttribute("currentPage",pageN);
model.addAttribute("totalPages",page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("books",books);
model.addAttribute("sortField",sortField);
model.addAttribute("sortDir",sortDir);
model.addAttribute("reverseSortDir",sortDir.equals("asc") ? "desc" : "asc");
return "librarian/show-all-books";
}
It seems that you have a Book record that refers to an Author with id 10000001 that does not exit in Author table.
Try these changes. I hope that each book has only one author.
Book.java:
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "author_id")
private Author author;
Author.java:
#OneToMany(mappedBy = "author")
private List<Book> books=new ArrayList<>();

ManyToMany relationship entries are not being displayed in Spring REST

I have two classes: MenuItem.java and Tag.java. I am using the Hibernate implementation of JPA, and using PagingAndSortingRepository. After creating a bunch of dummy MenuItems and Tags, these are displayed in my local database and can be accessed with either: localhost:8080/api/menuItems or localhost:8080/api/tags. Additionally, as described by my annotations in the files below, there exists a ManyToMany relationship between these two objects, and a Menu_Items_Tags table with the appropriate entries is also created in my database without any issue.
The problem I run into is that every time I attempt to access a given menu item's list of tags via: localhost:8080/api/menuItems/1/tags for example, I receive a 500 error and additionally have a repeating error code like this:
: HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#726b0462<rs=HikariProxyResultSet#1877641821 wrapping Result set representing update count of 6>
Is there something I am doing wrong in either trying to request the Tags of a MenuItem or am I incorrectly setting up my #ManyToMany relationship?
MenuItem.java:
#Data
#Entity
public class MenuItem implements Serializable {
private static final long serialVersionUID = 1132661429342356177L;
public MenuItem() {
}
public MenuItem(String name, Double price, Integer inventory, String description, Set<Tag> tags) {
this.name = name;
this.price = price;
this.inventory = inventory;
this.description = description;
this.tags = tags;
}
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
#Column(name = "price", nullable = false)
private Double price;
#Column(name = "inventory", nullable = false)
private Integer inventory;
#Column(name = "description", nullable = false)
private String description;
#OneToMany(mappedBy = "menuItem")
private Set<Order> orders;
#ManyToMany
#JoinTable(
name="menu_item_tags",
joinColumns = #JoinColumn(name = "menu_item_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "id"))
#JsonManagedReference
private Set<Tag> tags;
}
Tag.java:
#Data
#Entity
public class Tag implements Serializable {
private static final long serialVersionUID = 1132661429342356176L;
public Tag() {
}
public Tag(String name) {
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", unique = true)
private String name;
// #JsonIgnore
#ManyToMany(mappedBy = "tags")
#JsonBackReference
private Set<MenuItem> menuItems;
}

Categories