Spring data jpa #ManyToOne orphanRemoval=true doesn't delete - java

Situation: Got Books and categories. Each category can have n books. Want to create new, update or delete books from category. If category is empty delete it from db. Currently insert, update, and delete books.
Problem: orphanRemoval=true doesn't delete category if it is empty.
#Entity
#Table(name = "book")
#Getter
#Setter
public class BookEntity implements DBEntity, Serializable {
#Id
private Integer id;
private String bookname;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name = "categoryid", referencedColumnName = "id")
private CategoryEntity categoryEntity;
public Integer getId() {
return this.id;
}
}
#Entity
#Table(name = "category")
#Getter
#Setter
public class CategoryEntity implements DBEntity, Serializable {
#Id
private Integer id;
private String categoryName;
#OneToMany(cascade = CascadeType.REMOVE, mappedBy = "categoryEntity", orphanRemoval = true)
private Set<BookEntity> bookEntitySet = new HashSet<BookEntity>(0);
}
#Transactional
public interface CategoryDAO extends CrudRepository<CategoryEntity, Integer> {
}
#Transactional
public interface BookDAO extends CrudRepository<BookEntity, Integer> {
}
Thanks for any help
p.s.
Tried:
didnt help:
Spring + JPA #OneToMany with orphanRemoval
looks stupid - deleting book from category (in case 2-n foreign key wont work):
https://hellokoding.com/jpa-one-to-many-relationship-mapping-example-with-spring-boot-maven-and-mysql/
Nothing mentioned:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/

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 #OneToMany and #ManyToOne: back reference is null though mappedBy is included

I have 4 Entities, that a related to each other with #OneToMany relationships.
When I try to save Order that contains OrderItem - Orderitem has no backreference.
In the code below only important fields are showed for brevity ( usual strings and primitives are omitted ). I decided to include Dish and User Entities also.
Order:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
private List < OrderItem > orderItems;
}
Dish:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class Dish {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "dish")
#ToString.Exclude
private List < OrderItem > orderItems;
}
OrderItem:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#ToString.Exclude
private Dish dish;
#ManyToOne(fetch = FetchType.LAZY)
private Order order;
private int quantity;
}
User:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List < Order > orders;
}
The problem happens when I try to save Order with Spring data JPA.
Let's print Order to see OrderItem before saving.
public Order saveOrder(Order order) {
System.out.println("SERVICE saving order " + order);
return orderRepository.save(order);
}
As you can see, orderItems backreference is null before saving ( I though spring data jpa should deal with setting it ).
SERVICE saving order Order(id=0,
orderItems=[OrderItem(id=0, quantity=2, order=null)])
Here is what I have in DB ( Order and OrderItem entities ).
In your OrderItem class, add annotation below:
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST}, fetch=FetchType.LAZY)
#JoinColumn(name="order_id", referencedColumnName="id", nullable = false)
Order order.
One more thing, I suggest you use SEQUENCE_GENERATOR, beacause IDENTITY means: I'll create the entity with a null ID and the database will generate one for me. I don't think Postgres even supports that, and even if it does, a sequence generator is a better, more efficient choice.
The best option that I found for this is doing something like:
order.getOrderItems().forEach(orderItem -> orderItem.setOrder(order));
Before your save() call. Even though order is not persisted at this point, it seems like Hibernate can resolve the relation and the back references will be set correctly.
If you do not want to bother setting the back reference in your business logic, you can add something like this to your entity:
class Order {
...
#PrePersist
public void prePersist() {
setMissingBackReferences();
}
private void setMissingBackReferences() {
orderItems.forEach(oderItem -> {
if (oderItem.getOrder() == null) {
oderItem.setOrder(this);
}
});
}
...
}

Java Spring Data App doesn't save sub-objects

I'm trying to build build service, which saves object with sub-objects, but getting error. In result object data fields saved, but sub-object not.
I have the next object. The main is Order and sub-object is Partner:
#Getter
#Setter
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private int orderId;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private Set<Partner> partners;
}
#Getter
#Setter
#Entity
#Table(name = "partners")
public class Partner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "partner_id")
private int id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
I use standard embedded method "save" from Spring Jpa Repository:
#Repository
public interface OrdersRepository extends JpaRepository<Order, Integer> {
}
and service, which call this Repository:
#Service
public class OrdersServiceImpl implements OrdersService {
#Autowired
private OrdersRepository repository;
#Override
public Order save(Order order) {
return repository.save(order);
}
}
Does someone have an idea why Partners are not saved?
Thanks a lot!
Because the relationship owner is Partner, so that you need to save the Order first. Or you can put cascade = CascadeType.PERSIST on private Order order;

JPA: Deletion with Foreign Keys in ManyToMany and ManyToOne relations?

I am using spring boot to model my backend and I have several foreign keys in my model structure.
I am unable to run delete operations in general and update operations when foreign keys are affected. I have tried several methods found on the internet but none seem to work.
Via SQL (SQL commands) can delete entities from book_queue_entry, copy and author_write but not author, book and book_store user due to foreign key constraints. Therefore I'd like to know what I am doing wrong in each of my models/relationships that don't work on SQL level.
Since it does not work on SQL level something with my modelling, at least as far as configuring on delete operations is fundamentally wrong. I can't point my finger at what.
Problem 1: Cannot delete a book: When a book is deleted, it should be deleted from the list of works of an author - in other words, it should be deleted from authors_write:
Author.java:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class Author extends BaseEntity implements Serializable {
//stuff
#ManyToMany(cascade = CascadeType.ALL)
#JsonIgnore
#JoinTable(
name = "authors_write",
joinColumns = #JoinColumn(name = "author_id"),
inverseJoinColumns = #JoinColumn(name = "book_id"))
Set<Book> works;
}
Book.java
#Entity
#Setter
#Getter
#NoArgsConstructor
public class Book extends BaseEntity implements Serializable {
//stuff
#ManyToMany(mappedBy = "works", cascade = CascadeType.ALL)
#EqualsAndHashCode.Exclude
Set<Author> authors;
#JsonIgnore
#OneToMany(mappedBy = "book")
private List<BookQueueEntry> bookQueue;
}
Problem 2: Cannot delete Authors. When an author is deleted then all entries in authors_write with the corresponding author_id should be deleted. The classes are the same as above. Cascading does not work in this case either.
Problem 3: Cannot delete Users. When users are deleted borrower_id in copy should be nulled (I've read this does not work in JPA at all) and book_queue_entries with corresponding user_id should be deleted
Copy.java
#Entity
#Setter
#Getter
#NoArgsConstructor
public class Copy extends BaseEntity implements Serializable {
//stuff
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn
#EqualsAndHashCode.Exclude
private Book reference;
#ManyToOne
#JoinColumn
#EqualsAndHashCode.Exclude
private BookStoreUser borrower;
}
User.java:
public class BookStoreUser extends BaseEntity implements Serializable {
//more stuff here
#JsonIgnore
#OneToMany(mappedBy = "borrower")
private Set<Copy> booksBorrowed;
}
BookQueueEntry.java
#Entity
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode
public class BookQueueEntry extends BaseEntity implements Serializable {
//more stuff here
#ManyToOne(cascade = CascadeType.ALL)
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "user_id", nullable = false)
#EqualsAndHashCode.Exclude
private BookStoreUser user;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "book_id", nullable = false)
#EqualsAndHashCode.Exclude
private Book book;
}
Once again, I believe something is wrong with how I've mapped the cascading operations. Something is wrong in a more general sense and I cant figure out what.
EDIT: I should note that #OnDelete(action = OnDeleteAction.CASCADE) did not work for me either.
EDIT 2: With a clear head, I've been able to fix Problems 1 and 2 using this: https://www.youtube.com/watch?v=vYNdjtf7iAQ
As noted in the comments, follow this video: https://www.youtube.com/watch?v=vYNdjtf7iAQ
My problems are solved but when deleting Users it throws a ConcurrentModificationException which is not really related to this problem.
My classes now look like this:
Book.java
public class Book extends BaseEntity implements Serializable {
#Column(unique = true)
private String isbn;
#NotBlank
private String title;
private Integer year;
private String imageUrl;
#ManyToMany(mappedBy = "works")
#EqualsAndHashCode.Exclude
Set<Author> authors;
private Boolean isAvailable;
private Integer version;
#Enumerated(EnumType.STRING)
private Language language;
#JsonIgnore
#OneToMany(mappedBy = "reference", cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Copy> copies;
#JsonIgnore
#OneToMany(mappedBy = "book", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<BookQueueEntry> bookQueue;
// see here why: https://www.youtube.com/watch?v=vYNdjtf7iAQ
public void addAuthor(Author author) {
this.authors.add(author);
author.getWorks().add(this);
}
public void removeAuthor(Author author) {
this.authors.remove(author);
author.getWorks().remove(this);
}
}
Author.java:
public class Author extends BaseEntity implements Serializable {
private String name;
#ManyToMany
#JsonIgnore
#JoinTable(
name = "authors_write",
joinColumns = #JoinColumn(name = "author_id"),
inverseJoinColumns = #JoinColumn(name = "book_id"))
Set<Book> works;
public void addBook(Book book) {
this.works.add(book);
book.addAuthor(this);
}
public void removeBook(Book book) {
this.works.remove(book);
book.removeAuthor(this);
}
}
Copy.java:
public class Copy extends BaseEntity implements Serializable {
#ManyToOne
#JoinColumn
#EqualsAndHashCode.Exclude
private Book reference;
private LocalDate borrowedAt;
private LocalDate dueDate;
#ManyToOne
#JoinColumn
#EqualsAndHashCode.Exclude
private BookStoreUser borrower;
#NotNull
#Enumerated(EnumType.STRING)
private Location location;
public void addBorrower(BookStoreUser user) {
user.addBorrowedCopy(this);
}
public void removeBorrower(BookStoreUser user) {
user.removeBorrowedCopy(this);
}
}
BookStoreUser.java
public class BookStoreUser extends BaseEntity implements Serializable {
#NotNull
#Column(unique = true)
private String email;
private String firstName;
#NotNull
private String lastName;
private boolean isAdmin;
#JsonIgnore
#OneToMany(mappedBy = "borrower", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Copy> booksBorrowed;
#NotNull
private String password;
public void addBorrowedCopy(Copy copy) {
this.booksBorrowed.add(copy);
copy.setBorrower(this);
copy.setBorrowedAt(LocalDate.now());
copy.setDueDate(LocalDate.now().plusMonths(1));
}
public void removeBorrowedCopy(Copy copy) {
this.booksBorrowed.remove(copy);
copy.setBorrower(null);
copy.setBorrowedAt(null);
copy.setDueDate(null);
}
}
And also make sure to make use of the utility methods in the service layer like this:
public Long deleteById(Long id) {
this.validator.checkIDNotNull(id);
Book book = em.find(Book.class, id);
for (Author author : book.getAuthors()) {
book.removeAuthor(author);
}
bookRepository.deleteById(id);
validator.checkEntityNotExists(id);
return id;
}
public Book create(Book book) {
this.checkISBNIsValid(book.getIsbn());
this.checkISBNExists(book.getIsbn());
this.checkEntityHasValues(book);
for (Author author : book.getAuthors()) {
author.addBook(book);
}
log.info("Book with ISBN {} created successfully", book.getId());
return bookRepository.save(book);
}
EDIT: ConcurrentModificationException: Deleting from a list/Set will throw this problem. This can be found on SO - to fix ConcurrentModificationExceptions you have to create a new HashSet with whatever is throwing that problem and iterate through that:
new HashSet<Author>(book.getAuthors())
.forEach(author -> book.removeAuthor(author));

Ebean ManyToMany within Join Table?

I'm using ebean with play framework and I have a problem mapping a many-many relationship between 2 join tables. Let's say we have 3 entities, Person, Company, and Profession. A Person can have many Professions. A Person can work for many Companies. And a Company can employ many Persons in many Professions.
Here's my code:
#Entity
public class Company extends Model {
#Id
public Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
public List<CompanyPerson> companyPersons;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
public List<CompanyProfession> companyProfessions;
...
}
#Entity
public class Profession extends Model {
#Id
public Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "profession")
public List<CompanyProfession> companyProfessions;
...
}
#Entity
public class Person extends Model {
#Id
public Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "person")
public List<CompanyPerson> companyPersons;
...
}
#Entity
public class CompanyPerson extends Model {
#EmbeddedId
public CompanyPersonId id = new CompanyPersonId();
#ManyToOne
#JoinColumn(name = "company_id")
private Company company;
#ManyToOne
#JoinColumn(name = "person_id")
private Person person;
#ManyToMany(mappedBy = "companyPersons")
public List<CompanyProfession> companyProfessions;
...
}
#Entity
public class CompanyProfession extends Model {
#EmbeddedId
public CompanyProfessionId id = new CompanyProfessionId();
#ManyToOne
#JoinColumn(name = "company_id")
private Company company;
#ManyToOne
#JoinColumn(name = "profession_id")
private Person person;
#ManyToMany(cascade = CascadeType.ALL)
public List<CompanyPerson> companyPersons;
...
}
I get the following error:
Error injecting constructor, javax.persistence.PersistenceException: Error with the Join on [models.CompanyProfession.companyPersons]. Could not find the local match for [null] Perhaps an error in a #JoinColumn
at play.db.ebean.EbeanDynamicEvolutions.(EbeanDynamicEvolutions.java:36)
I tried adding JoinColumn(name = "companyperson_id") but that just changed the error from [null] to [companyperson_id].
Not sure what to do here. Any help?

Categories