JPA Self Referencing Entities - java

I have a Table called User like this:
public class User
{
private Long userId;
private String username;
//Other fields;
}
Now Lets say the User can have other User friends. So how do I create this relationship in JPA.
Meanwhile I was doing this in the User Table in Database:
USER_ID
USERNAME
USER_FRIENDS
And in the User Entity like this:
public class User
{
private Long userId;
private String username;
//Other fields;
#OneToMany(cascade = CascadeType.ALL)
#Column(name = "USER_FRIENDS")
private List<UserEntity> friends;
}
This doesn't work. So, how do I achieve this in JPA? Thanks in advance!

After doing research and help from #Florian Schaetz, I found the solution to the self referencing entities.
The need was User can have other User friends and that User can be friend to other Users. So, what I did was like this:
public class User
{
private Long userId;
private String username;
//Other fields;
#JoinTable(name = "USER_FRIENDS", joinColumns = {
#JoinColumn(name = "ADDING_USER", referencedColumnName = "USER_ID", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "ADDED_USER", referencedColumnName = "USER_ID", nullable = false)})
#ManyToMany
private Collection<User> friends;
#ManyToMany(mappedBy = "friends")
private Collection<User> addUser;
}
Hope This will help other people! Thanks again friends!

Related

Mutiple #ManyToMany on a single Entity causes Cross Join

I have a User Entity.
#Entity
#Table(name = "t_login_user")
public class User extends Auditable<Long> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "user_uid")
private String userUid;
#Column(name = "user_name")
private String userName;
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name="primary_role_id")
private Role primaryRole;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "t_login_user_role_map", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles;
}
My Role Entity is
#Entity
#Table(name = "t_login_role")
public class Role extends Auditable<Long> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="role_id")
private Long roleId;
#Column(name="role_code")
private String roleCode;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "t_login_role_priv_map", joinColumns = #JoinColumn(name = "role_id"), inverseJoinColumns = #JoinColumn(name = "priv_id"))
private List<Privilege> privileges;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "t_login_role_menu_map", joinColumns = #JoinColumn(name = "role_id"), inverseJoinColumns = #JoinColumn(name = "menu_id"))
private List<Menu> menus;
}
My Menu Entity is
#Entity
#Table(name = "t_login_menu")
public class Menu extends Auditable<Long> implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="menu_text")
private String menuText;
#Column(name="menu_icon")
private String menuIcon;
#Column(name="menu_url")
private String menuURL;
}
As you can see my Role has multiple Privileges and Multiple Menus. The problem I face is that when I have a code like
LoggedinUser liu = (LoggedinUser)authentication.getPrincipal();
List<Menu> menus = liu.getPrimaryRole().getMenus();
If I have two privileges say READ_DATA and WRITE_DATA
And three Menus 1. HOME 2.USER 3.PROFILE
my menus variable has a value of [HOME,HOME,USER, USER, PROFILE, PROFILE] (i.e. 2 privileges * 3 Roles)
I suspect that this is due to my Role entity having more than one #ManyToMany annotations.
I tried to search online and Stackoverflow but no results.
Anybody face this issue? Am i doing something fundamentally wrong?
Okay. I understand where the cross join happens. Since both the ManyToMany are being EAGER loaded, this is where the Cross Join Happens.
If I change to LAZY Load then the issue disappears. Slight performance hit on LAZY load, but thats fine since I do it only once and store the result in the session.

Hibernate ManyToMany HQL issue

My MySql tables:
db tables
And My Two Entity Classes are
#Entity
public class Tweet {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="tweet_id")
private int tweetId;
private String message;
private Date created;
#ManyToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinColumn(name="user_id")
private Person person;
...
}
#Entity
public class Person {
#Id
#Column(name = "user_id")
private String userId;
private String password;
private String email;
private String fullName;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "following", joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "following_id") })
private List<Person> following = new ArrayList<Person>();
...
}
Now I want to display messages of user praveen, and also all the messages mapped to praveen user in the following table. That means praveen user have 2 messages and his following users have 2 messages . Total 4 messages should be displayed. I really don't know how to retrieve this info using any technique in Hibernate. Please help me

Ignore an Entity fields for a Spring REST call

I have two Entity classes "Teacher & Class" where there is a #OneToMany relationship between them. The first one has a rest interface at /teachers/{id} and the second one has a rest interface at /classes/{id}. When a user sends a GET request to the Teacher interface, he should receive all of the Teacher and Class fields. But, when the user sends a GET request to the Class interface, I would like him to receive all of the Class fields and only a part of the Teacher fields "firstName, lastName"
Here are the entities code:
#Entity
#DiscriminatorValue("Th")
public class Teacher{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
private String phone;
#Column(unique = true)
#Email
private String email;
private String password;
#OneToMany(mappedBy = "teacher", fetch = FetchType.EAGER)
private List<Class> classes;
#OneToMany(mappedBy = "teacher", fetch = FetchType.EAGER)
private List<Note> notes;
protected Teacher() {
}
}
#Entity
public class Class {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToOne()
#JoinColumn(name = "subject_id")
private Subject subject;
#Enumerated(EnumType.STRING)
private Grade grade;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teacher_id")
private Teacher teacher;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "class_student", joinColumns = { #JoinColumn(name = "class_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "student_id", referencedColumnName = "id") })
private List<Student> students;
protected Class() {
}
}
//getters and setters
Your request sounds mutualy exclusive, don't load Teacher object but do load it for it's ID.
You are better off then making the teacher object lazy load and then initialise it once class is loaded.
FYI
#JsonIgnore prevents loading of objects when RESTfull calls are made.

Hibernate exception during deletion of #ManyToMany association

I have a confusing problem which I haven't figured out how to solve. if you can offer a suggestion of how I can fix my problem I would be grateful.
So I have the following entity relationship model here.
The mapping of User.class is:
#Entity
#Table(name = "CRM_USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID")
private Long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "BIRTHDATE")
private Date birthDate;
#Column(name = "EMAIL")
private String email;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private UserAdditionalInfo additionalInfo;
#ManyToOne
#JoinColumn(name = "TEAM_FK")
private Team team;
#ManyToOne
#JoinColumn(name = "JOB_FK")
private Job job;
#ManyToOne
#JoinColumn(name = "ORGANIZATION_FK", nullable = false)
private Organization organization;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Security security;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "INFO_FILE_FK")
private InfoFile profilePicture;
#ManyToOne
#JoinColumn(name = "COUNTRY_FK")
private Country country;
// Getters and Setters
}
The mapping of Comment.class is:
#Entity
#Table(name = "CRM_COMMENT")
public class Comment implements Serializable {
private static final long serialVersionUID = -104145851368148154L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "COMMENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name = "ARTICLE_ID")
private Article article;
#ManyToOne
#JoinColumn(name = "USER_FK", nullable = false)
private User createdUser;
#Column(nullable = false)
private String comment;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
#Column(name = "MODIFICATION_DATE")
private Date modificationDate;
#OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinTable(name = "CRM_COMMENT_LIKE",
joinColumns = {#JoinColumn(name = "COMMENT_ID")},
inverseJoinColumns = {#JoinColumn(name = "USER_ID")})
private Set<User> fans = new LinkedHashSet<>();
// Getters and Setters
}
The mapping of Article.class is:
#Entity
#Table(name = "CRM_ARTICLE")
public class Article implements Serializable {
// other properties
#OneToMany(mappedBy = "article", cascade = {CascadeType.ALL}, orphanRemoval = true)
#OrderBy("id DESC")
private Set<Comment> comments = new LinkedHashSet<>();
// Getters and Setters
}
The problem is related to my ManyToMany relation between the Comment and User - CRM_COMMENT_LIKE.
Actually, when I add some new 'fan' into Comment, there is no problem.
#Override
public boolean giveAnLikeToComment(Long commentId, User fan) {
Comment comment = commentDao.get(commentId);
if (Objects.isNull(comment)|| BooleanUtils.isTrue(comment.getFans().contains(fan))) {
return false;
}
comment.getFans().add(fan);
commentDao.update(comment);
return true;
}
The problem arises when I try to delete some comment, which has at least one 'like'/'fan' to it.
#Override
public boolean deleteCommentById(final Long commentId) {
Comment comment = commentDao.get(commentId);
if (Objects.nonNull(comment)) {
Article article = comment.getArticle();
article.getComments().remove(comment);
comment.setFans(null); // This line fix the problem
articleDao.update(article);
return true;
}
return false;
}
So in this case, I manage the relation between an Article ( which is parent of a Comment) and the comment itself. This is easy, because the connection between them is bidirectional. But what about the fans? I can't remove the connection between a Comment and CRM_COMMENT_LIKE relation, because the User doesn't know about the CRM_COMMENT_LIKE or about the Comments. Something more, I want, when I remove a Comment, to remove and all created relations in CRM_COMMENT_LIKE. But I'm prevent, because Hibernate throws an exception which says:
deleted object would be re-saved by cascade (remove deleted object from associations):
[crm.alltogether.core.admin.model.User#1]; nested exception is
org.hibernate.ObjectDeletedException: deleted object would be re-saved
by cascade (remove deleted object from associations):
[crm.alltogether.core.admin.model.User#1]
This is my issue, so if you have a suggestion, I would be glad to read it :)
Best Regards,
You need to have a orphanRemoval=true cascading between Article and Comment. Then you would do this.
if (Objects.nonNull(comment)) {
Article a = comment.getArticle();
a.getComments().remove(comment);
articleDao.saveOrUpdate(a);
return true;
}
This will take care of deleting orphan comment, as you already have a cascade all on fans it would delete that association as well. I would suggest you to play around with cascade orphanRemoval=true on fans as well.

How to create join table with JPA annotations?

I need to create a join table in my database using JPA annotations so the result will be this:
So far I just implemented 2 entities:
#Entity
#Table(name="USERS", schema="ADMIN")
public class User implements Serializable {
private static final long serialVersionUID = -1244856316278032177L;
#Id
#Column(nullable = false)
private String userid;
#Column(nullable = false)
private String password;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
#Entity
#Table(name="GROUPS", schema="ADMIN")
public class Group implements Serializable {
private static final long serialVersionUID = -7274308564659753174L;
#Id
#Column(nullable = false)
private String groupid;
public String getGroupid() {
return groupid;
}
public void setGroupid(String groupid) {
this.groupid = groupid;
}
}
Should i create another entity called USER_GROUP or i can just add some annotations, so the join table will be created automatically when i run create tables from entities(ORM)?
How should i annotate my entities to achieve the same as in the image?
You definitely shouldn't create User_Group entity as it's more the underlying database representation than the object oriented one.
You can achieve the join table by defining something like:
#Entity
#Table(name="USERS", schema="ADMIN")
public class User implements Serializable {
//...
#ManyToOne
#JoinTable(name="USER_GROUP")
Group group;
#Entity
#Table(name="GROUPS", schema="ADMIN")
public class Group implements Serializable {
//...
#OneToMany(mappedBy="group")
Set<User> users;
Edit: If you want to explicitly set the names of the columns you could use #JoinColumn elements as shown below:
#ManyToOne
#JoinTable(name="USER_GROUP",
joinColumns = #JoinColumn(name = "userid",
referencedColumnName = "userid"),
inverseJoinColumns = #JoinColumn(name = "groupid",
referencedColumnName = "groupid"))
Group group;
I would implement it this way:
#Entity
#Table(name="GROUPS", schema="ADMIN")
public class Group implements Serializable {
#OneToMany
#JoinTable(name = "USER_GROUP",
joinColumns = #JoinColumn(name = "groupid"),
inverseJoinColumns = #JoinColumn(name = "userid"))
private List<User> users;
}
Solution suggested by #PedroKowalski should work too, but then you'll have to keep a reference to Group entity in your User entity which is not always possible.
To have the same annotations like in your diagram you can do this in your User class:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "USER_GROUP",
joinColumns = { #JoinColumn(name = "userid") },
inverseJoinColumns = { #JoinColumn(name = "groupid") })
private List<Group> grups;
in your group class
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "USER_GROUP",
joinColumns = { #JoinColumn(name = "groupid") },
inverseJoinColumns = { #JoinColumn(name = "userid") })
private List<User> users;
I'm wondering what is the point to create a Join Table in this way, considering that we can't access directly for queries?
JPA doesn't allow to make queries directly to the Join Table, so if the user want to do an operation on USER_GROUP, he has to creare a normal join query between users and groups; due to this, the join table USER_GROUP is useless.

Categories