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?
Related
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));
Here is the code:
#Entity
#Table(name = "students")
public class BotUser {
...
#Id
#Column(name = "id", updatable = false)
private int id;
#OneToOne
private Equipment equipment;
...
}
#Entity
#Table(name = "students")
public class Equipment {
...
#Id
#Column(name = "id")
private int id;
...
}
When Hibernate is fetching equipment data from PostrgeSQL table, he requests "equipment_id" field, not "id". How to solve this problem?
I must use #JoinColumn annotation. And to be sure I added CascadeType.ALL
#Entity
#Table(name = "students")
public class BotUser {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id")
private Equipment equipment;
...
}
The same problem: AnnotationException Referenced property not a (One|Many)ToOne
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/
I've done the necessary changes to my models outlined here. However, I don't know what to put on my join table entity.
Note that my join table has a surrogate key , and two extra columns (date and varchar).
What I've got so far is:
User.java
#Entity
#Table (name = "tbl_bo_gui_user")
#DynamicInsert
#DynamicUpdate
public class User implements Serializable {
private String id;
private String ntName;
private String email;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
Group.java
#Entity
#Table (name = "tbl_bo_gui_group")
#DynamicInsert
#DynamicUpdate
public class Group implements Serializable {
private String id;
private String groupName;
private String groupDesc;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.group", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
The problem is that I don't know what to put on my join table entity. Here it is.
GroupUser.java
#Entity
#Table (name = "tbl_bo_gui_group_user")
#DynamicInsert
#DynamicUpdate
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "pk.group",
joinColumns = #JoinColumn(name = "id")) })
public class GroupUser implements Serializable {
private String id;
private User userId;
private Group groupId;
private Date dateCreated;
private String createdBy;
// constructors and getters and setters for each property
// What now? ? No idea
}
user to group would be a Many-To-Many relation. Now, you are splitting that up into Two One-To-Many Relations. Therefore your Mapping Entity simple needs to complete the Many-To-Many relation, by using Many-To-One:
public class GroupUser implements Serializable {
private String id;
#ManyToOne
private User userId;
#ManyToOne
private Group groupId;
private Date dateCreated;
private String createdBy;
}
See also this example: Mapping many-to-many association table with extra column(s) (The Answer with 38 upvotes)
I have four class; UserGroup, UserAccount, Role, UserGroupRoleRelation and my db is IBM DB2
#Entity
#Table(name = "USER_GROUP")
public class UserGroup implements Serializable {
#Id
#Column(name = "USER_GROUP_ID")
#GeneratedValue
private Long id;
......
..
#OneToMany(mappedBy = "userGroup", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserGroupRoleRelation> userAccountsRole = new ArrayList<UserGroupRoleRelation>();
}
#Entity
#Table(name = "ROLE")
public class Role implements Serializable {
#Id
#Column(name = "ROLE_ID")
#GeneratedValue
private Long id;
......
#OneToMany(mappedBy = "role")
private List<UserGroupRoleRelation> userAccountInGroup = new ArrayList<UserGroupRoleRelation>();
}
#Entity
#Table(name = "USER_GROUP_ROLE_LINE", uniqueConstraints = #UniqueConstraint(columnNames = { "ROLE_ID", "USER_GROUP_ID" }))
public class UserGroupRoleRelation {
#Id
#GeneratedValue
#Column(name = "RELATION_ID")
private Long relationId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "USER_ACCOUNT_USER_GROUP_ROLE_LINE", joinColumns = { #JoinColumn(name = "RELATION_ID") }, inverseJoinColumns = { #JoinColumn(name = "USER_ID") }, uniqueConstraints = #UniqueConstraint(columnNames = { "USER_ID", "RELATION_ID" }))
private List<UserAccount> userAccountList = new ArrayList<UserAccount>();
#ManyToOne
#JoinColumn(name = "USER_GROUP_ID")
private UserGroup userGroup;
#ManyToOne
#JoinColumn(name = "ROLE_ID")
private Role role;
}
#Entity
#Table(name = "USER_ACCOUNT")
public class UserAccount implements Serializable {
#Id
#Column(name = "USER_ID")
#GeneratedValue
private Long id;
.....
#ManyToMany(mappedBy = "userAccountList", cascade = CascadeType.ALL)
private List<UserGroupRoleRelation> rolesInGroup = new ArrayList<UserGroupRoleRelation>();
}
I wanna find usergroups of a useraccount and i prepared a method with criteria. its like;
#Override
#Transactional
public List<UserGroup> findUserGroupOf(UserAccount userAccount) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.createAlias("userAccountsRole", "userAccountsRole");
criteria.add(Restrictions.eq("userAccountsRole.userAccountList", userAccount));
return criteria.list();
}
But when i try to get result of that method, DB2 gives to me DB2 SQL Error: SQLCODE=-313, SQLSTATE=07004, SQLERRMC=null, DRIVER=3.63.75
Probably its about creating alias on many to many relation. I dont know what should i do to create alias on many to many. How can I get result of that function?
Thank
#Override
#Transactional
public List<UserGroup> findUserGroupOf(UserAccount userAccount) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.createAlias("userAccountsRole", "userAccountsRole");
criteria.createAlias("userAccountsRole.userAccountList", "userAccountList");
criteria.add(Restrictions.eq("userAccountList.id", userAccount.getId()));
return criteria.list();
}
It works for me. I mean criteria on "id". But I don't understand why I cant check equality on object instead of id when there is ManyToMany list
It is not of creating alias. You are passing an object to hibernate on which it can not make any criteria. You need to create bidirectional mapping for that.Or else if you your requirement is just to fetch the the list of UserAccountList of particular UserGroup class you can follow the below code.
#Override
#Transactional
public List<UserGroup> findUserGroupOf(long userGroupId) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.add(Restrictions.eq("id",userGroupId));
criteria.createAlias("userAccountsRole", "uar");
criteria.setFetchMode("uar.userAccountList",FetchMode.JOIN);
return criteria.list();
}